Skip to content

Commit

Permalink
- WIP support for Philips channel list format version 120
Browse files Browse the repository at this point in the history
  • Loading branch information
PredatH0r committed Nov 3, 2023
1 parent 6134eb3 commit 7f8e73f
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 29 deletions.
2 changes: 1 addition & 1 deletion source/ChanSort.Api/Controller/SerializerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ protected int ParseInt(string input)
}
#endregion

#region ParseInt()
#region ParseLong()
protected long ParseLong(string input)
{
if (string.IsNullOrWhiteSpace(input))
Expand Down
80 changes: 73 additions & 7 deletions source/ChanSort.Loader.Philips/ChanLstBin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,17 @@ public void Load(string path, Action<string> log)
off += 4;

if (modelNameLen >= 1)
this.ModelName = Encoding.ASCII.GetString(content, off, modelNameLen-1);
this.ModelName = Encoding.ASCII.GetString(content, off, modelNameLen).TrimEnd('\0');
off += modelNameLen;

log?.Invoke($"Philips TV model: {this.ModelName}\nFile format version: {VersionMajor}.{VersionMinor}\n\n");

if (VersionMajor >= 120)
{
LoadVersion120OrLater(path);
return;
}

var baseDir = Path.GetDirectoryName(path) ?? "";
var relPath = "/channellib/";
while (off < content.Length)
Expand All @@ -86,19 +92,76 @@ public void Load(string path, Action<string> log)
}
}

this.Validate(path);
this.Validate(path, false);
}

private class V120Info
{
public readonly int Offset;
public readonly string Filename;
public readonly int BytesBeforeChecksum;
public readonly string SubDir;

public V120Info(int offset, string filename, int bytesBeforeChecksum, string subDir)
{
this.Offset = offset;
this.Filename = filename;
this.BytesBeforeChecksum = bytesBeforeChecksum;
this.SubDir = subDir;
}
}

private void LoadVersion120OrLater(string path)
{
// starting with version 120, the chanLst.bin
// - no longer includes a 0x00 terminating character as part of the file name
// - has a random number of 0x00 bytes following the file name (0-3) (2 after DVBT, 3 after DVBC, 0 after DVBSall, 1 after Favorite)
// - only stores 1 byte for the length of "/s2channellib/" instead of 4
// - only stores the lower 8 bits of the CRC16-Modbus
// This format required a tailor-made implementation with the exact byte layout

if (content.Length != 118)
throw LoaderException.Fail($"chanLst.bin has an unsupported size");

var entries = new List<V120Info>
{
new V120Info(0x29, "DVBT.xml", 2, "/channellib/"),
new V120Info(0x38, "DVBC.xml", 3, "/channellib/"),
new V120Info(0x58, "DVBSall.xml", 0, "/s2channellib/"),
new V120Info(0x68, "Favorite.xml", 1, ""),
};


foreach(var entry in entries )
{
var off = entry.Offset;
var fileName = Encoding.ASCII.GetString(content, off, entry.Filename.Length);
if (fileName != entry.Filename)
throw LoaderException.Fail("Entry in chanLst.bin doesn't match expected data");

var newPath = entry.SubDir + fileName;
crcOffsetByRelPath[newPath] = off + entry.Filename.Length + entry.BytesBeforeChecksum;
}

this.Validate(path, true);
}

