diff --git a/.gitignore b/.gitignore
index 2f64768..1cca489 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
source/ChanSort.opensdf
/source/.vs/
/source/packages/
+/source/ChanSort.Loader.PhilipsBin/DllClient.cs
diff --git a/source/ChanSort.Api/Model/DataRoot.cs b/source/ChanSort.Api/Model/DataRoot.cs
index b6f510e..cd72a32 100644
--- a/source/ChanSort.Api/Model/DataRoot.cs
+++ b/source/ChanSort.Api/Model/DataRoot.cs
@@ -49,8 +49,8 @@ namespace ChanSort.Api
this.Warnings.AppendFormat("Duplicate transponder data record for satellite #{0} with id {1}\r\n", sat?.Id, trans.Id);
return;
}
- if (sat != null)
- sat.Transponder.Add(trans.Id, trans);
+
+ sat?.Transponder.Add(trans.Id, trans);
this.Transponder.Add(trans.Id, trans);
}
#endregion
diff --git a/source/ChanSort.Api/Utils/Crc32.cs b/source/ChanSort.Api/Utils/Crc32.cs
index 0edac29..a41d40c 100644
--- a/source/ChanSort.Api/Utils/Crc32.cs
+++ b/source/ChanSort.Api/Utils/Crc32.cs
@@ -6,11 +6,12 @@
// To get the same CRC32 values that an LSB-first implementation would produce,
// all bits in the input bytes, the polynomial (=> 0xEDB88320) and the resulting crc need to be reversed (msb to lsb)
+ public const uint NormalPoly = 0x04C11DB7;
+ public const uint ReversedPoly = 0xEDB88320;
private const uint CrcMask = 0xFFFFFFFF;
- private const uint CrcPoly = 0x04C11DB7;
- public static Crc32 Normal = new Crc32(true);
- public static Crc32 Reversed = new Crc32(false);
+ public static Crc32 Normal = new Crc32(true, NormalPoly);
+ public static Crc32 Reversed = new Crc32(false, NormalPoly);
private static readonly byte[] BitReversedBytes = new byte[256];
private readonly uint[] crc32Table;
@@ -20,7 +21,6 @@
static Crc32()
{
- InitCrc32Table();
InitReversedBitOrderTable();
}
@@ -42,11 +42,20 @@
}
}
+ #endregion
- private static uint[] InitCrc32Table()
+ /// true for using the "left shift" most-significant-bit-first algorithm
+ ///
+ public Crc32(bool msbFirst, uint poly)
+ {
+ this.msbFirst = msbFirst;
+ this.crc32Table = InitCrc32Table(poly);
+ }
+
+ #region InitCrc32Table()
+ private uint[] InitCrc32Table(uint poly)
{
var crcTable = new uint[256];
- var poly = CrcPoly;
for (uint i = 0; i < 256; i++)
{
uint r = i << 24;
@@ -65,13 +74,6 @@
}
#endregion
- /// true for using the "left shift" MSB-first algorithm with polynomial 0x04C11Db7. false to use "right shift" with polynomial 0xEDB883320
- public Crc32(bool msbFirst = true)
- {
- this.msbFirst = msbFirst;
- crc32Table = InitCrc32Table();
- }
-
#region CalcCrc32()
public uint CalcCrc32(byte[] data, int start, int length)
{
diff --git a/source/ChanSort.Api/Utils/DataMapping.cs b/source/ChanSort.Api/Utils/DataMapping.cs
index 74f8624..7432ca6 100644
--- a/source/ChanSort.Api/Utils/DataMapping.cs
+++ b/source/ChanSort.Api/Utils/DataMapping.cs
@@ -37,6 +37,30 @@ namespace ChanSort.Api
}
#endregion
+ #region GetMask()
+ public int GetMask(string key)
+ {
+ var list = settings.GetIntList(key);
+ if (list != null && list.Length > 0)
+ return list[0];
+ list = settings.GetIntList("mask" + key);
+ if (list != null && list.Length > 0)
+ return list[0];
+ return -1;
+ }
+ #endregion
+
+ #region GetConst()
+ public int GetConst(string key, int defaultValue)
+ {
+ var list = settings.GetIntList(key);
+ if (list != null && list.Length > 0)
+ return list[0];
+ return defaultValue;
+ }
+ #endregion
+
+
public IniFile.Section Settings { get { return this.settings; } }
diff --git a/source/ChanSort.Api/Utils/Tools.cs b/source/ChanSort.Api/Utils/Tools.cs
index 14ec834..4c85962 100644
--- a/source/ChanSort.Api/Utils/Tools.cs
+++ b/source/ChanSort.Api/Utils/Tools.cs
@@ -31,12 +31,12 @@ namespace ChanSort.Api
#region GetInt16/32()
- public static int GetInt16(byte[] data, int offset, bool littleEndian)
+ public static int GetInt16(this byte[] data, int offset, bool littleEndian = true)
{
return littleEndian ? BitConverter.ToInt16(data, offset) : (data[offset] << 8) + data[offset + 1];
}
- public static int GetInt32(byte[] data, int offset, bool littleEndian)
+ public static int GetInt32(this byte[] data, int offset, bool littleEndian = true)
{
return littleEndian ? BitConverter.ToInt32(data, offset) :
(data[offset] << 24) + (data[offset + 1] << 16) + (data[offset + 2] << 8) + data[offset + 3];
@@ -45,7 +45,7 @@ namespace ChanSort.Api
#region SetInt16/32()
- public static void SetInt16(byte[] data, int offset, int value, bool littleEndian = true)
+ public static void SetInt16(this byte[] data, int offset, int value, bool littleEndian = true)
{
if (littleEndian)
{
@@ -59,7 +59,7 @@ namespace ChanSort.Api
}
}
- public static void SetInt32(byte[] data, int offset, int value, bool littleEndian = true)
+ public static void SetInt32(this byte[] data, int offset, int value, bool littleEndian = true)
{
if (littleEndian)
{
@@ -82,11 +82,10 @@ namespace ChanSort.Api
public static void MemCopy(byte[] source, int sourceIndex, byte[] dest, int destIndex, int count)
{
- for (int i = 0; i < count; i++)
- dest[destIndex + i] = source[sourceIndex + i];
+ Array.Copy(source, sourceIndex, dest, destIndex, count);
}
- public static void MemSet(byte[] data, int offset, byte value, int count)
+ public static void MemSet(this byte[] data, int offset, byte value, int count)
{
for (int i = 0; i < count; i++)
data[offset++] = value;
diff --git a/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.csproj b/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.csproj
new file mode 100644
index 0000000..cc11a55
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.csproj
@@ -0,0 +1,83 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}
+ Library
+ Properties
+ ChanSort.Loader.PhilipsBin
+ ChanSort.Loader.PhilipsBin
+ v4.8
+ 512
+ true
+
+
+ true
+ full
+ false
+ ..\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ latest
+ x86
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ latest
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ latest
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+ bin\x86\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ latest
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {dccffa08-472b-4d17-bb90-8f513fc01392}
+ ChanSort.Api
+
+
+
+
+ PreserveNewest
+
+
+
+
\ No newline at end of file
diff --git a/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.ini b/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.ini
new file mode 100644
index 0000000..4a7a94d
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.ini
@@ -0,0 +1,19 @@
+[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
+
diff --git a/source/ChanSort.Loader.PhilipsBin/Properties/AssemblyInfo.cs b/source/ChanSort.Loader.PhilipsBin/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..6de427c
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsBin/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 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")]
diff --git a/source/ChanSort.Loader.PhilipsBin/Serializer.cs b/source/ChanSort.Loader.PhilipsBin/Serializer.cs
new file mode 100644
index 0000000..0b85b63
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsBin/Serializer.cs
@@ -0,0 +1,505 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Xml;
+using System.Xml.Schema;
+using ChanSort.Api;
+
+namespace ChanSort.Loader.PhilipsBin
+{
+
+ class Serializer : SerializerBase
+ {
+ private readonly ChannelList dvbtChannels = new ChannelList(SignalSource.DvbCT, "DVB-T");
+ private readonly ChannelList dvbcChannels = new ChannelList(SignalSource.DvbCT, "DVB-C");
+ private readonly ChannelList satChannels = new ChannelList(SignalSource.DvbS, "DVB-S");
+ private readonly ChannelList favChannels = new ChannelList(SignalSource.All, "Favorites");
+
+
+ private readonly IniFile ini;
+ private byte[] dvbcData, dvbtData, dvbsData;
+
+ private readonly List dataFilePaths = new List();
+
+ #region ctor()
+ public Serializer(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.SupportedFavorites = Favorites.A;
+ this.Features.SortedFavorites = true;
+ this.Features.AllowGapsInFavNumbers = false;
+ this.Features.CanEditFavListNames = false;
+
+ this.DataRoot.AddChannelList(this.dvbtChannels);
+ this.DataRoot.AddChannelList(this.dvbcChannels);
+ this.DataRoot.AddChannelList(this.satChannels);
+ //this.DataRoot.AddChannelList(this.favChannels);
+
+ foreach (var list in this.DataRoot.ChannelLists)
+ {
+ list.VisibleColumnFieldNames.Remove("Skip");
+ list.VisibleColumnFieldNames.Remove("ShortName");
+ }
+
+ var supportedColumns = new[] {"OldPosition", "Position", "Name", "Lock"};
+ this.satChannels.VisibleColumnFieldNames.Remove("AudioPid");
+ this.satChannels.VisibleColumnFieldNames.Remove("ServiceTypeName");
+ this.satChannels.VisibleColumnFieldNames.Remove("Encrypted");
+ this.satChannels.VisibleColumnFieldNames.Remove("Hidden");
+ //this.satChannels.VisibleColumnFieldNames.Clear();
+ //foreach(var supportedColumn in supportedColumns)
+ // this.satChannels.VisibleColumnFieldNames.Add(supportedColumn);
+
+ this.favChannels.IsMixedSourceFavoritesList = true;
+ this.ini = new IniFile("ChanSort.Loader.PhilipsBin.ini");
+ }
+ #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");
+
+ var dir = Path.GetDirectoryName(this.FileName);
+ dvbtData = LoadDvbCT(dvbtChannels, Path.Combine(dir, "channellib", "AntennaDigSrvTable"));
+ dvbcData = LoadDvbCT(dvbcChannels, Path.Combine(dir, "channellib", "CableDigSrvTable"));
+
+ LoadDvbsSatellites(Path.Combine(dir, "s2channellib", "satellite.dat"));
+ LoadDvbsTransponders(Path.Combine(dir, "s2channellib", "tuneinfo.dat"));
+ dvbsData = LoadDvbS(satChannels, Path.Combine(dir, "s2channellib", "service.dat"));
+ LoadDvbsFavorites(Path.Combine(dir, "s2channellib", "favorite.dat"));
+ var db_file_info = Path.Combine(dir, "s2channellib", "db_file_info.dat");
+ if (File.Exists(db_file_info))
+ this.dataFilePaths.Add(db_file_info);
+ }
+ #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 byte[] LoadDvbCT(ChannelList list, string path)
+ {
+ if (!File.Exists(path))
+ return null;
+
+ var data = File.ReadAllBytes(path);
+ if (data.Length < 20)
+ return null;
+
+ var recordSize = BitConverter.ToInt32(data, 8);
+ var recordCount = BitConverter.ToInt32(data, 12);
+ if (data.Length != 20 + recordCount * recordSize)
+ throw new FileLoadException("Unsupported file content: " + path);
+
+ this.dataFilePaths.Add(path);
+
+ int baseOffset = 20;
+ for (int i = 0; i < recordCount; i++, baseOffset += recordSize)
+ {
+ uint checksum = BitConverter.ToUInt32(data, baseOffset + 0);
+ ushort progNr = BitConverter.ToUInt16(data, baseOffset + 122);
+ byte locked = data[baseOffset + 140];
+ int nameLen;
+ for (nameLen=0; nameLen<64; nameLen+=2)
+ if (data[baseOffset + 216 + nameLen] == 0)
+ break;
+ string channelName = Encoding.Unicode.GetString(data, baseOffset + 216, nameLen);
+
+ data[baseOffset + 0] = 0;
+ data[baseOffset + 1] = 0;
+ data[baseOffset + 2] = 0;
+ data[baseOffset + 3] = 0;
+ var crc = FaultyCrc32(data, baseOffset, recordSize);
+
+ if (crc != checksum)
+ throw new FileLoadException($"Invalid CRC in record {i} in {path}");
+
+ var ch = new ChannelInfo(list.SignalSource, i, progNr, channelName);
+ ch.Lock = locked != 0;
+ this.DataRoot.AddChannel(list, ch);
+ }
+
+ return data;
+ }
+ #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 byte[] LoadDvbS(ChannelList list, string path)
+ {
+ if (!File.Exists(path))
+ return null;
+
+ var data = File.ReadAllBytes(path);
+ if (data.Length < 4)
+ return null;
+
+ 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);
+
+ // 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("service.dat_entry"));
+ mapping.SetDataPtr(data, 12 + recordCount * 4);
+ for (int i = 0; i < recordCount; i++, mapping.BaseOffset += recordSize)
+ {
+ var ch = new ChannelInfo(list.SignalSource, i, 0, null);
+
+ var progNr = mapping.GetWord("offProgNr");
+ var transponderId = mapping.GetWord("offTransponderIndex");
+ if (progNr == 0xFFFF || transponderId == 0xFFFF)
+ {
+ ch.IsDeleted = true;
+ ch.OldProgramNr = -1;
+ DataRoot.AddChannel(list, ch);
+ continue;
+ }
+
+ ch.PcrPid = mapping.GetWord("offPcrPid") & mapping.GetMask("maskPcrPid");
+ ch.Lock = mapping.GetFlag("Locked");
+ ch.OriginalNetworkId = mapping.GetWord("OffOnid"); // can be 0 in some lists
+ 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; // setting this here would mess up the proper order
+ ch.OldProgramNr = progNr;
+
+ dvbStringDecoder.GetChannelNames(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(data, mapping.BaseOffset + mapping.GetConst("offProvider", 0), mapping.GetConst("lenProvider", 0), out var provider, out _);
+ ch.Provider = provider.TrimEnd('\0');
+
+ 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 (ch.OriginalNetworkId == 0)
+ ch.OriginalNetworkId = t.OriginalNetworkId;
+ if (ch.TransportStreamId == 0)
+ ch.TransportStreamId = t.TransportStreamId;
+ }
+
+ this.DataRoot.AddChannel(list, ch);
+ }
+
+ return data;
+ }
+ #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;
+
+ int firstFavIndex = BitConverter.ToInt16(data, 4);
+ int favCount = BitConverter.ToInt16(data, 6);
+ if (favCount > recordCount || firstFavIndex < 0 || firstFavIndex >= recordCount)
+ return;
+
+ this.dataFilePaths.Add(path);
+
+ 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
+
+ ///
+ /// List of files for backup/restore
+ ///
+ public override IEnumerable GetDataFilePaths() => this.dataFilePaths;
+ #endregion
+
+
+ #region Save()
+ public override void Save(string tvOutputFile)
+ {
+ var dir = Path.GetDirectoryName(this.FileName);
+
+ // TODO: save cable and antenna channels
+
+ SaveDvbsChannels(Path.Combine(dir, "s2channellib", "service.dat"));
+ SaveDvbsFavorites(Path.Combine(dir, "s2channellib", "favorite.dat"));
+ SaveDvbsDbFileInfo(Path.Combine(dir, "s2channellib", "db_file_info.dat"));
+ }
+ #endregion
+
+ #region SaveDvbsChannels
+ private void SaveDvbsChannels(string path)
+ {
+ byte[] deletedChannelData = {
+ 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc2, 0x3f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ };
+
+ var data = File.ReadAllBytes(path);
+ int recordSize = BitConverter.ToInt32(data, 4);
+ int recordCount = BitConverter.ToInt32(data, 8);
+
+ var mapping = new DataMapping(this.ini.GetSection("service.dat_entry"));
+ mapping.SetDataPtr(data, 12 + recordCount * 4);
+
+ foreach (var ch in this.satChannels.Channels)
+ {
+ mapping.BaseOffset = 12 + recordCount * 4 + (int)ch.RecordIndex * recordSize;
+ if (ch.IsDeleted)
+ {
+ Array.Copy(deletedChannelData, 0, data, mapping.BaseOffset, Math.Min(deletedChannelData.Length, recordSize));
+ continue;
+ }
+ mapping.SetWord("offProgNr", ch.NewProgramNr);
+ mapping.SetFlag("IsFav", ch.Favorites != 0);
+ mapping.SetFlag("Locked", ch.Lock);
+ }
+
+ 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 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
+ }
+}
diff --git a/source/ChanSort.Loader.PhilipsBin/SerializerPlugin.cs b/source/ChanSort.Loader.PhilipsBin/SerializerPlugin.cs
new file mode 100644
index 0000000..ad577a0
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsBin/SerializerPlugin.cs
@@ -0,0 +1,16 @@
+using ChanSort.Api;
+
+namespace ChanSort.Loader.PhilipsBin
+{
+ public class SerializerPlugin : ISerializerPlugin
+ {
+ public string DllName { get; set; }
+ public string PluginName => "Philips .bin/.dat";
+ public string FileFilter => "*.bin;*.dat;*";
+
+ public SerializerBase CreateSerializer(string inputFile)
+ {
+ return new Serializer(inputFile);
+ }
+ }
+}
diff --git a/source/ChanSort.sln b/source/ChanSort.sln
index 005d6ee..7266222 100644
--- a/source/ChanSort.sln
+++ b/source/ChanSort.sln
@@ -76,6 +76,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Loader.VDR", "Test.Loa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.M3u", "ChanSort.Loader.M3u\ChanSort.Loader.M3u.csproj", "{484028B6-3AAE-4F7E-A88A-76BEEB70203B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.PhilipsBin", "ChanSort.Loader.PhilipsBin\ChanSort.Loader.PhilipsBin.csproj", "{1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -422,6 +424,18 @@ Global
{484028B6-3AAE-4F7E-A88A-76BEEB70203B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{484028B6-3AAE-4F7E-A88A-76BEEB70203B}.Release|x86.ActiveCfg = Release|x86
{484028B6-3AAE-4F7E-A88A-76BEEB70203B}.Release|x86.Build.0 = Release|x86
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Debug|x86.ActiveCfg = Debug|x86
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Debug|x86.Build.0 = Debug|x86
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Release|x86.ActiveCfg = Release|Any CPU
+ {1F52B5EC-A2F1-4E53-9E1A-4658296C5BB5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/source/ChanSort/ChanSort.csproj b/source/ChanSort/ChanSort.csproj
index 4cab1dc..19d49c6 100644
--- a/source/ChanSort/ChanSort.csproj
+++ b/source/ChanSort/ChanSort.csproj
@@ -484,6 +484,10 @@
{68da8072-3a29-4076-9f64-d66f38349585}
ChanSort.Loader.Panasonic
+
+ {1f52b5ec-a2f1-4e53-9e1a-4658296c5bb5}
+ ChanSort.Loader.PhilipsBin
+
{d7bafd55-50f5-46c3-a76b-2193bed5358f}
ChanSort.Loader.PhilipsXml
diff --git a/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/philips_dat.h b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/philips_dat.h
index a13e330..3758539 100644
--- a/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/philips_dat.h
+++ b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/philips_dat.h
@@ -6,36 +6,167 @@ struct Ph_NextPrevTableEntry
word prev;
};
-struct Ph_Channel
+public struct Ph_DbFileInfoDat
+{
+ dword dataSize;
+ var off0 = current_offset;
+ byte unk1[6];
+ struct
+ {
+ word unk : 2;
+ word seqNr : 14;
+ } seq;
+ byte unk[dataSize - (current_offset - off0)];
+ dword crc32;
+};
+
+public struct Ph_SatelliteDat
{
- word pcrPid;
word unk1;
- word onid;
- word tsid;
- word sid;
- word unk3;
- word unk4;
- word unk5;
- word vpid;
- word unk6;
- word progNr;
- word unk7;
- word unk8;
- word unk9;
- char channelName[32];
- char providerName[32];
- char unk9[40];
+ word unk2;
+ dword recordSize;
+ dword recordCount;
+ Ph_NextPrevTableEntry NextPrevTable[recordCount];
+ struct
+ {
+ var off0 = current_offset;
+ dword oneShiftLeftSatIndex;
+ word unk1;
+ word unk2;
+ byte orbitalPos;
+ byte unk3[7];
+ char name[16];
+ byte unk[recordSize - (current_offset - off0)];
+ } Satellites[recordCount];
+ dword crc32;
+};
+
+public struct Ph_TuneinfoDat
+{
+ word unk1;
+ word unk2;
+ dword recordSize;
+ dword recordCount;
+ Ph_NextPrevTableEntry NextPrevTable[recordCount];
+ struct
+ {
+ var off0 = current_offset;
+ word symbolRate;
+ word freqInMhz;
+ word unk1;
+ struct
+ {
+ byte unk : 4;
+ byte satIndex: 4;
+ } u1a;
+ byte unk2[9];
+ word tsid;
+ word onid;
+ word unk3;
+ char networkName[32];
+ word unk4;
+ byte unk[recordSize - (current_offset - off0)];
+ } Transponders[recordCount];
+ dword crc32;
};
public struct Ph_ServiceDat
{
word unk1;
- word chanCount;
- word chanSize;
- word unk2;
word chanCount2;
- word unk3;
+ dword chanSize;
+ dword chanCount;
Ph_NextPrevTableEntry NextPrevTable[chanCount];
- Ph_Channel Channels[chanCount];
+ struct
+ {
+ var off0 = current_offset;
+ struct
+ {
+ word pid : 13;
+ word unk : 3;
+ } pcrPid;
+ byte unk1a;
+ struct
+ {
+ byte unk : 4;
+ byte locked : 1;
+ byte unk2 : 3;
+ } flags;
+ word onid;
+ word tsid;
+ word sid;
+ word transponderIndex;
+ word unk4;
+ word unk5;
+ struct
+ {
+ word vpid : 13;
+ word unk : 2;
+ word isFav : 1;
+ } vpid;
+ word unk6;
+ word progNr;
+ word unk7;
+ word unk8;
+ word unk9;
+ char channelName[32];
+ char providerName[32];
+ byte unk9[chanSize - (current_offset - off0)];
+ } Channels[chanCount];
+ [description("CRC32 MSBit first, init=0xFFFFFFFF, poly=0xEDB88320, post=0xFFFFFFFF, as int32 little-endian")]
dword crc32;
};
+
+public struct Ph_FavoriteDat
+{
+ dword dataSize;
+ short firstIndex;
+ short count;
+ struct
+ {
+ short prev;
+ short next;
+ } Table[dataSize/4-1];
+ dword crc32;
+};
+
+
+public struct Ph_CableDigSrvTable
+{
+ byte unk1[8];
+ dword chRecordSize;
+ dword channelCount;
+ byte unk2[4];
+ struct Ph_CableChannel
+ {
+ var off0 = current_offset;
+ dword checksum;
+ byte unk1[110];
+ word progNr;
+ byte unk2[6];
+ word progNr2;
+ byte unk2b[16];
+ byte locked;
+ byte unk3[75];
+ wchar_t channelName[32];
+ byte unk4[chRecordSize - (current_offset - off0)];
+ } Channels[channelCount];
+};
+
+public struct Ph_CablePresetTable
+{
+ byte unk1[8];
+ dword recordSize;
+ dword recordCount;
+ byte unk2[4];
+ struct
+ {
+ var off0 = current_offset;
+ byte unk1[12];
+ word unk2;
+ word unk3;
+ word unk4;
+ word unk5;
+ byte unk[recordSize - (current_offset - off0)];
+ } ChanPreset[recordCount];
+};