2021-09-03 02:26:57 +02:00
using System ;
2022-07-03 21:48:12 +02:00
using System.Collections.Generic ;
2021-09-03 02:26:57 +02:00
using System.IO ;
2022-07-03 21:48:12 +02:00
using System.Linq ;
2021-09-03 02:26:57 +02:00
using System.Text ;
using ChanSort.Api ;
namespace ChanSort.Loader.CmdbBin
{
public class CmdbFileSerializer : SerializerBase
{
private IniFile ini ;
2022-07-03 21:48:12 +02:00
private readonly List < string > files = new ( ) ;
private readonly ChannelList avbtTv = new ( SignalSource . AnalogT | SignalSource . Tv , "Analog Antenna TV" ) ;
private readonly ChannelList avbcTv = new ( SignalSource . AnalogC | SignalSource . Tv , "Analog Cable TV" ) ;
private readonly ChannelList dvbsTv = new ( SignalSource . DvbS | SignalSource . Tv , "Sat TV" ) ;
private readonly ChannelList dvbsRadio = new ( SignalSource . DvbS | SignalSource . Radio , "Sat Radio" ) ;
private readonly ChannelList dvbsData = new ( SignalSource . DvbS | SignalSource . Radio , "Sat Data" ) ;
2021-09-03 02:26:57 +02:00
private DvbStringDecoder dvbStringDecoder ;
2021-09-05 00:46:26 +02:00
private bool loaded ;
2022-07-03 21:48:12 +02:00
private readonly StringBuilder protocol = new ( ) ;
2021-09-03 02:26:57 +02:00
public CmdbFileSerializer ( string inputFile ) : base ( inputFile )
{
2021-09-04 22:24:56 +02:00
this . Features . FavoritesMode = FavoritesMode . Flags ;
this . Features . MaxFavoriteLists = 1 ;
2021-09-05 00:46:26 +02:00
this . Features . DeleteMode = DeleteMode . FlagWithoutPrNr ; // TODO there can be lots of channels in each list with number 65534, which seems to indicate user-deleted
2022-07-03 21:48:12 +02:00
this . Features . ChannelNameEdit = ChannelNameEditMode . Analog ;
2021-09-04 22:24:56 +02:00
2022-07-03 21:48:12 +02:00
this . DataRoot . AddChannelList ( avbtTv ) ;
this . DataRoot . AddChannelList ( avbcTv ) ;
2021-09-03 02:26:57 +02:00
this . DataRoot . AddChannelList ( dvbsTv ) ;
this . DataRoot . AddChannelList ( dvbsRadio ) ;
// this.DataRoot.AddChannelList(dvbsData); // there seem to be multiple data lists with Toshiba TVs which all have their own numbering starting at 1. Better don't show data channels at all than dupes
this . ReadConfigurationFromIniFile ( ) ;
2021-09-04 22:24:56 +02:00
foreach ( var list in this . DataRoot . ChannelLists )
list . VisibleColumnFieldNames . Remove ( nameof ( ChannelInfo . Hidden ) ) ;
2021-09-03 02:26:57 +02:00
}
#region ReadConfigurationFromIniFile ( )
private void ReadConfigurationFromIniFile ( )
{
string iniFile = this . GetType ( ) . Assembly . Location . ToLowerInvariant ( ) . Replace ( ".dll" , ".ini" ) ;
this . ini = new IniFile ( iniFile ) ;
}
#endregion
#region Load ( )
public override void Load ( )
{
this . dvbStringDecoder = new DvbStringDecoder ( this . DefaultEncoding ) ;
2021-09-05 00:46:26 +02:00
foreach ( var file in Directory . GetFiles ( Path . GetDirectoryName ( this . FileName ) ? ? "." ) )
2021-09-03 02:26:57 +02:00
{
var lower = Path . GetFileName ( file ) . ToLowerInvariant ( ) ;
switch ( lower )
{
case "dtv_cmdb_2.bin" :
2021-09-05 00:46:26 +02:00
LoadFile ( file , this . dvbsTv , this . dvbsRadio , this . dvbsData ) ;
2021-09-03 02:26:57 +02:00
break ;
2022-07-03 21:48:12 +02:00
case "atv_cmdb.bin" :
LoadFile ( file , this . avbtTv , null , null ) ;
break ;
case "atv_cmdb_cable.bin" :
LoadFile ( file , this . avbcTv , null , null ) ;
break ;
2021-09-03 02:26:57 +02:00
}
}
if ( ! this . loaded )
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( "\"" + this . FileName + "\" does not belong to a supported dtv_cmdb_* file system" ) ;
2021-09-03 02:26:57 +02:00
}
#endregion
#region LoadFile ( )
2021-09-05 00:46:26 +02:00
private void LoadFile ( string file , ChannelList tvList , ChannelList radioList , ChannelList dataList )
2021-09-03 02:26:57 +02:00
{
var data = File . ReadAllBytes ( file ) ;
2021-09-05 00:46:26 +02:00
var fileName = Path . GetFileName ( file ) . ToLowerInvariant ( ) ;
2022-01-07 22:02:35 +01:00
var secId = $"{fileName}:{data.Length}" ;
var sec = this . ini . GetSection ( secId ) ;
if ( sec = = null )
{
protocol . AppendLine ( "Skipped file with unknown data size: " + secId ) ;
return ;
}
2021-09-03 02:26:57 +02:00
2022-07-03 21:48:12 +02:00
if ( ( tvList . SignalSource & SignalSource . Analog ) ! = 0 )
{
var seq = LoadAnalogProgramNumbers ( data , sec ) ;
LoadBitmappedRecords ( data , sec , "avb" , "Channel" , ( map , index , len ) = > ReadAnalogChannel ( map , tvList , seq , index , len ) ) ;
}
else
{
LoadBitmappedRecords ( data , sec , "dvbs" , "Satellite" , ReadSatellite ) ;
LoadBitmappedRecords ( data , sec , "dvbs" , "Transponder" , ReadTransponder ) ;
LoadBitmappedRecords ( data , sec , "dvbs" , "Channel" , ( map , index , len ) = > ReadDigitalChannel ( map , tvList , radioList , dataList , index , len ) ) ;
}
2021-09-03 02:26:57 +02:00
this . loaded = true ;
2022-07-03 21:48:12 +02:00
this . files . Add ( file ) ;
2021-09-03 02:26:57 +02:00
}
#endregion
2022-07-03 21:48:12 +02:00
#region LoadAnalogProgramNumbers ( )
private byte [ ] LoadAnalogProgramNumbers ( byte [ ] data , IniFile . Section sec )
{
var off = sec . GetInt ( "offProgNrList" ) ;
var len = sec . GetInt ( "lenProgNrList" ) ;
var bytes = new byte [ len ] ;
Array . Copy ( data , off , bytes , 0 , len ) ;
return bytes ;
}
#endregion
2021-09-03 02:26:57 +02:00
#region LoadBitmappedRecords ( )
2022-07-03 21:48:12 +02:00
private void LoadBitmappedRecords ( byte [ ] data , IniFile . Section sec , string recordSectionPrefix , string recordType , Action < DataMapping , int , int > readRecord )
2021-09-03 02:26:57 +02:00
{
var lenRecord = sec . GetInt ( $"len{recordType}Record" ) ;
2022-07-03 21:48:12 +02:00
var map = new DataMapping ( this . ini . GetSection ( $"{recordSectionPrefix}{recordType}:{lenRecord}" ) ) ;
2021-09-03 02:26:57 +02:00
map . DefaultEncoding = this . DefaultEncoding ;
map . SetDataPtr ( data , sec . GetInt ( $"off{recordType}Record" ) ) ;
var off = sec . GetInt ( $"off{recordType}Bitmap" ) ;
var len = sec . GetInt ( $"len{recordType}Bitmap" ) ;
2022-07-03 21:48:12 +02:00
var count = sec . GetInt ( $"num{recordType}Record" , short . MaxValue ) ;
2021-09-03 02:26:57 +02:00
int index = 0 ;
for ( int i = 0 ; i < len ; i + + )
{
var b = data [ off + i ] ;
for ( byte mask = 1 ; mask ! = 0 ; mask < < = 1 )
{
if ( ( b & mask ) ! = 0 )
2021-09-05 00:46:26 +02:00
readRecord ( map , index , lenRecord ) ;
2021-09-03 02:26:57 +02:00
map . BaseOffset + = lenRecord ;
2022-07-03 21:48:12 +02:00
+ + index ;
if ( index > = count )
2021-09-03 02:26:57 +02:00
break ;
}
}
}
#endregion
2021-09-04 22:24:56 +02:00
#region ReadSatellite ( )
2021-09-05 00:46:26 +02:00
private void ReadSatellite ( DataMapping map , int index , int lenRecord )
2021-09-03 02:26:57 +02:00
{
var sat = new Satellite ( index ) ;
sat . Name = map . GetString ( "offName" , map . Settings . GetInt ( "lenName" ) ) ;
this . DataRoot . AddSatellite ( sat ) ;
}
2021-09-04 22:24:56 +02:00
#endregion
#region ReadTransponder ( )
2021-09-05 00:46:26 +02:00
private void ReadTransponder ( DataMapping map , int index , int lenRecord )
2021-09-03 02:26:57 +02:00
{
//var idx = map.GetWord("offTransponderIndex"); // seems to be some logical number, skipping a new numbers here and there
var tp = new Transponder ( index ) ;
var satIndex = map . GetWord ( "offSatelliteIndex" ) ;
tp . Satellite = this . DataRoot . Satellites . TryGet ( satIndex ) ;
tp . OriginalNetworkId = map . GetWord ( "offOriginalNetworkId" ) ;
tp . TransportStreamId = map . GetWord ( "offTransportStreamId" ) ;
tp . FrequencyInMhz = map . GetDword ( "offFreqInMhz" ) ;
tp . SymbolRate = map . GetWord ( "offSymbolRate" ) ;
this . DataRoot . AddTransponder ( tp . Satellite , tp ) ;
}
2021-09-04 22:24:56 +02:00
#endregion
2022-07-03 21:48:12 +02:00
#region ReadAnalogChannel
private void ReadAnalogChannel ( DataMapping chanMap , ChannelList list , byte [ ] progNrList , int recordIndex , int recordLength )
{
var channelNameLength = chanMap . Settings . GetInt ( "lenName" ) ;
var name = chanMap . GetString ( "offName" , channelNameLength ) . TrimEnd ( new [ ] { '\0' , ' ' } ) ;
var progNr = Array . IndexOf ( progNrList , ( byte ) recordIndex ) + 1 ;
var ch = new ChannelInfo ( list . SignalSource , recordIndex , progNr , name ) ;
ch . FreqInMhz = ( decimal ) chanMap . GetWord ( "offFrequency" ) * 50 / 1000 ;
//if ((list.SignalSource & SignalSource.Cable) != 0)
// ch.ChannelOrTransponder = LookupData.Instance.GetDvbcChannelName(ch.FreqInMhz + ((list.SignalSource & SignalSource.Analog) != 0 ? 2.75m : 0)); //
this . DataRoot . AddChannel ( list , ch ) ;
}
#endregion
2021-09-03 02:26:57 +02:00
2022-07-03 21:48:12 +02:00
#region ReadDigitalChannel ( )
private void ReadDigitalChannel ( DataMapping chanMap , ChannelList tvList , ChannelList radioList , ChannelList dataList , int recordIndex , int recordLength )
2021-09-03 02:26:57 +02:00
{
var channelType = ( int ) chanMap . GetByte ( "offChannelType" ) ;
if ( channelType = = 0 ) // some file format versions store the channel type in the upper nibble of a byte
channelType = chanMap . GetByte ( "offChannelTypeOld" ) > > 4 ;
var serviceType = chanMap . GetByte ( "offServiceType" ) ;
2021-09-04 22:24:56 +02:00
if ( chanMap . Settings . GetInt ( "offFav" , - 1 ) < 0 )
this . Features . FavoritesMode = FavoritesMode . None ;
2021-09-03 02:26:57 +02:00
ChannelList list ;
if ( channelType ! = 0 )
list = channelType = = 1 ? tvList : channelType = = 2 ? radioList : dataList ;
else if ( serviceType ! = 0 )
{
var type = LookupData . Instance . IsRadioTvOrData ( serviceType ) ;
list = type = = SignalSource . Radio ? radioList : type = = SignalSource . Tv ? tvList : dataList ;
}
else
list = tvList ;
var progNr = ( int ) chanMap . GetWord ( "offProgramNr" ) ;
if ( progNr = = 0xFFFE )
progNr = - 2 ;
var ch = new ChannelInfo ( list . SignalSource , recordIndex , progNr , "" ) ;
ch . ServiceType = serviceType ;
2021-09-05 00:46:26 +02:00
ch . ServiceTypeName = LookupData . Instance . GetServiceTypeDescription ( ch . ServiceType ) ;
2021-09-03 02:26:57 +02:00
ch . ServiceId = chanMap . GetWord ( "offServiceId" ) ;
2021-09-05 00:46:26 +02:00
ch . PcrPid = chanMap . GetWord ( "offPcrPid" ) & 0x1FFF ;
ch . AudioPid = chanMap . GetWord ( "offAudioPid" ) & 0x1FFF ;
2021-09-04 22:24:56 +02:00
ch . VideoPid = chanMap . GetWord ( "offVideoPid" ) & 0x1FFF ;
2021-09-15 10:59:05 +02:00
ch . Encrypted = chanMap . GetFlag ( "Encrypted" , false ) ;
ch . Skip = chanMap . GetFlag ( "Skip" , false ) ;
ch . Lock = chanMap . GetFlag ( "Locked" , false ) ;
ch . Favorites = chanMap . GetFlag ( "Fav" , false ) ? Favorites . A : 0 ;
2021-09-03 02:26:57 +02:00
var off = chanMap . BaseOffset + chanMap . GetOffsets ( "offName" ) [ 0 ] ;
this . dvbStringDecoder . GetChannelNames ( chanMap . Data , off , chanMap . Settings . GetInt ( "lenName" ) , out var longName , out var shortName ) ;
ch . Name = longName ;
ch . ShortName = shortName ;
var offProv = chanMap . GetOffsets ( "offProvider" ) ;
if ( offProv . Length > 0 )
{
off = chanMap . BaseOffset + offProv [ 0 ] ;
this . dvbStringDecoder . GetChannelNames ( chanMap . Data , off , chanMap . Settings . GetInt ( "lenName" ) , out longName , out _ ) ;
ch . Provider = longName ;
}
var offDebug = chanMap . Settings . GetInt ( "offDebug" ) ;
var lenDebug = chanMap . Settings . GetInt ( "lenDebug" ) ;
ch . AddDebug ( chanMap . Data , chanMap . BaseOffset + offDebug , lenDebug ) ;
var transponderIndex = chanMap . GetWord ( "offTransponderIndex" ) ;
var tp = this . DataRoot . Transponder . TryGet ( transponderIndex ) ;
if ( tp ! = null )
{
ch . Transponder = tp ;
ch . OriginalNetworkId = tp . OriginalNetworkId ;
ch . TransportStreamId = tp . TransportStreamId ;
ch . FreqInMhz = tp . FrequencyInMhz ;
ch . SymbolRate = tp . SymbolRate ;
ch . Satellite = tp . Satellite ? . Name ;
}
this . DataRoot . AddChannel ( list , ch ) ;
2021-09-05 00:46:26 +02:00
// validate checksum
var calculated = CalcChecksum ( chanMap . Data , chanMap . BaseOffset , recordLength - 4 ) ;
var expected = BitConverter . ToInt32 ( chanMap . Data , chanMap . BaseOffset + recordLength - 4 ) ;
if ( calculated ! = expected )
this . protocol . AppendFormat ( $"Data record has invalid checksum. Expected: {expected}, calculated: {calculated}\r\n" ) ;
}
#endregion
#region CalcChecksum ( )
private int CalcChecksum ( byte [ ] data , int offset , int length )
{
int sum = 0 ;
for ( int i = 0 ; i < length ; i + + )
sum + = data [ offset + + ] ;
return sum ;
2021-09-03 02:26:57 +02:00
}
#endregion
2021-09-05 00:46:26 +02:00
#region Save ( )
2022-07-03 21:48:12 +02:00
2022-11-29 22:00:16 +01:00
public override void Save ( )
2021-09-03 02:26:57 +02:00
{
2022-07-03 21:48:12 +02:00
foreach ( var path in this . files )
{
var name = Path . GetFileName ( path ) . ToLowerInvariant ( ) ;
switch ( name )
{
case "dtv_cmdb_2.bin" :
SaveDtvCmdb ( path , "dvbsChannel" , SignalSource . DvbS ) ;
break ;
case "atv_cmdb.bin" :
SaveAtvCmdb ( path , "avbChannel" , this . avbtTv ) ;
break ;
case "atv_cmdb_cable.bin" :
SaveAtvCmdb ( path , "avbChannel" , this . avbcTv ) ;
break ;
}
}
}
#endregion
2021-09-05 00:46:26 +02:00
2022-07-03 21:48:12 +02:00
#region SaveDtvCmdb ( )
private void SaveDtvCmdb ( string path , string channelSectionName , SignalSource sourceMask )
{
var data = File . ReadAllBytes ( path ) ;
var name = Path . GetFileName ( path ) . ToLowerInvariant ( ) ;
var config = this . ini . GetSection ( name + ":" + data . Length ) ;
2021-09-05 00:46:26 +02:00
var lenChannelRecord = config . GetInt ( "lenChannelRecord" ) ;
2022-07-03 21:48:12 +02:00
var sec = this . ini . GetSection ( $"{channelSectionName}:{lenChannelRecord}" ) ;
2021-09-05 00:46:26 +02:00
sec . Set ( "offChecksum" , lenChannelRecord - 4 ) ;
var mapping = new DataMapping ( sec ) ;
var baseOffset = config . GetInt ( "offChannelRecord" ) ;
foreach ( var list in this . DataRoot . ChannelLists )
{
2023-06-03 10:38:11 +02:00
if ( ( list . SignalSource & ( SignalSource . MaskBcastSystem | SignalSource . MaskBcastMedium ) ) ! = sourceMask )
2022-07-03 21:48:12 +02:00
continue ;
2021-09-05 00:46:26 +02:00
foreach ( var chan in list . Channels )
{
mapping . SetDataPtr ( data , baseOffset + ( int ) chan . RecordIndex * lenChannelRecord ) ;
mapping . SetWord ( "offProgramNr" , chan . IsDeleted ? 0xFFFE : chan . NewProgramNr ) ;
if ( chan . IsDeleted ) // undo the automatic number changes from the "File / Save" function
{
chan . NewProgramNr = - 2 ;
chan . IsDeleted = false ;
}
mapping . SetFlag ( "Skip" , chan . Skip ) ;
mapping . SetFlag ( "Lock" , chan . Lock ) ;
mapping . SetFlag ( "Fav" , chan . Favorites ! = 0 ) ;
var sum = CalcChecksum ( data , mapping . BaseOffset , lenChannelRecord - 4 ) ;
mapping . SetDword ( "offChecksum" , sum ) ;
}
}
2022-11-29 22:00:16 +01:00
File . WriteAllBytes ( path , data ) ;
2021-09-05 00:46:26 +02:00
}
#endregion
2022-07-03 21:48:12 +02:00
#region SaveAtvCmdb ( )
private void SaveAtvCmdb ( string path , string channelSectionName , ChannelList list )
{
var data = File . ReadAllBytes ( path ) ;
var name = Path . GetFileName ( path ) . ToLowerInvariant ( ) ;
var config = this . ini . GetSection ( name + ":" + data . Length ) ;
var lenChannelRecord = config . GetInt ( "lenChannelRecord" ) ;
var offProgNrList = config . GetInt ( "offProgNrList" ) ;
var lenProgNrList = config . GetInt ( "lenProgNrList" ) ;
var sec = this . ini . GetSection ( $"{channelSectionName}:{lenChannelRecord}" ) ;
var mapping = new DataMapping ( sec ) ;
var offChannelBitmap = config . GetInt ( "offChannelBitmap" ) ;
var offChannelRecord = config . GetInt ( "offChannelRecord" ) ;
var maxNameLen = mapping . Settings . GetInt ( "lenName" ) ;
data . MemSet ( offProgNrList , 0xFD , lenProgNrList ) ;
foreach ( var chan in list . Channels )
{
if ( chan . NewProgramNr > 0 & & chan . NewProgramNr < lenProgNrList )
data [ offProgNrList + chan . NewProgramNr - 1 ] = ( byte ) chan . RecordIndex ;
if ( chan . IsNameModified )
{
mapping . SetDataPtr ( data , offChannelRecord + ( int ) chan . RecordIndex * lenChannelRecord ) ;
mapping . SetString ( "offName" , chan . Name , maxNameLen ) ;
}
if ( chan . IsDeleted )
{
var idx = ( int ) chan . RecordIndex ;
data [ offChannelBitmap + idx / 8 ] & = ( byte ) ~ ( 1 < < ( idx & 0x07 ) ) ;
}
}
File . WriteAllBytes ( path , data ) ;
}
#endregion
2021-09-05 00:46:26 +02:00
#region GetFileInformation ( )
public override string GetFileInformation ( )
{
return base . GetFileInformation ( ) + "\n\n" + protocol ;
2021-09-03 02:26:57 +02:00
}
2021-09-05 00:46:26 +02:00
#endregion
2022-07-03 21:48:12 +02:00
public override IEnumerable < string > GetDataFilePaths ( ) = > this . files . ToList ( ) ; // these files will be backed up / restored
2021-09-03 02:26:57 +02:00
}
}