private void Validate(string chanLstBinPath)
private void Validate(string chanLstBinPath, bool onlyLower8Bits)
{
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;
int expectedCrc;
if (onlyLower8Bits)
expectedCrc = this.content[crcOffset];
else
{
expectedCrc = BitConverter.ToUInt16(this.content, crcOffset);
if (expectedCrc == 0)
continue;
}

var filePath = baseDir + entry.Key;
if (!File.Exists(filePath))
Expand All @@ -115,6 +178,8 @@ private void Validate(string chanLstBinPath)
// length = 0x0140000;

var actualCrc = Crc16.Modbus(data, 0, length);
if (onlyLower8Bits)
actualCrc &= 0x00FF;
if (actualCrc != expectedCrc)
{
var msg = $"chanLst.bin: stored CRC for {entry.Key} is {expectedCrc:X4} but calculated {actualCrc:X4}";
Expand Down Expand Up @@ -163,7 +228,8 @@ public void Save(string chanLstBinPath)
var crc = Crc16.Modbus(data, 0, length);
var off = entry.Value;
content[off] = (byte) crc;
content[off + 1] = (byte) (crc >> 8);
if (VersionMajor < 120)
content[off + 1] = (byte) (crc >> 8);
}
File.WriteAllBytes(chanLstBinPath, content);
}
Expand Down
18 changes: 17 additions & 1 deletion source/ChanSort.Loader.Philips/ChanSort.Loader.Philips.ini
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ incrementFavListVersion=true
allowDelete=false

############################################################################
# ChannelMap_110: same as 110
# ChannelMap_115: same as 110

[Map115]
padChannelName=true
Expand All @@ -437,3 +437,19 @@ userReorderChannel=0
reorderRecordsByChannelNumber=true
incrementFavListVersion=true
allowDelete=false

############################################################################
# ChannelMap_120: mostly same as 115
# - Umlauts are encoded with high byte 0xFF instead of 0x00
# - Scrambled is always 0 without any real indication for encryption
# - UserHidden has value "3" instead of "0" for most channels, "1" still exists for some

[Map120]
padChannelName=true
setFavoriteNumber=false
setReorderedFavNumber=false
userReorderChannel=0
reorderRecordsByChannelNumber=true
incrementFavListVersion=false
allowDelete=false
userHiddenDefaultValue=3
2 changes: 1 addition & 1 deletion source/ChanSort.Loader.Philips/Channel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public Channel(SignalSource source, long index, int oldProgNr, string name) : ba
this.RecordOrder = (int)index;
}

internal Channel(SignalSource source, int order, int rowId, XmlNode setupNode)
internal Channel(SignalSource source, int order, long rowId, XmlNode setupNode)
{
this.SignalSource = source;
this.RecordOrder = order;
Expand Down
10 changes: 7 additions & 3 deletions source/ChanSort.Loader.Philips/PhilipsPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,13 @@ public class PhilipsPlugin : ISerializerPlugin
* e.g. 65PUS8535/12, 55PUS7334/12
*
* version 115.0
* same as 110.0
* same as 105.0
*
* version 120.0
* same as 105 plus additional ChannelList\MtkChannelList.xml
*
*
* Version 0.1 and 100-115 are XML based and loaded through the XmlSerializer.
* Version 0.1 and 100-120 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.
*/
Expand Down Expand Up @@ -131,7 +135,7 @@ public SerializerBase CreateSerializer(string inputFile)
}
}

