added first draft of Philips "Repair/ChannelList/s2channellib/*.dat" loader.

currently supports:
- read and write of satellite channels (incl. sorted favorites and "locked" flag)
- read-only of digital cable/antenna files (still missing transponder information)
This commit is contained in:
Horst Beham
2020-08-08 13:58:53 +02:00
parent 736ab2eebf
commit 736f385ccd
13 changed files with 878 additions and 44 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
source/ChanSort.opensdf
/source/.vs/
/source/packages/
/source/ChanSort.Loader.PhilipsBin/DllClient.cs

View File

@@ -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

View File

@@ -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()
/// <param name="msbFirst">true for using the "left shift" most-significant-bit-first algorithm</param>
/// <param name="poly"></param>
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
/// <param name="msbFirst">true for using the "left shift" MSB-first algorithm with polynomial 0x04C11Db7. false to use "right shift" with polynomial 0xEDB883320</param>
public Crc32(bool msbFirst = true)
{
this.msbFirst = msbFirst;
crc32Table = InitCrc32Table();
}
#region CalcCrc32()
public uint CalcCrc32(byte[] data, int start, int length)
{

View File

@@ -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; } }

View File

@@ -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;

View File

@@ -0,0 +1,83 @@
<?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.PhilipsBin</RootNamespace>
<AssemblyName>ChanSort.Loader.PhilipsBin</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="Properties\AssemblyInfo.cs" />
<Compile Include="Serializer.cs" />
<Compile Include="SerializerPlugin.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ChanSort.Api\ChanSort.Api.csproj">
<Project>{dccffa08-472b-4d17-bb90-8f513fc01392}</Project>
<Name>ChanSort.Api</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="ChanSort.Loader.PhilipsBin.ini">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -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

View File

@@ -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")]

View File

@@ -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<string> dataFilePaths = new List<string>();
#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
/// <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);
// 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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -484,6 +484,10 @@
<Project>{68da8072-3a29-4076-9f64-d66f38349585}</Project>
<Name>ChanSort.Loader.Panasonic</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.PhilipsBin\ChanSort.Loader.PhilipsBin.csproj">
<Project>{1f52b5ec-a2f1-4e53-9e1a-4658296c5bb5}</Project>
<Name>ChanSort.Loader.PhilipsBin</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.PhilipsXml\ChanSort.Loader.PhilipsXml.csproj">
<Project>{d7bafd55-50f5-46c3-a76b-2193bed5358f}</Project>
<Name>ChanSort.Loader.PhilipsXml</Name>

View File

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