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]; +};