if (majorVersion == 0 || majorVersion >= 100 && majorVersion <= 115)
if (majorVersion == 0 || majorVersion >= 100 && majorVersion <= 120)
return new XmlSerializer(inputFile);
if (majorVersion == 1 || majorVersion == 2 || majorVersion == 30 || majorVersion == 45) // || majorVersion == 11 // format version 11 is similar to 1.x, but not (yet) supported
return new BinarySerializer(inputFile);
Expand Down
75 changes: 59 additions & 16 deletions source/ChanSort.Loader.Philips/XmlSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ private void ReadChannel(FileData file, ChannelList curList, XmlNode node, int r
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
if (!data.ContainsKey("UniqueID") || !long.TryParse(data["UniqueID"], out var uniqueId)) // UniqueId only exists in ChannelMap_105 and later
uniqueId = rowId;
var chan = new Channel(curList.SignalSource & SignalSource.MaskBcast, rowId, uniqueId, setupNode);
chan.OldProgramNr = -1;
Expand Down Expand Up @@ -478,7 +478,7 @@ private void ParseChannelMapXml(Dictionary<string,string> data, Channel chan)
chan.RawName = data.TryGet("ChannelName");
chan.Name = DecodeName(chan.RawName);
chan.Lock = data.TryGet("ChannelLock") == "1";
chan.Hidden = data.TryGet("UserHidden") == "1";
chan.Hidden = data.TryGet("UserHidden") == "1"; // can be "3" instead of "0", at least in format 120
var fav = ParseInt(data.TryGet("FavoriteNumber"));
chan.SetOldPosition(1, fav == 0 ? -1 : fav);
chan.OriginalNetworkId = ParseInt(data.TryGet("Onid"));
Expand All @@ -487,13 +487,23 @@ private void ParseChannelMapXml(Dictionary<string,string> data, Channel chan)
chan.FreqInMhz = ParseInt(data.TryGet("Frequency")); ;
if (chan.FreqInMhz > 2000 && (chan.SignalSource & SignalSource.Sat) == 0)
chan.FreqInMhz /= 1000;


// version 120 stores actual DVB ServiceType values, earlier versions only 1=TV, 2=Radio
var st = ParseInt(data.TryGet("ServiceType"));
chan.ServiceTypeName = st == 1 ? "TV" : "Radio";
if (st == 1)
chan.SignalSource |= SignalSource.Tv;
if (chanLstBin != null && chanLstBin.VersionMajor >= 120)
{
chan.ServiceType = st;
chan.ServiceTypeName = LookupData.Instance.GetServiceTypeDescription(st);
chan.SignalSource |= LookupData.Instance.IsRadioTvOrData(st);
}
else
chan.SignalSource |= SignalSource.Radio;
{
chan.ServiceTypeName = st == 1 ? "TV" : "Radio";
if (st == 1)
chan.SignalSource |= SignalSource.Tv;
else
chan.SignalSource |= SignalSource.Radio;
}

chan.Source = (chan.SignalSource & SignalSource.Sat) != 0 ? "DVB-S" : (chan.SignalSource & SignalSource.Cable) != 0 ? "DVB-C" : (chan.SignalSource & SignalSource.Antenna) != 0 ? "DVB-T" : "";
chan.SignalSource |= LookupData.Instance.IsRadioTvOrData(chan.ServiceType);
Expand Down Expand Up @@ -528,19 +538,21 @@ private void ReadFavList(XmlNode node)
{
favChannels.Channels.Add(chan);
for (int i=0; i<this.DataRoot.FavListCount; i++)
chan.SetOldPosition(i+1, -1);
chan.SetOldPosition(i + 1, -1);
}
}
}

var startNrBias = (chanLstBin?.VersionMajor ?? 0) >= 120 ? 0 : +1;

foreach (XmlNode child in node.ChildNodes)
{
if (child.LocalName == "FavoriteChannel")
{
var uniqueId = ParseInt(child["UniqueID"].InnerText);
var uniqueId = ParseLong(child["UniqueID"].InnerText);
var favNumber = ParseInt(child["FavNumber"].InnerText);
var chan = this.favChannels.Channels.FirstOrDefault(ch => ch.RecordIndex == uniqueId);
chan?.SetOldPosition(index, favNumber + 1);
var chan = this.favChannels.Channels.FirstOrDefault(ch => ch.RecordIndex == uniqueId && ch.GetOldPosition(index) <= 0);
chan?.SetOldPosition(index, favNumber + startNrBias);
}
}
}
Expand All @@ -555,15 +567,21 @@ private string DecodeName(string input)
// according to https://github.com/PredatH0r/ChanSort/issues/347 Philips seems to not use UTF 16, but instead use locale dependent encoding and
// writing "0xAA 0x00" to the file for an 8 bit code point. At least for the favorite list captions. Congratulations, well done!

// In version 120 umlauts in channel names are encoded as 1 byte CP-1252 (= low byte UTF16) code point + 0xFF as the second byte

var hexParts = input.Split(' ');
var buffer = new MemoryStream();

bool highByte = false;
foreach (var part in hexParts)
{
if (part == "")
continue;
var val = (byte)ParseInt(part);
if (highByte && val == 0xff) // hack-around for version 120
val = 0;
buffer.WriteByte(val);
highByte = !highByte;
}

