merged Philips loaders into one

This commit is contained in:
Horst Beham
2021-01-09 12:06:32 +01:00
parent 24b19b2c17
commit fd603ac8ec
94 changed files with 1808 additions and 632 deletions

View File

@@ -0,0 +1,700 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using ChanSort.Api;
namespace ChanSort.Loader.Philips
{
/*
channellib\CableDigSrvTable:
===========================
Channels in this file are not physically 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.
Also, all observed channel records have progNr either equal to the physical index + 1 or to 0xffff.
Each channel record with progNr 0xFFFF causes a gap in the progNr sequence.
It is unclear:
- if the next/prev list MUST be kept linear
- channel records MUST be physically ordered to be in-sync with the next/prev list
- channel records MUST be physically ordered by progNr (allowing 0xFFFF for gaps)
To be on the safe side, this code keeps the list linear, physically reorders the records to match the progNr.
Since we don't show deleted channels in the UI, we can't keep the gaps caused by them in the channel list. They will be appended at the end and the gaps closed.
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 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 BinarySerializer : SerializerBase
{
private readonly IniFile ini;
private readonly List<string> dataFilePaths = new List<string>();
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");
private ChanLstBin chanLstBin;
private readonly StringBuilder logMessages = new StringBuilder();
#region ctor()
public BinarySerializer(string inputFile) : base(inputFile)
{
this.Features.ChannelNameEdit = ChannelNameEditMode.None;
this.Features.CanSkipChannels = false;
this.Features.CanLockChannels = true;
this.Features.CanHideChannels = false;
this.Features.DeleteMode = DeleteMode.NotSupported;
this.Features.CanSaveAs = false;
this.Features.CanHaveGaps = false;
this.Features.SupportedFavorites = Favorites.A;
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;
this.DataRoot.AddChannelList(this.dvbtChannels);
this.DataRoot.AddChannelList(this.dvbcChannels);
this.DataRoot.AddChannelList(this.satChannels);
foreach (var list in this.DataRoot.ChannelLists)
{
list.VisibleColumnFieldNames.Remove("Skip");
list.VisibleColumnFieldNames.Remove("ShortName");
list.VisibleColumnFieldNames.Remove("ServiceTypeName");
list.VisibleColumnFieldNames.Remove("Hidden");
list.VisibleColumnFieldNames.Remove("AudioPid");
list.VisibleColumnFieldNames.Remove("Encrypted");
}
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);
}
#endregion
#region Load()
public override void Load()
{
if (!SetFileNameToChanLstBin())
throw new FileLoadException("Unsupported folder structure. Required files are:\n"
+ "ChannelList\\chanLst.bin\n"
+ "ChannelList\\channellib\\CableDigSrvTable\n"
+ "ChannelList\\s2channellib\\service.dat");
this.chanLstBin = new ChanLstBin();
this.chanLstBin.Load(this.FileName, msg => this.logMessages.AppendLine(msg));
var dir = Path.GetDirectoryName(this.FileName) ?? "";
var channellib = Path.Combine(dir, "channellib");
var s2channellib = Path.Combine(dir, "s2channellib");
// channellib files for DVB-C/T in version 1.1 and 1.2
LoadDvbCT(dvbtChannels, Path.Combine(channellib, "AntennaDigSrvTable"), "CableDigSrvTable_entry");
LoadDvbCTPresets(dvbtChannels, Path.Combine(channellib, "AntennaPresetTable"));
LoadDvbCT(dvbcChannels, Path.Combine(channellib, "CableDigSrvTable"), "CableDigSrvTable_entry");
LoadDvbCTPresets(dvbcChannels, Path.Combine(channellib, "CablePresetTable"));
// s2channellib files for DVB-S in version 1.1 and 1.2
LoadDvbsSatellites(Path.Combine(s2channellib, "satellite.dat"));
LoadDvbsTransponders(Path.Combine(s2channellib, "tuneinfo.dat"));
LoadDvbS(satChannels, Path.Combine(s2channellib, "service.dat"), "service.dat_entry");
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);
// version 45
if (chanLstBin.VersionMajor == 45)
LoadDvbS(satChannels, Path.Combine(s2channellib, "SatelliteDb.bin"), "Map45_SatelliteDb_entry");
// 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())
{
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 dirName = Path.GetFileName(dir);
if (StringComparer.InvariantCultureIgnoreCase.Compare(dirName, "channellib") == 0 || StringComparer.InvariantCultureIgnoreCase.Compare(dirName, "s2channellib") == 0)
{
dir = Path.GetDirectoryName(dir) ?? "";
dirName = Path.GetFileName(dir);
}
if (StringComparer.InvariantCultureIgnoreCase.Compare(dirName, "ChannelList") != 0)
return false;
var chanLstBin = Path.Combine(dir, "chanLst.bin");
if (!File.Exists(chanLstBin))
return false;
if (!File.Exists(Path.Combine(dir, "channellib", "CableDigSrvTable")))
return false;
if (!File.Exists(Path.Combine(dir, "s2channellib", "service.dat")))
return false;
this.FileName = chanLstBin; // this file is used as a fixed reference point for the whole directory structure
return true;
}
#endregion
#region LoadDvbCT
private void LoadDvbCT(ChannelList list, string path, string mappingName)
{
if (!ReadAndValidateChannellibFile(path, out var data, out var recordSize, out var recordCount))
return;
var mapping = new DataMapping(this.ini.GetSection(mappingName));
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<ulong, Channel>();
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 false;
data = File.ReadAllBytes(path);
if (data.Length < 20)
return false;
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);
return true;
}
#endregion
#region LoadDvbsSatellites()
private void LoadDvbsSatellites(string path)
{
if (!File.Exists(path))
return;
var data = File.ReadAllBytes(path);
if (data.Length < 4)
return;
var checksum = BitConverter.ToUInt32(data, data.Length - 4);
var crc = ~Crc32.Reversed.CalcCrc32(data, 0, data.Length - 4);
if (checksum != crc)
return;
int recordSize = BitConverter.ToInt32(data, 4);
int recordCount = BitConverter.ToInt32(data, 8);
// 12 byte header, table of (next, prev) transponder, records, crc32
if (data.Length != 12 + recordCount * 4 + recordCount * recordSize + 4)
return;
var baseOffset = 12 + recordCount * 4;
for (int i = 0; i < recordCount; i++, baseOffset += recordSize)
{
if (data[baseOffset + 0] == 0)
continue;
var s = new Satellite(i);
var pos = (sbyte)data[baseOffset + 8];
s.OrbitalPosition = pos < 0 ? -pos + "W" : pos + "E";
s.Name = this.DefaultEncoding.GetString(data, baseOffset + 16, 16).TrimEnd('\0');
this.DataRoot.AddSatellite(s);
}
}
#endregion
#region LoadDvbsTransponders
private void LoadDvbsTransponders(string path)
{
if (!File.Exists(path))
return;
var data = File.ReadAllBytes(path);
if (data.Length < 4)
return;
var checksum = BitConverter.ToUInt32(data, data.Length - 4);
var crc = ~Crc32.Reversed.CalcCrc32(data, 0, data.Length - 4);
if (checksum != crc)
return;
int recordSize = BitConverter.ToInt32(data, 4);
int recordCount = BitConverter.ToInt32(data, 8);
// 12 byte header, table of (next, prev) transponder, records, crc32
if (data.Length != 12 + recordCount * 4 + recordCount * recordSize + 4)
return;
var baseOffset = 12 + recordCount * 4;
for (int i = 0; i < recordCount; i++, baseOffset += recordSize)
{
var symRate = BitConverter.ToUInt16(data, baseOffset + 0);
if (symRate == 0xFFFF)
continue;
var tsid = BitConverter.ToUInt16(data, baseOffset + 16);
var onid = BitConverter.ToUInt16(data, baseOffset + 18);
var t = new Transponder(i);
t.SymbolRate = symRate;
t.FrequencyInMhz = BitConverter.ToUInt16(data, baseOffset + 2);
var satIndex = data[baseOffset + 6] >> 4; // guesswork
t.Satellite = DataRoot.Satellites.TryGet(satIndex);
t.TransportStreamId = tsid;
t.OriginalNetworkId = onid;
this.DataRoot.AddTransponder(t.Satellite, t);
}
}
#endregion
#region LoadDvbS
private void LoadDvbS(ChannelList list, string path, string mappingName)
{
if (!File.Exists(path))
return;
var data = File.ReadAllBytes(path);
if (data.Length < 4)
return;
if (chanLstBin.VersionMajor == 1)
{
var checksum = BitConverter.ToUInt32(data, data.Length - 4);
var crcObj = new Crc32(false, Crc32.NormalPoly);
var crc = ~crcObj.CalcCrc32(data, 0, data.Length - 4);
if (checksum != crc)
throw new FileLoadException("Invalid CRC32 in " + path);
}
int recordSize = BitConverter.ToInt32(data, 4);
int recordCount = BitConverter.ToInt32(data, 8);
if (chanLstBin.VersionMajor == 1)
{
// 12 bytes header, then a "next/prev" table, then the service records, then a CRC32
// the "next/prev" table is a ring-list, every entry consists of 2 ushorts with the next and previous channel, wrapping around on the ends
if (data.Length != 12 + recordCount * 4 + recordCount * recordSize + 4)
throw new FileLoadException("Unsupported file content: " + path);
}
this.dataFilePaths.Add(path);
var dvbStringDecoder = new DvbStringDecoder(this.DefaultEncoding);
var mapping = new DataMapping(this.ini.GetSection(mappingName));
mapping.SetDataPtr(data, 12 + recordCount * 4);
for (int i = 0; i < recordCount; i++, mapping.BaseOffset += recordSize)
{
var ch = LoadDvbsChannel(list, mapping, i, dvbStringDecoder);
this.DataRoot.AddChannel(list, ch);
}
}
#endregion
#region LoadDvbsChannel
private ChannelInfo LoadDvbsChannel(ChannelList list, DataMapping mapping, int recordIndex, DvbStringDecoder dvbStringDecoder)
{
var transponderId = mapping.GetWord("offTransponderIndex");
var progNr = mapping.GetWord("offProgNr");
var ch = new ChannelInfo(list.SignalSource, recordIndex, progNr, null);
// deleted channels must be kept in the list because their records must also be physically reordered when saving the list
if (progNr == 0xFFFF || transponderId == 0xFFFF)
{
ch.IsDeleted = true;
ch.OldProgramNr = -1;
return ch;
}
// onid, tsid, pcrpid and vpid can be 0 in some lists
ch.PcrPid = mapping.GetWord("offPcrPid") & mapping.GetMask("maskPcrPid");
ch.Lock = mapping.GetFlag("Locked");
ch.OriginalNetworkId = mapping.GetWord("OffOnid");
ch.TransportStreamId = mapping.GetWord("offTsid");
ch.ServiceId = mapping.GetWord("offSid");
ch.VideoPid = mapping.GetWord("offVpid") & mapping.GetMask("maskVpid");
ch.Favorites = mapping.GetFlag("IsFav") ? Favorites.A : 0;
ch.OldProgramNr = progNr;
// the 0x1F as the first byte of the channel name is likely the DVB encoding indicator for UTF-8. So we use the DvbStringDecoder here
dvbStringDecoder.GetChannelNames(mapping.Data, mapping.BaseOffset + mapping.GetConst("offName", 0), mapping.GetConst("lenName", 0), out var longName, out var shortName);
ch.Name = longName.TrimEnd('\0');
ch.ShortName = shortName.TrimEnd('\0');
dvbStringDecoder.GetChannelNames(mapping.Data, mapping.BaseOffset + mapping.GetConst("offProvider", 0), mapping.GetConst("lenProvider", 0), out var provider, out _);
ch.Provider = provider.TrimEnd('\0');
// copy values from the satellite/transponder tables to the channel
if (this.DataRoot.Transponder.TryGetValue(transponderId, out var t))
{
ch.Transponder = t;
ch.FreqInMhz = t.FrequencyInMhz;
ch.SymbolRate = t.SymbolRate;
ch.SatPosition = t.Satellite?.OrbitalPosition;
ch.Satellite = t.Satellite?.Name;
if (t.OriginalNetworkId != 0)
ch.OriginalNetworkId = t.OriginalNetworkId;
if (t.TransportStreamId != 0)
ch.TransportStreamId = t.TransportStreamId;
}
return ch;
}
#endregion
#region LoadDvbsFavorites
private void LoadDvbsFavorites(string path)
{
if (!File.Exists(path))
return;
var data = File.ReadAllBytes(path);
if (data.Length < 4)
return;
var checksum = BitConverter.ToUInt32(data, data.Length - 4);
var crc = ~Crc32.Reversed.CalcCrc32(data, 0, data.Length - 4);
if (checksum != crc)
return;
int dataSize = BitConverter.ToInt32(data, 0);
var recordSize = 4;
var recordCount = (dataSize - 4) / recordSize;
// 4 byte header, data, crc32
if (data.Length != 4 + dataSize + 4)
return;
this.dataFilePaths.Add(path);
int firstFavIndex = BitConverter.ToInt16(data, 4);
int favCount = BitConverter.ToInt16(data, 6);
if (favCount > recordCount || firstFavIndex < 0 || firstFavIndex >= recordCount)
return;
var baseOffset = 8;
for (int i = 0, curFav = firstFavIndex; i < favCount; i++)
{
this.satChannels.Channels[curFav].SetOldPosition(1, i + 1);
curFav = BitConverter.ToInt16(data, baseOffset + curFav * 4 + 2);
}
}
#endregion
#region GetDataFilePaths
/// <summary>
/// List of files for backup/restore
/// </summary>
public override IEnumerable<string> GetDataFilePaths() => this.dataFilePaths;
#endregion
#region Save()
public override void Save(string tvOutputFile)
{
var dir = Path.GetDirectoryName(this.FileName) ?? "";
var channellib = Path.Combine(dir, "channellib");
var s2channellib = Path.Combine(dir, "s2channellib");
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(s2channellib, "service.dat"));
SaveDvbsFavorites(Path.Combine(s2channellib, "favorite.dat"));
SaveDvbsDbFileInfo(Path.Combine(s2channellib, "db_file_info.dat"));
this.chanLstBin.Save(this.FileName);
}
#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
#region SaveDvbsChannels
private void SaveDvbsChannels(string path)
{
var orig = File.ReadAllBytes(path);
int recordSize = BitConverter.ToInt32(orig, 4);
int recordCount = BitConverter.ToInt32(orig, 8);
// create a new array for the modified data, copying the header and next/prev table
var data = new byte[orig.Length];
Array.Copy(orig, data, 12 + recordCount * 4);
var baseOffset = 12 + recordCount * 4;
var mapping = new DataMapping(this.ini.GetSection("service.dat_entry"));
mapping.SetDataPtr(data, baseOffset);
// copy physical records to bring them in the new order and update fields like progNr
// this way the linked next/prev list remains in-sync with the channel order
int i = 0;
foreach (var ch in this.satChannels.Channels.OrderBy(c => c.NewProgramNr <= 0 ? int.MaxValue : c.NewProgramNr).ThenBy(c => c.OldProgramNr))
{
mapping.BaseOffset = baseOffset + i * recordSize;
Array.Copy(orig, baseOffset + (int)ch.RecordIndex * recordSize, data, mapping.BaseOffset, recordSize);
if (ch.IsDeleted)
{
mapping.SetWord("offSid", 0xFFFF);
mapping.SetWord("offTransponderIndex", 0xFFFF);
mapping.SetWord("offProgNr", 0xFFFF);
}
else
{
mapping.SetWord("offProgNr", ch.NewProgramNr);
mapping.SetFlag("IsFav", ch.Favorites != 0);
mapping.SetFlag("Locked", ch.Lock);
}
ch.RecordIndex = i++; // required so that subsequent saves don't reshuffle the records
}
var crc32 = ~Crc32.Reversed.CalcCrc32(data, 0, data.Length - 4);
data.SetInt32(data.Length-4, (int)crc32);
File.WriteAllBytes(path, data);
var backupFile = path.Replace(".dat", "_backup.dat");
File.WriteAllBytes(backupFile, data);
}
#endregion
#region SaveDvbsFavorites
private void SaveDvbsFavorites(string path)
{
var data = File.ReadAllBytes(path);
int dataSize = BitConverter.ToInt32(data, 0);
var recordSize = 4;
var recordCount = (dataSize - 4) / recordSize;
var favList = this.satChannels.Channels.Where(c => c.FavIndex[0] != -1).OrderBy(c => c.FavIndex[0]).ToList();
var favCount = favList.Count;
var firstFavIndex = favCount == 0 ? -1 : (int)favList[0].RecordIndex;
data.SetInt16(4, firstFavIndex);
data.SetInt16(6, favCount);
data.MemSet(8, 0xFF, recordCount * 4);
if (favCount > 0)
{
var prevFav = (int) favList[favList.Count - 1].RecordIndex;
var curFav = firstFavIndex;
var nextFav = (int) favList[1 % favCount].RecordIndex;
for (int i = 0; i < favCount; i++)
{
var ch = favList[i];
var off = 8 + (int) ch.RecordIndex * 4;
data.SetInt16(off + 0, prevFav);
data.SetInt16(off + 2, nextFav);
prevFav = curFav;
curFav = nextFav;
nextFav = (int) favList[(i + 2) % favCount].RecordIndex;
}
}
var crc32 = ~Crc32.Reversed.CalcCrc32(data, 0, data.Length - 4);
data.SetInt32(data.Length - 4, (int)crc32);
File.WriteAllBytes(path, data);
var backupFile = path.Replace(".dat", "_backup.dat");
File.WriteAllBytes(backupFile, data);
}
#endregion
#region SaveDvbsDbFileInfo
private void SaveDvbsDbFileInfo(string path)
{
var data = File.ReadAllBytes(path);
// the ushort at offset 10 is incremented by 4 every time a change is made to the list (maybe the lower 2 bits of that fields are used for something else)
var offset = 10;
data.SetInt16(offset,data.GetInt16(offset) + 4);
var crc32 = ~Crc32.Reversed.CalcCrc32(data, 0, data.Length - 4);
data.SetInt32(data.Length - 4, (int)crc32);
File.WriteAllBytes(path, data);
var backupFile = path.Replace(".dat", "_backup.dat");
File.WriteAllBytes(backupFile, data);
}
#endregion
#region FaultyCrc32
public static uint FaultyCrc32(byte[] bytes, int start, int count)
{
var crc = 0xFFFFFFFF;
var off = start;
for (int i = 0; i < count; i++, off++)
{
var b = bytes[off];
for (int j = 0; j < 8; j++)
{
crc <<= 1;
var b1 = (uint)b >> 7;
var b2 = crc >> 31;
if (b1 != b2)
crc ^= 0x04C11DB7;
b <<= 1;
}
}
return ~crc;
}
#endregion
public override string GetFileInformation()
{
return base.GetFileInformation() + this.logMessages.Replace("\n", "\r\n");
}
}
}

View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ChanSort.Api;
namespace ChanSort.Loader.Philips
{
class ChanLstBin
{
// the chanLst.bin file contains the model name and CRC16 values for all the files in the channellib and s2channellib subdirectories
/*
public struct Ph_chanLst_bin
{
word versionMinor;
word versionMajor;
byte unknown4[14];
dword modelNameLen;
char modelName[modelNameLen];
Ph_chanLst_bin_FileEntry fileEntries[*];
};
struct Ph_chanLst_bin_FileEntry
{
dword fileNameLength;
char fileName[fileNameLength];
if (fileName[0] != '/')
word crc16modbus;
};
*/
private byte[] content;
private readonly Dictionary<string,int> crcOffsetByRelPath = new Dictionary<string, int>();
public uint VersionMinor { get; private set; }
public uint VersionMajor { get; private set; }
private Action<string> log;
public void Load(string path, Action<string> log)
{
this.log = log;
this.content = File.ReadAllBytes(path);
var off = 0;
VersionMinor = BitConverter.ToUInt16(content, off);
off += 2;
VersionMajor = BitConverter.ToUInt16(content, off);
off += 2;
// skip unknown 14 bytes
off += 14;
var modelNameLen = BitConverter.ToInt32(content, off);
off += 4 + modelNameLen;
var baseDir = Path.GetDirectoryName(path);
var relPath = "/channellib/";
while (off < content.Length)
{
var fileNameLen = BitConverter.ToInt32(content, off);
off += 4;
var fileName = Encoding.ASCII.GetString(content, off, fileNameLen).TrimEnd('\0');
off += fileNameLen;
if (fileName[0] == '/')
relPath = fileName;
else
{
// normally all files after the /s2channellib/ entry are inside that folder, but "Favorite.xml" is in the main folder
var newPath = relPath + fileName;
if (!File.Exists(Path.Combine(baseDir, newPath)) && File.Exists(Path.Combine(baseDir, fileName)))
newPath = "/" + fileName;
crcOffsetByRelPath[newPath] = off;
off += 2;
}
}
this.Validate(path);
}
private void Validate(string chanLstBinPath)
{
var baseDir = Path.GetDirectoryName(chanLstBinPath);
string errors = "";
foreach (var entry in crcOffsetByRelPath)
{
var crcOffset = entry.Value;
var expectedCrc = BitConverter.ToUInt16(this.content, crcOffset);
if (expectedCrc == 0)
continue;
var filePath = baseDir + entry.Key;
if (!File.Exists(filePath))
continue;
var data = File.ReadAllBytes(filePath);
var length = Math.Min(data.Length, VersionMajor <= 12 ? 0x6000 : 0x145A00);
var actualCrc = Crc16.Calc(data, 0, length);
if (actualCrc != expectedCrc)
{
var msg = $"chanLst.bin: stored CRC for {entry.Key} is {expectedCrc:x4} but calculated {actualCrc:x4}";
errors += "\n" + msg;
}
}
if (errors != "")
{
this.log.Invoke(errors);
//throw new FileLoadException(errors);
}
}
public void Save(string chanLstBinPath)
{
var baseDir = Path.GetDirectoryName(chanLstBinPath);
foreach (var entry in crcOffsetByRelPath)
{
var path = baseDir + entry.Key;
var data = File.ReadAllBytes(path);
var length = Math.Min(data.Length, VersionMajor <= 12 ? 0x6000 : 0x145A00);
var crc = Crc16.Calc(data, 0, length);
var off = entry.Value;
content[off] = (byte) crc;
content[off + 1] = (byte) (crc >> 8);
}
File.WriteAllBytes(chanLstBinPath, content);
}
}
}

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ChanSort.Loader.Philips</RootNamespace>
<AssemblyName>ChanSort.Loader.Philips</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>latest</LangVersion>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<LangVersion>latest</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<LangVersion>latest</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ChanLstBin.cs" />
<Compile Include="Channel.cs" />
<Compile Include="CustomXmlWriter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="BinarySerializer.cs" />
<Compile Include="PhilipsLoader.cs" />
<Compile Include="XmlSerializer.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ChanSort.Api\ChanSort.Api.csproj">
<Project>{dccffa08-472b-4d17-bb90-8f513fc01392}</Project>
<Name>ChanSort.Api</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="ChanSort.Loader.Philips.ini">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,67 @@
# ChannelMap_11 format
[service.dat_entry]
offPcrPid=0
maskPcrPid=0x1FFF
offLocked=3
maskLocked=0x10
offOnid=4
offTsid=6
offSid=8
offTransponderIndex=10
offVpid=16
maskVpid=0x1FFF
offIsFav=17
maskIsFav=0x80
offProgNr=20
offName=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
# ChannelMap_45 format
[Map45_SatelliteDbBin_entry]
offId=0
offFreq=4
offProgNr=8
offAnalogUid=12
offOnid=16
offTsid=20
offSid=24
offSymbolRate=28
offLogoNr=32
offScrambleStat=36
offLocked=40
offModulateion=44
offServiceType=52
offName=80
lenName=64
offSatName=146
lenSatName=64

View File

@@ -0,0 +1,32 @@
using System.Xml;
using ChanSort.Api;
namespace ChanSort.Loader.Philips
{
internal class Channel : ChannelInfo
{
public Channel(SignalSource source, long index, int oldProgNr, string name) : base(source, index, oldProgNr, name)
{
}
internal Channel(SignalSource source, int order, int rowId, XmlNode setupNode)
{
this.SignalSource = source;
this.RecordOrder = order;
this.RecordIndex = rowId;
this.SetupNode = setupNode;
}
/// <summary>
/// index of the record in the AntennaPresetTable / CablePresetTable file for the channel, matched by (onid + tsid + sid)
/// </summary>
public int PresetTableIndex { 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;
}
}

View File

@@ -0,0 +1,170 @@
using System;
using System.IO;
using System.Xml;
namespace ChanSort.Loader.Philips
{
/// <summary>
/// This XmlWriter replaces some characters with Char- or Entity- references the same way
/// they are escaped in the original Sony XML files
/// </summary>
class CustomXmlWriter : XmlWriter
{
private static readonly char[] CharsToEscape = { '\'', '\"', '&', '<', '>' };
private static readonly string[] CharEntites = { "apos", "quot", "amp", "lt", "gt" };
private XmlWriter w;
private readonly bool escapeAsEntityRef; // if true, use &amp; otherwise &#34;
public CustomXmlWriter(TextWriter tw, XmlWriterSettings settings, bool useEntityRef)
{
this.w = XmlWriter.Create(tw, settings);
this.escapeAsEntityRef = useEntityRef;
}
public override void WriteString(string text)
{
int i = 0, j;
while ((j = text.IndexOfAny(CharsToEscape, i)) >= 0)
{
this.w.WriteString(text.Substring(i, j - i));
if (this.escapeAsEntityRef)
{
// => &amp;
int k = Array.IndexOf(CharsToEscape, text[j]);
this.w.WriteEntityRef(CharEntites[k]);
}
else
{
// => &#38;
//this.w.WriteCharEntity(text[j]);
this.w.WriteRaw("&#" + (int)text[j] + ";");
}
i = j + 1;
}
this.w.WriteString(text.Substring(i));
}
#region 1:1 delegation
public override void WriteStartDocument()
{
this.w.WriteStartDocument();
}
public override void WriteStartDocument(bool standalone)
{
this.w.WriteStartDocument(standalone);
}
public override void WriteEndDocument()
{
this.w.WriteEndDocument();
}
public override void WriteDocType(string name, string pubid, string sysid, string subset)
{
this.w.WriteDocType(name, pubid, sysid, subset);
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
this.w.WriteStartElement(prefix, localName, ns);
}
public override void WriteEndElement()
{
this.w.WriteEndElement();
}
public override void WriteFullEndElement()
{
this.w.WriteFullEndElement();
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
this.w.WriteStartAttribute(prefix, localName, ns);
}
public override void WriteEndAttribute()
{
this.w.WriteEndAttribute();
}
public override void WriteCData(string text)
{
this.w.WriteCData(text);
}
public override void WriteComment(string text)
{
this.w.WriteComment(text);
}
public override void WriteProcessingInstruction(string name, string text)
{
this.w.WriteProcessingInstruction(name, text);
}
public override void WriteEntityRef(string name)
{
this.w.WriteEntityRef(name);
}
public override void WriteCharEntity(char ch)
{
this.w.WriteCharEntity(ch);
}
public override void WriteWhitespace(string ws)
{
this.w.WriteWhitespace(ws);
}
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
this.w.WriteSurrogateCharEntity(lowChar, highChar);
}
public override void WriteChars(char[] buffer, int index, int count)
{
this.w.WriteChars(buffer, index, count);
}
public override void WriteRaw(char[] buffer, int index, int count)
{
this.w.WriteRaw(buffer, index, count);
}
public override void WriteRaw(string data)
{
this.w.WriteRaw(data);
}
public override void WriteBase64(byte[] buffer, int index, int count)
{
this.w.WriteBase64(buffer, index, count);
}
public override void Close()
{
this.w.Close();
}
public override void Flush()
{
this.w.Flush();
}
public override string LookupPrefix(string ns)
{
return this.w.LookupPrefix(ns);
}
public override WriteState WriteState => this.w.WriteState;
#endregion
}
}