return Encoding.Unicode.GetString(buffer.GetBuffer(), 0, (int) buffer.Length).TrimEnd('\x0');
Expand All @@ -573,7 +591,11 @@ private string DecodeName(string input)
#region GetDataFilePaths()
public override IEnumerable<string> GetDataFilePaths()
{
return this.fileDataList.Select(f => f.path);
var list = new List<string>();
if (this.chanLstBin != null)
list.Add(this.FileName);
list.AddRange(this.fileDataList.Select(f => f.path));
return list;
}
#endregion

Expand All @@ -596,7 +618,7 @@ public override void Save()
// The official Philips Editor 6.61.22 does not reorder the XML nodes and does not change dtv_cmdb_*.bin when editing a ChannelMap_100 folder. But it is unclear if this editor is designed to handle the cmdb flavor.

// A user with a ChannelMap_100 export including a dtv_cmdb_2.bin reported, that the TV shows the reordered list in the menu, but tunes the channels based on the original numbers.
// It's unclear if that happenes because the XML was reordered and out-of-sync with the .bin, or if the TV always uses the .bin for tuning and XML edits are moot.
// It's unclear if that happens because the XML was reordered and out-of-sync with the .bin, or if the TV always uses the .bin for tuning and XML edits are moot.
// On top of that this TV messed up Umlauts during the import, despite ChanSort writing the exact same name data in hex-encoded UTF16. The result was as if the string was exported as UTF-8 bytes and then parsed with an 8-bit code page.
var reorderNodes = this.iniMapSection?.GetBool("reorderRecordsByChannelNumber") ?? false;

Expand Down Expand Up @@ -666,7 +688,7 @@ private void UpdateChannelMapXml(Channel ch, bool paddedName, bool setFavoriteNu
}

setup["ChannelLock"].Value = ch.Lock ? "1" : "0";
setup["UserHidden"].Value = ch.Hidden ? "1" : "0";
setup["UserHidden"].Value = ch.Hidden ? "1" : iniMapSection.GetInt("userHiddenDefaultValue", 0).ToString();

// ChannelMap_100 supports a single fav list and stores the favorite number directly in the channel.
// The official Philips editor allows to reorder favorites when switched to the "Favourite" list view
Expand Down Expand Up @@ -726,18 +748,39 @@ private void UpdateFavList()
attr.InnerText = (version + 1).ToString();
}

var startNrBias = (chanLstBin?.VersionMajor ?? 0) >= 120 ? 0 : -1;
var uniqueIdFormat = (chanLstBin?.VersionMajor ?? 0) >= 120 ? "d10" : "d"; // v120 writes 10 digits as uniqueID, including leading zeros

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();
uniqueIdNode.InnerText = ch.RecordIndex.ToString(uniqueIdFormat);
var favNrNode = favFile.doc.CreateElement("FavNumber");
favNrNode.InnerText = (nr-1).ToString();
favNrNode.InnerText = (nr + startNrBias).ToString();
var channelNode = favFile.doc.CreateElement("FavoriteChannel");
channelNode.AppendChild(uniqueIdNode);
channelNode.AppendChild(favNrNode);

// Version 120 also stores a <ChannelType> element in the XML
if ((chanLstBin?.VersionMajor ?? 0) >= 120)
{
var chanTypeNode = favFile.doc.CreateElement("ChannelType");
string type = null;

if ((ch.SignalSource & SignalSource.Sat) != 0) type = "TYPE_DVB_S"; // observed
else if ((ch.SignalSource & SignalSource.Cable) != 0) type = "TYPE_DVB_C"; // fairly sure
else if ((ch.SignalSource & SignalSource.Antenna) != 0) type = "TYPE_DVB_T"; // could also be T2 maybe

if (type != null)
{
chanTypeNode.InnerText = type;
channelNode.AppendChild(chanTypeNode);
}
}

favListNode.AppendChild(channelNode);
}
}
Expand Down

0 comments on commit 7f8e73f

Please sign in to comment.