View File

@@ -0,0 +1,89 @@
namespace ChanSort.Loader.PhilipsBin
{
class Serializer
{
[DllImport("Cable.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ConvertToXML_Cable([MarshalAs(UnmanagedType.LPArray)] byte[] path, [MarshalAs(UnmanagedType.LPArray)] byte[] read_buff);
[DllImport("Cable.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ConvertToBIN_Cable([MarshalAs(UnmanagedType.LPArray)] byte[] read_buff);
//[DllImport("Cable.dll")]
//public static extern int GetFavoriteList([MarshalAs(UnmanagedType.I4)] int ListId, [MarshalAs(UnmanagedType.LPArray)] int[] NoOfRecords, [MarshalAs(UnmanagedType.LPArray)] int[] ChannelIdList);
//[DllImport("Cable.dll")]
//public static extern int SetFavoriteList([MarshalAs(UnmanagedType.I4)] int ListId, [MarshalAs(UnmanagedType.I4)] int NoOfRecords, [MarshalAs(UnmanagedType.LPArray)] int[] ChannelIdList);
[DllImport("dvbs2_cte.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ConvertToXML_Satellite([MarshalAs(UnmanagedType.LPArray)] byte[] path, [MarshalAs(UnmanagedType.LPArray)] byte[] read_buff);
//public static extern int ConvertToXML_Satellite(IntPtr pathAs8BitChar, [MarshalAs(UnmanagedType.LPArray)] byte[] read_buff);
[DllImport("dvbs2_cte.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ConvertToBin_Satellite([MarshalAs(UnmanagedType.LPArray)] byte[] read_buff);
/*
[DllImport("dvbs2_cte.dll")]
public static extern int GetFavoriteList([MarshalAs(UnmanagedType.I4)] int ListId, [MarshalAs(UnmanagedType.LPArray)] int[] NoOfRecords, [MarshalAs(UnmanagedType.LPArray)] int[] ChannelIdList);
[DllImport("dvbs2_cte.dll")]
public static extern int SetFavoriteList([MarshalAs(UnmanagedType.I4)] int ListId, [MarshalAs(UnmanagedType.I4)] int NoOfRecords, [MarshalAs(UnmanagedType.LPArray)] int[] ChannelIdList);
*/
[DllImport("kernel32.dll")]
private static extern int LoadLibrary(string strLib);
[DllImport("kernel32.dll")]
private static extern int FreeLibrary(int iModule);
[DllImport("kernel32.dll")]
private static extern IntPtr GetProcAddress(int iModule, string strProcName);
#region Load()
public override void Load()
{
var dir = Path.GetDirectoryName(this.FileName) + "\\";
var fname = Encoding.Default.GetBytes(dir);
//var ptr = Marshal.AllocHGlobal(enc.Length);
//var handle = GCHandle.Alloc(fname, GCHandleType.Pinned);
//var ptr = handle.AddrOfPinnedObject();
var arr = new byte[10 * 1024 * 1024];
//int r1 = 0;
//PhilipsChannelEditor.BinaryDll.CSatellite.GetBinaryFilesToXML(ref r1, dir);
var hLib = LoadLibrary("Cable.dll");
var addr = GetProcAddress(hLib, "ConvertToBIN_Cable");
try
{
//var r1 = PhilipsChannelEditor.BinaryDll.CSatellite.ConvertToXML_Satellite(fname, arr);
//var r1 = ConvertToXML_Cable(ptr, arr);
var sat = this.FileName.Contains("\\s2");
var r1 = sat ? ConvertToXML_Satellite(fname, arr) : ConvertToXML_Cable(fname, arr);
if (r1 != 0)
throw new FileLoadException("Philips DLL returned error code loading file: " + r1);
int len = 0;
while (arr[len] != 0)
++len;
using (var file = new FileStream(@"c:\temp\philips.xml", FileMode.Create))
file.Write(arr, 0, len);
var arr2 = new byte[len + 1];
Array.Copy(arr, arr2, len);
arr2[len] = 0;
var r2 = sat ? ConvertToBin_Satellite(arr2) : ConvertToBIN_Cable(arr2);
if (r2 != 0)
throw new FileLoadException("Philips DLL returned error code saving file: " + r2);
}
finally
{
FreeLibrary(hLib);
}
}
#endregion
}
}

View File

@@ -0,0 +1,121 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using ChanSort.Api;
namespace ChanSort.Loader.Philips
{
public class PhilipsLoader : ISerializerPlugin
{
/*
* Philips has a whole lot of completely incompatible channel list file formats with different folder structures.
* Most formats have a chanLst.bin file, which contains a minor and major version number in the header and CRC16 checksums for various other files.
* (first word is the minor, second word the major)
*
* version -1 (not an official number):
* Repair\mgr_chan_dvbt.db (binary file, not a SQLite database)
* Repair\FLASH_*
* no chanLst.bin
*
* version 0 (not an official number):
* Repair\CM_*_LA_CK.BIN (+ a hidden .xml file with the actual channel list)
* e.g. 47PFL5008K
* no chanLst.bin
*
* version 1.1
* Repair\ChannelList\chanLst.bin
* Repair\ChannelList\channellib\CableDigSrvTable
* Repair\ChannelList\s2channellib\service.dat
* e.g. 32PFL5806K/02, 42PFL7656K/02
*
* version 1.2
* same as version 1.1
* e.g. 32PFL5507K/12, 42PFL4317K/12, 32PFL5507K/12
*
* version 11.1
* PhilipsChannelMaps\ChannelMap_11\ChannelList\chanLst.bin
* PhilipsChannelMaps\ChannelMap_11\ChannelList\channelLib\*Table (as with 1.1)
* PhilipsChannelMaps\ChannelMap_11\ChannelList\s2channellib\*.dat (as with 1.1)
* PhilipsChannelMaps\ChannelMap_11\ChannelList\s2channellib\Satellite*Table (new here)
* e.g. 55PFL8008S/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\channelLib\*Db.bin
* PhilipsChannelMaps\ChannelMap_45\ChannelList\s2channellib\*Db.bin
* e.g. 65PUS7601/12, 55PUS6581/12, 43PUS6401/12, 55PUS8601/12
*
* version 100.0
* PhilipsChannelMaps\ChannelMap_100\ChannelList\chanLst.bin
* PhilipsChannelMaps\ChannelMap_100\ChannelList\channellib\DVB*.xml
* PhilipsChannelMaps\ChannelMap_100\ChannelList\s2channellib\DVBS.xml
* e.g. 65PUS6754/12, 24PFT4022/12
*
* version 105.0
* PhilipsChannelMaps\ChannelMap_105\Favorite.xml
* rest like 100.0
* e.g. 43PUS7307/12, 49PUS8303/12, 65PUS8503/12, 55OLED803/12
*
* version 110.0
* same as 105.0
* e.g. 65PUS8535/12, 55PUS7334/12
*
*
* Version 0.1 and 100-110 are XML based and loaded through the XmlSerializer.
* Version 1.1 and 1.2 are loaded through the BinSerializer.
* Version 0.0, 11.1 and 45.1 are not supported yet.
*/
public string DllName { get; set; }
public string PluginName => "Philips";
public string FileFilter => "*.bin;*.xml";
public SerializerBase CreateSerializer(string inputFile)
{
int majorVersion = int.MinValue;
var filename = Path.GetFileName(inputFile).ToLower();
if (Regex.IsMatch(filename, @"^CM_.*\.(?:bin|xml)$", RegexOptions.IgnoreCase))
majorVersion = 0;
else
{
// allow the user to pick pretty much any file within a Repair\ChannelList or PhilipsChannelMaps\ChannelMap_xxx\ChannelList structure to find a chanLst.bin
var dir = Path.GetDirectoryName(inputFile);
while(true)
{
var path = Path.Combine(dir, "chanLst.bin");
if (File.Exists(path))
{
inputFile = path;
var data = File.ReadAllBytes(inputFile);
majorVersion = BitConverter.ToInt16(data, 2);
break;
}
var dirName = Path.GetFileName(dir).ToLower();
if (dirName == "channellib" || dirName == "s2channellib")
dir = Path.GetDirectoryName(dir);
else if (Directory.Exists(Path.Combine(dir, "PhilipsChannelMaps")))
dir = Path.Combine(dir, "PhilipsChannelMaps");
else if (Directory.Exists(Path.Combine(dir, "ChannelList")))
dir = Path.Combine(dir, "ChannelList");
else
{
var maps = Directory.GetDirectories(dir, "ChannelMap_*");
if (maps.Length > 0)
dir = maps[0];
else
break;
}
}
}
if (majorVersion == 0 || majorVersion >= 100 && majorVersion <= 110)
return new XmlSerializer(inputFile);
if (majorVersion == 1)
return new BinarySerializer(inputFile);
throw new FileLoadException($"Selected file must be either chanLst.bin or CM_*.xml/.bin");
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("Test.Loader.PhilipsBin")]
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ChanSort.Loader.PhilipsBin")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ChanSort.Loader.PhilipsBin")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1f52b5ec-a2f1-4e53-9e1a-4658296c5bb5")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,645 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using ChanSort.Api;
namespace ChanSort.Loader.Philips
{
/*
This loader supports 2 different kinds of XML files from Philips, the first in a "Repair" folder, the others in a "ChannelMap_xxx" folder
Example from Repair\CM_TPM1013E_LA_CK.xml:
<Channel>
<Setup oldpresetnumber="1" presetnumber="1" name="Das Erste" ></Setup>
<Broadcast medium="dvbc" frequency="410000" system="west" serviceID="1" ONID="41985" TSID="1101" modulation="256" symbolrate="6901000" bandwidth="Unknown"></Broadcast>
</Channel>
<Channel>
<Setup SatelliteName="0x54 0x00 0x55 0x00 0x52 0x00 0x4B 0x00 0x53 0x00 0x41 0x00 0x54 0x00 0x20 0x00 0x34 0x00 0x32 0x00 0x45 0x00 " ChannelNumber="1" ChannelName="0x54 0x00 0xC4 0x00 0xB0 0x00 0x56 0x00 0xC4 0x00 0xB0 0x00 0x42 0x00 0x55 0x00 0x20 0x00 0x53 0x00 0x50 0x00 0x4F 0x00 0x52 0x00 " ChannelLock="0" UserModifiedName="0" LogoID="0" UserModifiedLogo="0" LogoLock="0" UserHidden="0" FavoriteNumber="0" />
<Broadcast ChannelType="3" Onid="1070" Tsid="43203" Sid="16001" Frequency="11794" Modulation="0" ServiceType="1" SymbolRate="27507" LNBNumber="38" Polarization="0" SystemHidden="0" />
</Channel>
Example from a ChannelMap_100\ChannelList\channellib\DVBC.xml:
<Channel>
<Setup ChannelNumber="2" ChannelName="0x50 0x00 0x61 0x00 0x72 0x00 0x61 0x00 0x6D 0x00 0x6F 0x00 0x75 0x00 0x6E 0x00 0x74 0x00 0x20 0x00 0x43 0x00 0x68 0x00 0x61 0x00 0x6E 0x00 0x6E 0x00 0x65 0x00 0x6C 0x00 " LogoID="0" ChannelLock="0" UserModifiedName="0" UserModifiedLogo="0" LogoLock="0" UserHidden="0" FavoriteNumber="0" Scramble="0"></Setup>
<Broadcast ChannelType="3" Onid="1" Tsid="104" Sid="357" Frequency="386000000" Modulation="64" ServiceType="1" Bandwidth="8" SymbolRate="6900" DecoderType="2" SubType="0" NetworkID="0" StreamPriority="0" SystemHidden="0"></Broadcast>
</Channel>
Example from a ChannelMap_105\ChannelList\s2channellib\DVBS.xml:
<Channel>
<Setup SatelliteName="0x41 0x00 0x53 0x00 0x54 0x00 0x52 0x00 0x41 0x00 0x20 0x00 0x31 0x00 0x39 0x00 0x2E 0x00 0x32 0x00 0x45 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00" ChannelNumber="45" ChannelName="0x31 0x00 0x2D 0x00 0x32 0x00 0x2D 0x00 0x33 0x00 0x2E 0x00 0x74 0x00 0x76 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00" ChannelLock="0" UserModifiedName="0" LogoID="1441" UserModifiedLogo="0" LogoLock="0" UserHidden="0" FavoriteNumber="45" Scrambled="0"></Setup>
<Broadcast UniqueID="14" ChannelType="3" Onid="133" Tsid="5" Sid="1" Frequency="12460" Modulation="8-VSB" ServiceType="1" SymbolRate="27500" LNBNumber="1" Polarization="1" SystemHidden="0"></Broadcast>
</Channel>
Example from a ChannelMap_110\ChannelList\channellib\DVBC.xml:
<Channel>
<Setup ChannelNumber="1" ChannelName="0x44 0x00 0x61 0x00 0x73 0x00 0x20 0x00 0x45 0x00 0x72 0x00 0x73 0x00 0x74 0x00 0x65 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00" ChannelLock="0" UserModifiedName="0" LogoID="0" UserModifiedLogo="0" LogoLock="0" UserHidden="0" FavoriteNumber="0" Scrambled="0"></Setup>
<Broadcast UniqueID="12" ChannelType="3" Onid="1" Tsid="1101" Sid="1" Frequency="306" Modulation="256" ServiceType="1" Bandwidth="8" SymbolRate="6901000" DecoderType="2" NetworkID="43264" StreamPriority="0" SystemHidden="0"></Broadcast>
</Channel>
DVB-T and DVB-C share the same number range, so they are treated as a unified logical list
*/
class XmlSerializer : SerializerBase
{
private readonly ChannelList analogChannels = new ChannelList(SignalSource.AnalogCT, "Analog C/T");
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");
private readonly ChannelList allSatChannels = new ChannelList(SignalSource.DvbS, "DVB-S all");
private readonly ChannelList favChannels = new ChannelList(SignalSource.All, "Favorites");
private readonly List<FileData> fileDataList = new List<FileData>();
private ChanLstBin chanLstBin;
private readonly StringBuilder logMessages = new StringBuilder();
#region ctor()
public XmlSerializer(string inputFile) : base(inputFile)
{
this.Features.ChannelNameEdit = ChannelNameEditMode.All;
this.Features.CanSkipChannels = false;
this.Features.CanLockChannels = true;
this.Features.CanHideChannels = true;
this.Features.DeleteMode = DeleteMode.Physically;
this.Features.CanSaveAs = false;
this.Features.AllowGapsInFavNumbers = false;
this.Features.CanEditFavListNames = true;
this.DataRoot.AddChannelList(this.analogChannels);
this.DataRoot.AddChannelList(this.dvbtChannels);
this.DataRoot.AddChannelList(this.dvbcChannels);
this.DataRoot.AddChannelList(this.satChannels);
this.DataRoot.AddChannelList(this.allSatChannels);
this.DataRoot.AddChannelList(this.favChannels);
this.dvbtChannels.VisibleColumnFieldNames.Add("Source");
this.dvbcChannels.VisibleColumnFieldNames.Add("Source");
foreach (var list in this.DataRoot.ChannelLists)
{
list.VisibleColumnFieldNames.Remove("PcrPid");
list.VisibleColumnFieldNames.Remove("VideoPid");
list.VisibleColumnFieldNames.Remove("AudioPid");
list.VisibleColumnFieldNames.Remove("Skip");
list.VisibleColumnFieldNames.Remove("ShortName");
list.VisibleColumnFieldNames.Remove("Provider");
}
this.analogChannels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.OriginalNetworkId));
this.analogChannels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.TransportStreamId));
this.analogChannels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.ServiceId));
this.analogChannels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.SymbolRate));
this.analogChannels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.ChannelOrTransponder));
this.analogChannels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.NetworkName));
this.analogChannels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.NetworkOperator));
this.favChannels.IsMixedSourceFavoritesList = true;
}
#endregion
#region Load()
public override void Load()
{
// read all files from a directory structure that looks like
// ./CM_TPM1013E_LA_CK.xml
// - or -
// ChannelMap_100/ChannelList/channellib/DVBC.xml
// ChannelMap_100/ChannelList/channellib/DVBT.xml
// ChannelMap_100/ChannelList/s2channellib/DVBS.xml
// ChannelMap_100/ChannelList/s2channellib/DVBSall.xml
// ChannelMap_100/ChannelList/chanLst.bin
// + optionally
// ChannelMap_100/ChannelList/channelFile.bin
// ChannelMap_100/ChannelList/Favorite.xml
// ChannelMap_100/ChannelList/satInfo.bin
var dataFiles = new[] { @"channellib\DVBC.xml", @"channellib\DVBT.xml", @"s2channellib\DVBS.xml", @"s2channellib\DVBSall.xml", @"Favorite.xml" };
// support for files in a ChannelMap_xxx directory structure
bool isChannelMapFolderStructure = false;
var dir = Path.GetDirectoryName(this.FileName);
var dirName = Path.GetFileName(dir).ToLower();
if (dirName == "channellib" || dirName == "s2channellib")
{
dir = Path.GetDirectoryName(dir);
isChannelMapFolderStructure = true;
}
var binFile = Path.Combine(dir, "chanLst.bin"); // the .bin file is used as a proxy for the whole directory structure
if (File.Exists(binFile))
{
this.FileName = binFile;
isChannelMapFolderStructure = true;
this.chanLstBin = new ChanLstBin();
this.chanLstBin.Load(this.FileName, msg => this.logMessages.AppendLine(msg));
}
else if (Path.GetExtension(this.FileName).ToLower() == ".bin")
{
// older Philips models export a visible file like Repair\CM_T911_LA_CK.BIN and an invisible (hidden+system) .xml file with the same name
var xmlPath = Path.Combine(dir, Path.GetFileNameWithoutExtension(this.FileName) + ".xml");
if (File.Exists(xmlPath))
{
try { File.SetAttributes(xmlPath, FileAttributes.Archive);}
catch { /**/ }
this.FileName = xmlPath;
}
}
if (isChannelMapFolderStructure)
{
foreach (var file in dataFiles)
{
var fullPath = Path.GetFullPath(Path.Combine(dir, file));
this.LoadFile(fullPath);
}
if (this.fileDataList.Count == 0)
throw new FileLoadException("No XML files found in folder structure");
}
else
{
// otherwise load the single file that was originally selected by the user
LoadFile(this.FileName);
}
}
#endregion
#region LoadFile()
private void LoadFile(string fileName)
{
if (!File.Exists(fileName))
return;
bool fail = false;
var fileData = new FileData();
try
{
fileData.path = fileName;
fileData.doc = new XmlDocument();
fileData.content = File.ReadAllBytes(fileName);
fileData.textContent = Encoding.UTF8.GetString(fileData.content);
fileData.newline = fileData.textContent.Contains("\r\n") ? "\r\n" : "\n";
var settings = new XmlReaderSettings
{
CheckCharacters = false,
IgnoreProcessingInstructions = true,
ValidationFlags = XmlSchemaValidationFlags.None,
DtdProcessing = DtdProcessing.Ignore
};
using (var reader = XmlReader.Create(new StringReader(fileData.textContent), settings))
{
fileData.doc.Load(reader);
}
}
catch
{
fail = true;
}
var root = fileData.doc.FirstChild;
if (root is XmlDeclaration)
root = root.NextSibling;
if (fail || root == null || (root.LocalName != "ChannelMap" && root.LocalName != "FavoriteListMAP"))
throw new FileLoadException("\"" + fileName + "\" is not a supported Philips XML file");
int rowId = 0;
ChannelList curList = null;
foreach (XmlNode child in root.ChildNodes)
{
switch (child.LocalName)
{
case "Channel":
if (rowId == 0)
curList = this.DetectFormatAndFeatures(fileData, child);
if (curList != null)
this.ReadChannel(fileData, curList, child, rowId++);
break;
case "FavoriteList":
this.ReadFavList(child);
break;
}
}
this.fileDataList.Add(fileData);
}
#endregion
#region DetectFormatAndFeatures()
private ChannelList DetectFormatAndFeatures(FileData file, XmlNode node)
{
var setupNode = node["Setup"] ?? throw new FileLoadException("Missing Setup XML element");
var bcastNode = node["Broadcast"] ?? throw new FileLoadException("Missing Broadcast XML element");
var fname = Path.GetFileNameWithoutExtension(file.path).ToLower();
var medium = bcastNode.GetAttribute("medium");
if (medium == "" && fname.Length >= 4 && fname.StartsWith("dvb"))
medium = fname;
bool hasEncrypt = false;
if (setupNode.HasAttribute("ChannelName"))
{
file.formatVersion = 1;
this.Features.SupportedFavorites = Favorites.A;
this.Features.SortedFavorites = true;
var dtype = bcastNode.GetAttribute("DecoderType");
if (dtype == "1")
medium = "dvbt";
else if (dtype == "2")
medium = "dvbc";
else if (dtype == "3")
medium = "dvbs";
hasEncrypt = setupNode.HasAttribute("Scrambled");
}
else if (setupNode.HasAttribute("name"))
{
file.formatVersion = 2;
this.Features.SupportedFavorites = 0;
this.Features.SortedFavorites = false;
foreach (var list in this.DataRoot.ChannelLists)
{
list.VisibleColumnFieldNames.Remove("Favorites");
list.VisibleColumnFieldNames.Remove("Lock");
list.VisibleColumnFieldNames.Remove("Hidden");
list.VisibleColumnFieldNames.Remove("ServiceType");
list.VisibleColumnFieldNames.Remove("ServiceTypeName");
list.VisibleColumnFieldNames.Remove("Encrypted");
}
}
else
throw new FileLoadException("Unknown data format");
ChannelList chList = null;
switch (medium)
{
case "analog":
chList = this.analogChannels;
break;
case "dvbc":
chList = this.dvbcChannels;
break;
case "dvbt":
chList = this.dvbtChannels;
break;
case "dvbs":
chList = this.satChannels;
break;
case "dvbsall":
chList = this.allSatChannels;
break;
}
if (!hasEncrypt)
chList?.VisibleColumnFieldNames.Remove("Encrypted");
return chList;
}
#endregion
#region ReadChannel()
private void ReadChannel(FileData file, ChannelList curList, XmlNode node, int rowId)
{
var setupNode = node["Setup"] ?? throw new FileLoadException("Missing Setup XML element");
var bcastNode = node["Broadcast"] ?? throw new FileLoadException("Missing Broadcast XML element");
var data = new Dictionary<string,string>(StringComparer.InvariantCultureIgnoreCase);
foreach (var n in new[] {setupNode, bcastNode})
{
foreach(XmlAttribute attr in n.Attributes)
data.Add(attr.LocalName, attr.Value);
}
if (!data.ContainsKey("UniqueID") || !int.TryParse(data["UniqueID"], out var uniqueId)) // UniqueId only exists in ChannelMap_105 and later
uniqueId = rowId;
var chan = new Channel(curList.SignalSource & SignalSource.MaskAdInput, rowId, uniqueId, setupNode);
chan.OldProgramNr = -1;
chan.IsDeleted = false;
if (file.formatVersion == 1)
this.ParseChannelFormat1(data, chan);
else if (file.formatVersion == 2)
this.ParseChannelFormat2(data, chan);
if ((chan.SignalSource & SignalSource.MaskAdInput) == SignalSource.DvbT)
chan.ChannelOrTransponder = LookupData.Instance.GetDvbtTransponder(chan.FreqInMhz).ToString();
else if ((chan.SignalSource & SignalSource.MaskAdInput) == SignalSource.DvbC)
chan.ChannelOrTransponder = LookupData.Instance.GetDvbcChannelName(chan.FreqInMhz);
DataRoot.AddChannel(curList, chan);
}
#endregion
#region ParseChannelFormat1
private void ParseChannelFormat1(Dictionary<string,string> data, Channel chan)
{
chan.Format = 1;
chan.RawSatellite = data.TryGet("SatelliteName");
chan.Satellite = DecodeName(chan.RawSatellite);
chan.OldProgramNr = ParseInt(data.TryGet("ChannelNumber"));
chan.RawName = data.TryGet("ChannelName");
chan.Name = DecodeName(chan.RawName);
chan.Lock = data.TryGet("ChannelLock") == "1";
chan.Hidden = data.TryGet("UserHidden") == "1";
var fav = ParseInt(data.TryGet("FavoriteNumber"));
chan.OldFavIndex[0] = fav == 0 ? -1 : fav;
chan.OriginalNetworkId = ParseInt(data.TryGet("Onid"));
chan.TransportStreamId = ParseInt(data.TryGet("Tsid"));
chan.ServiceId = ParseInt(data.TryGet("Sid"));
chan.FreqInMhz = ParseInt(data.TryGet("Frequency")); ;
if (chan.FreqInMhz > 2000)
chan.FreqInMhz /= 1000;
if (chan.FreqInMhz > 2000)
chan.FreqInMhz /= 1000;
chan.ServiceType = ParseInt(data.TryGet("ServiceType"));
var decoderType = data.TryGet("DecoderType");
if (decoderType == "1")
chan.Source = "DVB-T";
else if (decoderType == "2")
chan.Source = "DVB-C";
chan.SignalSource |= LookupData.Instance.IsRadioTvOrData(chan.ServiceType);
chan.SymbolRate = ParseInt(data.TryGet("SymbolRate"));
if (data.TryGetValue("Polarization", out var pol))
chan.Polarity = pol == "0" ? 'H' : 'V';
chan.Hidden |= data.TryGet("SystemHidden") == "1";
chan.Encrypted = data.TryGet("Scrambled") == "1"; // doesn't exist in all format versions
}
#endregion
#region ParseChannelFormat2
private void ParseChannelFormat2(Dictionary<string, string> data, Channel chan)
{
chan.Format = 2;
chan.OldProgramNr = ParseInt(data.TryGet("presetnumber"));
chan.Name = data.TryGet("name");
chan.RawName = chan.Name;
chan.FreqInMhz = ParseInt(data.TryGet("frequency"));
//if ((chan.SignalSource & SignalSource.Analog) != 0)
// chan.FreqInMhz /= 16;
if (chan.FreqInMhz > 1200)
chan.FreqInMhz /= 1000;
chan.ServiceId = ParseInt(data.TryGet("serviceID"));
chan.OriginalNetworkId = ParseInt(data.TryGet("ONID"));
chan.TransportStreamId = ParseInt(data.TryGet("TSID"));
chan.ServiceType = ParseInt(data.TryGet("serviceType"));
chan.SymbolRate = ParseInt(data.TryGet("symbolrate")) / 1000;
}
#endregion
#region ReadFavList
private void ReadFavList(XmlNode node)
{
int index = ParseInt(node.Attributes["Index"].InnerText);
string name = DecodeName(node.Attributes["Name"].InnerText);
this.Features.SupportedFavorites |= (Favorites) (1 << (index - 1));
this.Features.SortedFavorites = true;
this.Features.MixedSourceFavorites = true;
this.DataRoot.SetFavListCaption(index - 1, name);
if (this.favChannels.Count == 0)
{
foreach (var rootList in this.DataRoot.ChannelLists)
{
if (rootList.IsMixedSourceFavoritesList)
continue;
foreach (var chan in rootList.Channels)
{
favChannels.Channels.Add(chan);
for (int i=0; i<chan.FavIndex.Count; i++)
chan.SetOldPosition(i+1, -1);
}
}
}
foreach (XmlNode child in node.ChildNodes)
{
if (child.LocalName == "FavoriteChannel")
{
var uniqueId = ParseInt(child["UniqueID"].InnerText);
var favNumber = ParseInt(child["FavNumber"].InnerText);
var chan = this.favChannels.Channels.FirstOrDefault(ch => ch.RecordIndex == uniqueId);
chan?.SetOldPosition(index, favNumber + 1);
}
}
}
#endregion
#region DecodeName()
private string DecodeName(string input)
{
if (input == null || !input.StartsWith("0x")) // fallback for unknown input
return input;
var hexParts = input.Split(' ');
var buffer = new MemoryStream();
foreach (var part in hexParts)
{
if (part == "" || part == "0x00")
continue;
buffer.WriteByte((byte)ParseInt(part));
}
return this.DefaultEncoding.GetString(buffer.GetBuffer(), 0, (int)buffer.Length);
}
#endregion
#region DefaultEncoding
public override Encoding DefaultEncoding
{
get => base.DefaultEncoding;
set
{
if (value == this.DefaultEncoding)
return;
base.DefaultEncoding = value;
this.ChangeEncoding();
}
}
#endregion
#region ChangeEncoding
private void ChangeEncoding()
{
foreach (var list in this.DataRoot.ChannelLists)
{
foreach (var channel in list.Channels)
{
if (!(channel is Channel ch))
continue;
ch.Name = this.DecodeName(ch.RawName);
ch.Satellite = this.DecodeName(ch.RawSatellite);
}
}
}
#endregion
#region GetDataFilePaths()
public override IEnumerable<string> GetDataFilePaths()
{
return this.fileDataList.Select(f => f.path);
}
#endregion
#region Save()
public override void Save(string tvOutputFile)
{
// "Save As..." is not supported by this loader
foreach (var list in this.DataRoot.ChannelLists)
{
if (list.IsMixedSourceFavoritesList)
this.UpdateFavList();
else
this.UpdateChannelList(list);
}
foreach (var file in this.fileDataList)
this.SaveFile(file);
this.chanLstBin?.Save(this.FileName);
}
#endregion
#region SaveFile()
private void SaveFile(FileData file)
{
// by default .NET reformats the whole XML. These settings produce almost same format as the TV xml files use
var xmlSettings = new XmlWriterSettings();
xmlSettings.Encoding = this.DefaultEncoding;
xmlSettings.CheckCharacters = false;
xmlSettings.Indent = true;
xmlSettings.IndentChars = " ";
xmlSettings.NewLineHandling = NewLineHandling.None;
xmlSettings.NewLineChars = file.newline;
xmlSettings.OmitXmlDeclaration = false;
string xml;
using (var sw = new StringWriter())
using (var w = new CustomXmlWriter(sw, xmlSettings, false))
{
file.doc.WriteTo(w);
w.Flush();
xml = sw.ToString();
}
var enc = new UTF8Encoding(false, false);
File.WriteAllText(file.path, xml, enc);
}
#endregion
#region UpdateChannelList()
private void UpdateChannelList(ChannelList list)
{
foreach (var channel in list.Channels)
{
var ch = channel as Channel;
if (ch == null)
continue; // might be a proxy channel from a reference list
if (ch.IsDeleted || ch.NewProgramNr < 0)
{
ch.SetupNode.ParentNode.ParentNode.RemoveChild(ch.SetupNode.ParentNode);
continue;
}
if (ch.Format == 1)
this.UpdateChannelFormat1(ch);
else if (ch.Format == 2)
this.UpdateChannelFormat2(ch);
}
}
#endregion
#region UpdateChannelFormat1 and 2
private void UpdateChannelFormat1(Channel ch)
{
ch.SetupNode.Attributes["ChannelNumber"].Value = ch.NewProgramNr.ToString();
if (ch.IsNameModified)
ch.SetupNode.Attributes["ChannelName"].Value = EncodeName(ch.Name);
ch.SetupNode.Attributes["FavoriteNumber"].Value = Math.Max(ch.FavIndex[0], 0).ToString();
}
private void UpdateChannelFormat2(Channel ch)
{
ch.SetupNode.Attributes["presetnumber"].Value = ch.NewProgramNr.ToString();
if (ch.IsNameModified)
ch.SetupNode.Attributes["name"].Value = ch.Name;
}
#endregion
#region UpdateFavList
private void UpdateFavList()
{
var favFile = this.fileDataList.FirstOrDefault(fd => Path.GetFileName(fd.path).ToLower() == "favorite.xml");
if (favFile == null)
return;
int index = 0;
foreach(XmlNode favListNode in favFile.doc["FavoriteListMAP"].ChildNodes)
{
++index;
favListNode.InnerXml = ""; // clear all <FavoriteChannel> child elements but keep the attributes of the current node
var attr = favListNode.Attributes?["Name"];
if (attr != null)
attr.InnerText = EncodeName(this.DataRoot.GetFavListCaption(index - 1));
foreach (var ch in favChannels.Channels.OrderBy(ch => ch.GetPosition(index)))
{
var nr = ch.GetPosition(index);
if (nr <= 0)
continue;
var uniqueIdNode = favFile.doc.CreateElement("UniqueID");
uniqueIdNode.InnerText = ch.RecordIndex.ToString();
var favNrNode = favFile.doc.CreateElement("FavNumber");
favNrNode.InnerText = (nr-1).ToString();
var channelNode = favFile.doc.CreateElement("FavoriteChannel");
channelNode.AppendChild(uniqueIdNode);
channelNode.AppendChild(favNrNode);
favListNode.AppendChild(channelNode);
}
}
}
#endregion
#region EncodeName
private string EncodeName(string name)
{
var bytes = this.DefaultEncoding.GetBytes(name);
var sb = new StringBuilder();
foreach (var b in bytes)
sb.Append($"0x{b:X2} 0x00 ");
sb.Remove(sb.Length - 1, 1);
return sb.ToString();
}
#endregion
public override string GetFileInformation()
{
return base.GetFileInformation() + this.logMessages.Replace("\n", "\r\n");
}
#region class FileData
private class FileData
{
public string path;
public XmlDocument doc;
public byte[] content;
public string textContent;
public string newline;
public int formatVersion;
}
#endregion
}
}