- improved IniFile and Mapping to better handle missing settings

- working on philips *.db/FLASH* file format (identified 3 different variations so far)
This commit is contained in:
Horst Beham
2021-09-15 10:59:05 +02:00
parent 44749acaf0
commit ece2cd7e66
15 changed files with 968 additions and 354 deletions

View File

@@ -6,14 +6,14 @@ namespace ChanSort.Api
public class DataMapping
{
protected readonly IniFile.Section settings;
private int baseOffset;
private byte[] data { get; set; }
public Encoding DefaultEncoding { get; set; }
public bool ThrowWhenAccessingUnknownSetting { get; set; } = false;
#region ctor()
public DataMapping(IniFile.Section settings)
public DataMapping(IniFile.Section settings, byte[] data = null)
{
this.settings = settings;
this.Data = data;
this.DefaultEncoding = Encoding.Default;
}
#endregion
@@ -21,19 +21,23 @@ namespace ChanSort.Api
#region SetDataPtr(), Data, BaseOffset
public void SetDataPtr(byte[] data, int baseOffset)
{
this.data = data;
this.baseOffset = baseOffset;
this.Data = data;
this.BaseOffset = baseOffset;
}
public byte[] Data { get { return this.data; } }
public int BaseOffset { get { return this.baseOffset; } set { this.baseOffset = value; } }
public byte[] Data { get; set; }
public int BaseOffset { get; set; }
#endregion
#region GetOffsets()
public int[] GetOffsets(string key)
{
return settings.GetIntList(key);
var list= settings.GetIntList(key);
if (list == null)
return DefaultValue(key, Array.Empty<int>());
return list;
}
#endregion
@@ -46,7 +50,7 @@ namespace ChanSort.Api
list = settings.GetIntList("mask" + key);
if (list != null && list.Length > 0)
return list[0];
return -1;
return DefaultValue(key, -1);
}
#endregion
@@ -61,22 +65,22 @@ namespace ChanSort.Api
#endregion
public IniFile.Section Settings { get { return this.settings; } }
public IniFile.Section Settings => this.settings;
#region Byte
public byte GetByte(string key)
{
var offsets = settings.GetIntList(key);
if (offsets.Length==0) return 0;
return this.data[baseOffset + offsets[0]];
if (offsets.Length==0) return DefaultValue(key, (byte)0);
return this.Data[BaseOffset + offsets[0]];
}
public void SetByte(string key, int value)
{
var offsets = settings.GetIntList(key);
foreach (int offset in offsets)
this.data[baseOffset + offset] = (byte)value;
this.Data[BaseOffset + offset] = (byte)value;
}
#endregion
@@ -84,8 +88,8 @@ namespace ChanSort.Api
public ushort GetWord(string key)
{
var offsets = settings.GetIntList(key);
if (offsets.Length == 0) return 0;
return BitConverter.ToUInt16(this.data, baseOffset + offsets[0]);
if (offsets.Length == 0) return DefaultValue(key, (ushort)0);
return BitConverter.ToUInt16(this.Data, BaseOffset + offsets[0]);
}
public void SetWord(string key, int value)
@@ -93,8 +97,8 @@ namespace ChanSort.Api
var offsets = settings.GetIntList(key);
foreach (int offset in offsets)
{
this.data[baseOffset + offset + 0] = (byte)value;
this.data[baseOffset + offset + 1] = (byte)(value>>8);
this.Data[BaseOffset + offset + 0] = (byte)value;
this.Data[BaseOffset + offset + 1] = (byte)(value>>8);
}
}
#endregion
@@ -103,8 +107,8 @@ namespace ChanSort.Api
public long GetDword(string key)
{
var offsets = settings.GetIntList(key);
if (offsets.Length == 0) return 0;
return BitConverter.ToUInt32(this.data, baseOffset + offsets[0]);
if (offsets.Length == 0) return DefaultValue(key, (uint)0);
return BitConverter.ToUInt32(this.Data, BaseOffset + offsets[0]);
}
public void SetDword(string key, long value)
@@ -112,10 +116,10 @@ namespace ChanSort.Api
var offsets = settings.GetIntList(key);
foreach (int offset in offsets)
{
this.data[baseOffset + offset + 0] = (byte)value;
this.data[baseOffset + offset + 1] = (byte)(value >> 8);
this.data[baseOffset + offset + 2] = (byte)(value >> 16);
this.data[baseOffset + offset + 3] = (byte)(value >> 24);
this.Data[BaseOffset + offset + 0] = (byte)value;
this.Data[BaseOffset + offset + 1] = (byte)(value >> 8);
this.Data[BaseOffset + offset + 2] = (byte)(value >> 16);
this.Data[BaseOffset + offset + 3] = (byte)(value >> 24);
}
}
#endregion
@@ -124,8 +128,8 @@ namespace ChanSort.Api
public float GetFloat(string key)
{
var offsets = settings.GetIntList(key);
if (offsets.Length == 0) return 0;
return BitConverter.ToSingle(this.data, baseOffset + offsets[0]);
if (offsets.Length == 0) return DefaultValue(key, 0f);
return BitConverter.ToSingle(this.Data, BaseOffset + offsets[0]);
}
public void SetFloat(string key, float value)
@@ -135,25 +139,25 @@ namespace ChanSort.Api
foreach (int offset in offsets)
{
for (int i = 0; i < 4; i++)
this.data[baseOffset + offset + i] = bytes[i];
this.Data[BaseOffset + offset + i] = bytes[i];
}
}
#endregion
#region GetFlag
public bool GetFlag(string key, bool defaultValue = false)
public bool GetFlag(string key, bool defaultValue)
{
return GetFlag("off" + key, "mask" + key, defaultValue);
}
public bool GetFlag(string valueKey, string maskKey, bool defaultValue = false)
public bool GetFlag(string valueKey, string maskKey, bool defaultValue)
{
int mask = settings.GetInt(maskKey);
return GetFlag(valueKey, mask, defaultValue);
}
public bool GetFlag(string valueKey, int mask, bool defaultValue = false)
public bool GetFlag(string valueKey, int mask, bool defaultValue)
{
if (mask == 0) return defaultValue;
@@ -165,7 +169,7 @@ namespace ChanSort.Api
}
var offsets = settings.GetIntList(valueKey);
if (offsets.Length == 0) return defaultValue;
bool isSet = (this.data[baseOffset + offsets[0]] & mask) == mask;
bool isSet = (this.Data[BaseOffset + offsets[0]] & mask) == mask;
return isSet != reverseLogic;
}
#endregion
@@ -195,9 +199,9 @@ namespace ChanSort.Api
foreach (var offset in offsets)
{
if (value != reverseLogic)
this.data[baseOffset + offset] |= (byte)mask;
this.Data[BaseOffset + offset] |= (byte)mask;
else
this.data[baseOffset + offset] &= (byte)~mask;
this.Data[BaseOffset + offset] &= (byte)~mask;
}
}
#endregion
@@ -207,12 +211,12 @@ namespace ChanSort.Api
public string GetString(string key, int maxLen)
{
var offsets = settings.GetIntList(key);
if (offsets.Length == 0) return null;
if (offsets.Length == 0) return DefaultValue(key, (string)null);
int length = this.GetByte(key + "Length");
if (length == 0)
length = maxLen;
var encoding = this.DefaultEncoding;
return encoding.GetString(this.data, baseOffset + offsets[0], length).TrimEnd('\0');
return encoding.GetString(this.Data, BaseOffset + offsets[0], length).TrimEnd('\0');
}
#endregion
@@ -223,13 +227,30 @@ namespace ChanSort.Api
int len = Math.Min(bytes.Length, maxLen);
foreach (var offset in settings.GetIntList(key))
{
Array.Copy(bytes, 0, this.data, baseOffset + offset, len);
Array.Copy(bytes, 0, this.Data, BaseOffset + offset, len);
for (int i = len; i < maxLen; i++)
this.data[baseOffset + offset + i] = 0;
this.Data[BaseOffset + offset + i] = 0;
}
return len;
}
#endregion
#region DefaultValue()
private T DefaultValue<T>(string key, T value)
{
if (this.ThrowWhenAccessingUnknownSetting)
throw new ArgumentException($"undefined setting \"{key}\" in {this.Settings}");
return value;
}
#endregion
#region HasValue()
public bool HasValue(string key)
{
var list = this.settings.GetIntList(key);
return list != null && list.Length > 0;
}
#endregion
}
}

View File

@@ -12,11 +12,14 @@ namespace ChanSort.Api
{
private readonly Dictionary<string, string> data = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
public Section(string name)
public Section(string name, IniFile iniFile)
{
this.Name = name;
this.IniFile = iniFile;
}
public IniFile IniFile { get; }
#region Name
public string Name { get; }
#endregion
@@ -121,24 +124,31 @@ namespace ChanSort.Api
return intValue;
}
#endregion
public override string ToString() => $"{this.IniFile} [{this.Name}]";
}
#endregion
private readonly Dictionary<string, Section> sectionDict;
private readonly List<Section> sectionList;
private readonly string filePath;
public IniFile(string fileName)
public IniFile(string filePath)
{
this.filePath = filePath;
this.sectionDict = new Dictionary<string, Section>();
this.sectionList = new List<Section>();
this.ReadIniFile(fileName);
this.ReadIniFile(filePath);
}
public IEnumerable<Section> Sections => this.sectionList;
public Section GetSection(string sectionName)
public Section GetSection(string sectionName, bool throwException = false)
{
return sectionDict.TryGet(sectionName);
var sec = sectionDict.TryGet(sectionName);
if (sec == null && throwException)
throw new ArgumentException($"No [{sectionName}] in {this}");
return sec;
}
#region ReadIniFile()
@@ -152,14 +162,14 @@ namespace ChanSort.Api
while ((line = rdr.ReadLine()) != null)
{
string trimmedLine = line.Trim();
if (trimmedLine.StartsWith(";"))
if (trimmedLine.Length == 0 || trimmedLine.StartsWith(";") || trimmedLine.StartsWith("#") || trimmedLine.StartsWith("//"))
continue;
if (trimmedLine.StartsWith("["))
{
string sectionName = trimmedLine.EndsWith("]")
? trimmedLine.Substring(1, trimmedLine.Length - 2)
: trimmedLine.Substring(1);
currentSection = new Section(sectionName);
currentSection = new Section(sectionName, this);
this.sectionList.Add(currentSection);
this.sectionDict[sectionName] = currentSection;
continue;
@@ -186,5 +196,7 @@ namespace ChanSort.Api
}
}
#endregion
public override string ToString() => Path.GetFileName(this.filePath);
}
}

View File

@@ -160,10 +160,10 @@ namespace ChanSort.Loader.CmdbBin
ch.PcrPid = chanMap.GetWord("offPcrPid") & 0x1FFF;
ch.AudioPid = chanMap.GetWord("offAudioPid") & 0x1FFF;
ch.VideoPid = chanMap.GetWord("offVideoPid") & 0x1FFF;
ch.Encrypted = chanMap.GetFlag("Encrypted");
ch.Skip = chanMap.GetFlag("Skip");
ch.Lock = chanMap.GetFlag("Locked");
ch.Favorites = chanMap.GetFlag("Fav") ? Favorites.A : 0;
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;
var off = chanMap.BaseOffset + chanMap.GetOffsets("offName")[0];
this.dvbStringDecoder.GetChannelNames(chanMap.Data, off, chanMap.Settings.GetInt("lenName"), out var longName, out var shortName);

View File

@@ -51,11 +51,11 @@ namespace ChanSort.Loader.LG.Binary
this.ParseNames();
this.Favorites = (Favorites)((data.GetByte(_Favorites2) & 0x3C) >> 2);
this.Lock = data.GetFlag(_Lock);
this.Skip = data.GetFlag(_Skip);
this.Hidden = data.GetFlag(_Hide);
this.Encrypted = data.GetFlag(_Encrypted);
this.IsDeleted = data.GetFlag(_Deleted);
this.Lock = data.GetFlag(_Lock, false);
this.Skip = data.GetFlag(_Skip, false);
this.Hidden = data.GetFlag(_Hide, false);
this.Encrypted = data.GetFlag(_Encrypted, false);
this.IsDeleted = data.GetFlag(_Deleted, false);
}
#endregion

View File

@@ -148,7 +148,7 @@ from channel c inner join chanseq s on s.listid=c.listid and s.slot=c.slot
cmd.Parameters["@listid"].Value = listId;
cmd.Parameters["@slot"].Value = slot;
cmd.Parameters["@seq"].Value = DBNull.Value;
cmd.Parameters["@isdel"].Value = dvbsMapping.GetFlag("InUse") ? 0 : 1;
cmd.Parameters["@isdel"].Value = dvbsMapping.GetFlag("InUse", false) ? 0 : 1;
cmd.Parameters["@progmask"].Value = dvbsMapping.GetWord("offProgramNr");
cmd.Parameters["@prognr"].Value = dvbsMapping.GetWord("offProgramNr") & 0x3FFF;
cmd.Parameters["@progfix"].Value = dvbsMapping.GetWord("offProgramNrPreset");

View File

@@ -492,12 +492,12 @@ namespace ChanSort.Loader.Philips
// onid, tsid, pcrpid and vpid can be 0 in some lists
ch.PcrPid = mapping.GetWord("offPcrPid") & mapping.GetMask("maskPcrPid");
ch.Lock = mapping.GetFlag("Locked");
ch.Lock = mapping.GetFlag("Locked", false);
ch.OriginalNetworkId = mapping.GetWord("offOnid");
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;
ch.Favorites = mapping.GetFlag("IsFav", false) ? Favorites.A : 0;
ch.OldProgramNr = progNr;
ch.Id = mapping.GetWord("offId"); // relevant for ChannelMap45

View File

@@ -2,22 +2,31 @@
# Repair\CM_*_LA_CK.BIN file (or similar) with a hidden .xml - stable
[Repair_xml]
reorderRecordsByChannelNumber=false
; nothing to configure
############################################################################
# Repair\channel_db_ver.db, atv_chan_phy_c.db, FLASH_DTVINFO_S_FTA, mgr_chan_s_fta.db, ... - experimental
# unfortunately read-only because all attempts so far produced inconsistencies between the channels in the menu and what the tuner actually put on screen
# There is a program number at offset 0 and another one around 450. My guess is that the 1st is the "new" number and the 2nd the "old" number - or it is the other way around.
# Repair\channel_db_ver.db, mgr_chan_s_fta.db, FLASH_DTVINFO_S_FTA, _pkg, ... - experimental
# There are different version of this format using different offsets/lengths/sizes.
# The calculated length of a channel record in the .db file is used to select specific config sections.
#
# There is a program number at offset 0 and another one around 450 in the .db file.
# My guess is that the 1st is the "new" number and the 2nd the "old" number - or it is the other way around.
# When both numbers are updated and also the value of RecordIndex is set to PrNr-1, the TV doesn't show any changes.
; ---------------------------------------------
; mappings that are the same for all format variants
[mgr_chan_s_fta.db]
lenHeader=64
lenEntry=476
lenFooter=12
offFooterChecksum=8
reorderRecordsByChannelNumber=false
allowEdit=true
allowEdit=false
; field mappings needed to calculate variable channel record length: blockSize / (tvChannels + radioChannels)
numTvChannels=0x2C
numRadioChannels=0x30
channelBlockSize=0x3C
[mgr_chan_s_fta.db_entry]
offProgNr=0
@@ -32,15 +41,16 @@ offTsid=460
offSid=464
offOnid=466
[mgr_chan_s_pkg.db]
lenHeader=64
lenEntry=480
lenFooter=12
offFooterChecksum=8
reorderRecordsByChannelNumber=false
allowEdit=true
allowEdit=false
; field mappings needed to calculate variable channel record length: blockSize / (tvChannels+radioChannels)
numTvChannels=0x2C
numRadioChannels=0x30
channelBlockSize=0x3C
[mgr_chan_s_pkg.db_entry]
offProgNr=0
@@ -55,15 +65,17 @@ offTsid=460
offSid=464
offOnid=466
[mgr_chan_dvbt.db]
lenHeader=64
lenEntry=472
lenFooter=12
offFooterChecksum=8
reorderRecordsByChannelNumber=false
allowEdit=true
allowEdit=false
; field mappings needed to calculate variable channel record length: blockSize / (tvChannels + radioChannels)
numTvChannels=0x2C
numRadioChannels=0x30
channelBlockSize=0x3C
[mgr_chan_dvbt.db_entry]
offProgNr=0
@@ -80,15 +92,17 @@ offSymbolRate=462
offSid=464
offOnid=466
[mgr_chan_dvbc.db]
lenHeader=64
lenEntry=472
lenFooter=12
offFooterChecksum=8
reorderRecordsByChannelNumber=false
allowEdit=true
allowEdit=false
; field mappings needed to calculate variable channel record length: blockSize / (tvChannels + radioChannels)
numTvChannels=0x2C
numRadioChannels=0x30
channelBlockSize=0x3C
[mgr_chan_dvbc.db_entry]
offProgNr=0
@@ -105,7 +119,130 @@ offSymbolRate=462
offSid=464
offOnid=466
; ---------------------------------------------
; variant with 476 bytes per entry in mgr_chan_s_fta.db
[flash_dtvinfo_s_fta:476]
offChannelTransponderTable=4
numChannelTransponderTable=4000
lenChannelTransponder=4
offTransponderTable=0x5f2c
numTransponderTable=1100
lenTransponder=31
offSatellites=0xe460
offChannelBlock=0x10000
lenChannelBlock=0x10000
; struct Transponder
transponderId=0
freq=4,21
symbolRate=6
onid=10
tsid=12
nid=14
; struct ChannelBlock
offChannel=4
numChannel=734
lenChannel=89
; struct Channel
channelId=0
flags=3
sid=8
pcrPid=10
vpid=12
progNr=22
apid=31
[flash_dtvinfo_s_pkg:476]
offChannelTransponderTable=4
numChannelTransponderTable=2000
lenChannelTransponder=4
offTransponderTable=0x287C
numTransponderTable=550
lenTransponder=31
offSatellites=0x6B16
offChannelBlock=0x10000
lenChannelBlock=0x10000
numChannel=734
; struct transponder
transponderId=0
freq=4,21
symbolRate=6
onid=10
tsid=12
; relative to ChannelBlock
offChannel=4
lenChannel=89
; channels within the ChannelBlock
channelId=0
flags=3
sid=8
pcrPid=10
vpid=12
progNr=22
apid=31
;-------------------------------------------------------------------------
; variant with 480 bytes per entry in mgr_chan_s_fta.db
[flash_dtvinfo_s_fta:480]
offChannelTransponderTable=4
numChannelTransponderTable=4000
lenChannelTransponder=4
offTransponderTable=0x4f14
numTransponderTable=1100
lenTransponder=36
offSatellites=0xe9c4
offChannelBlock=0x10000
lenChannelBlock=0x10000
reorderRecordsByChannelNumber=false
; struct Transponder
transponderId=0
freq=4,21
symbolRate=6
onid=10
tsid=12
nid=14
; struct ChannelBlock
offChannel=4
numChannel=703
lenChannel=93
; struct Channel
channelId=0
flags=3
sid=8
pcrPid=10
vpid=12
progNr=24
apid=34
[flash_dtvinfo_s_pkg:480]
offChannelTransponderTable=4
numChannelTransponderTable=2000
lenChannelTransponder=4
offTransponderTable=0x2804
numTransponderTable=550
lenTransponder=36
offSatellites=0x755C
offChannelBlock=0x10000
lenChannelBlock=0x10000
; struct transponder
transponderId=0
freq=4,21
symbolRate=6
onid=10
tsid=12
; struct ChannelBlock
offChannel=4
numChannel=703
lenChannel=93
; struct Channel
channelId=0
flags=3
sid=8
pcrPid=10
vpid=12
progNr=24
apid=34
############################################################################
# Repair\ChannelList\chanLst.bin version 1.x with AntennaDigSrvTable, CableDigSrvTable and service.dat (for DVB-S) - should be stable

View File

@@ -36,5 +36,11 @@ namespace ChanSort.Loader.Philips
/// </summary>
public int Id; // links entries in the ChannelMap45/*Db.bin files with the entries in the tv.db channels table
/// <summary>
/// used in the mgr_db / FLASH format to hold the absolute offset of the channel in the FLASH file
/// </summary>
public int FlashFileOffset;
public int DbFileOffset;
}
}

View File

@@ -9,11 +9,27 @@ using ChanSort.Api;
namespace ChanSort.Loader.Philips
{
/*
* This serializer is used for the channel list format with a Repair\ folder containing files like channel_db_ver.db, mgr_chan_s_fta.db, ...
* The .db files are proprietary binary files, not SQLite databases.
* Due to lack of sample lists, the analog and DVB-C files have not been reverse engineered yet.
* The data offsets are defined in ChanSort.Loader.Philips.ini
*/
This serializer is used for the channel list format with a Repair\ folder containing files like channel_db_ver.db, mgr_chan_s_fta.db, FLASH_DTVINFO_S_FTA, ...
The .db files are proprietary binary files, not SQLite databases.
There are several variations of this file format using different record sizes, record counts and data offsets. Currently 2 different formats are known.
They are identified by their channel record size in the mgr_chan_s_fta.db file, currently supporting 476 and 480. This decides the config that will be used for other files too.
Lots of data is duplicated between FLASH_* and *.db files and must be updated in both.
It seems that the .db file contains valid channels and the index of a channel record in this file is used as an index in the ChannelIdMappingTable of the FLASH file
where IDs for for the channel and transponder are stored, which are used to find channels and transponders in the FLASH file.
The data records in the FLASH file are then looked up by their IDs, not by index.
A full satellite scan usually populates the mgr_chan_s_fta.db + FLASH_DTVINFO_S_FTA files. A preset list fills mgr_chan_s_pkg.db and FLASH_DTVINFO_S_PKG.
However there is also an example where a preset list uses mgr_chan_s_pkg.db + FLASH_DTVINFO_S_FTA.
A preset list has a .db file where records are ordered by the desired channel order. The corresponding FLASH file however has a different channel record order
and the ID-mapping table is used to resolve references.
Due to lack of sample lists, the analog and DVB-T files have not been reverse engineered yet, but DVB-T is supported experimentally assuming it is identical to DVB-C
The data offsets are defined in ChanSort.Loader.Philips.ini
*/
class DbSerializer : SerializerBase
{
private readonly IniFile ini;
@@ -22,9 +38,15 @@ namespace ChanSort.Loader.Philips
private readonly ChannelList dvbcChannels = new ChannelList(SignalSource.DvbT, "DVB-C");
private readonly ChannelList dvbsFtaChannels = new ChannelList(SignalSource.DvbS | SignalSource.Provider0, "DVB-S FTA");
private readonly ChannelList dvbsPkgChannels = new ChannelList(SignalSource.DvbS | SignalSource.Provider1, "DVB-S Preset");
private readonly Dictionary<ChannelList, string> fileByList = new();
private readonly Dictionary<ChannelList, string> dbFileByList = new();
private readonly Dictionary<ChannelList, Tuple<string, int>> flashFileByList = new();
private int dvbtChannelRecordLength;
private int dvbcChannelRecordLength;
private int ftaChannelRecordLength;
private int pkgChannelRecordLength;
#region ctor()
public DbSerializer(string inputFile) : base(inputFile)
{
this.Features.MaxFavoriteLists = 1;
@@ -43,28 +65,39 @@ namespace ChanSort.Loader.Philips
{
list.VisibleColumnFieldNames = new List<string>
{
"Position", //nameof(Channel.NewProgramNr),
"OldPosition", // nameof(Channel.OldProgramNr),
// data in *.db files
"Position", // new progNr in main or fav list
"OldPosition", // old progNr in main or fav list,
nameof(Channel.Name),
nameof(Channel.Favorites),
nameof(Channel.FreqInMhz),
nameof(Channel.SymbolRate),
nameof(Channel.TransportStreamId),
nameof(Channel.OriginalNetworkId),
nameof(Channel.ServiceId)
nameof(Channel.ServiceId),
// additional data in FLASH_ files only
nameof(ChannelInfo.PcrPid),
nameof(ChannelInfo.VideoPid),
nameof(ChannelInfo.AudioPid),
nameof(ChannelInfo.ServiceTypeName)
};
}
}
#endregion
#region Load()
public override void Load()
{
bool validList = false;
foreach (var file in Directory.GetFiles(Path.GetDirectoryName(this.FileName) ?? ""))
var dir = Path.GetDirectoryName(this.FileName) ?? ".";
// must process *.db files first, then the FLASH_ files
var files = Directory.GetFiles(dir, "*.db").Union(Directory.GetFiles(dir, "FLASH_*"));
foreach (var file in files)
{
var lc = Path.GetFileName(file).ToLowerInvariant();
switch (lc)
var lowercaseFileName = Path.GetFileName(file).ToLowerInvariant();
switch (lowercaseFileName)
{
case "atv_channel_t.db":
// TODO: no sample file yet that contains analog terrestrial channels
@@ -76,27 +109,47 @@ namespace ChanSort.Loader.Philips
LoadVersion(file);
break;
case "mgr_chan_dvbt.db":
LoadDvb(file, lc, dvbtChannels);
LoadDvb(file, lowercaseFileName, dvbtChannels, ref dvbtChannelRecordLength);
validList = true;
break;
case "mgr_chan_dvbc.db":
// no sample file with DVB-C data yet, so this here is a guess based on DVB-T
LoadDvb(file, lc, dvbcChannels);
LoadDvb(file, lowercaseFileName, dvbcChannels, ref dvbcChannelRecordLength);
validList = true;
break;
case "mgr_chan_s_fta.db":
LoadDvb(file, lc, dvbsFtaChannels);
LoadDvb(file, lowercaseFileName, dvbsFtaChannels, ref ftaChannelRecordLength);
validList = true;
break;
case "mgr_chan_s_pkg.db":
LoadDvb(file, lc, dvbsPkgChannels);
LoadDvb(file, lowercaseFileName, dvbsPkgChannels, ref pkgChannelRecordLength);
validList = true;
break;
case "flash_dtvinfo_s_fta":
if (dvbsFtaChannels.Count == 0 && dvbsPkgChannels.Count > 0)
LoadFlash(file, lowercaseFileName, dvbsPkgChannels, pkgChannelRecordLength); // weird case where _pkg.db must be combined with FLASH_FTA
else
LoadFlash(file, lowercaseFileName, dvbsFtaChannels, ftaChannelRecordLength);
break;
case "flash_dtvinfo_s_pkg":
if (!(dvbsFtaChannels.Count == 0 && dvbsPkgChannels.Count > 0))
LoadFlash(file, lowercaseFileName, dvbsPkgChannels, pkgChannelRecordLength);
break;
}
}
if (!validList)
throw new FileLoadException(this.FileName + " is not a supported Philips Repair/channel_db_ver.db channel list");
foreach (var channelList in this.DataRoot.ChannelLists)
{
foreach (var channelInfo in channelList.Channels)
{
var ch = (Channel)channelInfo;
if (ch.FlashFileOffset == 0)
this.DataRoot.Warnings.AppendLine($"Channel with index {ch.RecordIndex:d4} in .db file ({ch.OldProgramNr} - {ch.Name}) has no entry in FLASH files");
}
}
}
#endregion
@@ -132,22 +185,26 @@ namespace ChanSort.Loader.Philips
#endregion
#region LoadDvbs()
private void LoadDvb(string path, string sectionName, ChannelList list)
private void LoadDvb(string path, string sectionName, ChannelList list, ref int channelRecordLength)
{
var signalSource = list.SignalSource;
var data = File.ReadAllBytes(path);
var sec = ini.GetSection(sectionName);
var lenHeader = sec.GetInt("lenHeader");
var lenFooter = sec.GetInt("lenFooter");
var lenEntry = sec.GetInt("lenEntry");
var offFooterChecksum = sec.GetInt("offFooterChecksum");
var records = (data.Length - lenHeader - lenFooter) / lenEntry;
if (records <= 0)
if (!GetValuesFromDvbFileHeader(sec, data, out var lenHeader, out var lenEntry, out var records, out var offChecksum))
{
this.DataRoot.Warnings.AppendLine($"{sectionName} was not loaded because data record size could not be determined");
return;
}
list.ReadOnly = !sec.GetBool("allowEdit", false);
var expectedChecksum = BitConverter.ToUInt16(data, offChecksum);
var actualChecksum = (UInt16)CalcChecksum(data, 0, offChecksum);
if (actualChecksum != expectedChecksum)
throw new FileLoadException($"File {path} contains invalid checksum. Expected {expectedChecksum:x4} but calculated {actualChecksum:x4}");
channelRecordLength = lenEntry;
list.ReadOnly = !sec.GetBool("allowEdit");
var mapping = new DataMapping(this.ini.GetSection(sectionName + "_entry"));
sec = ini.GetSection("mgr_chan_s_fta.db_entry");
@@ -157,13 +214,13 @@ namespace ChanSort.Loader.Philips
mapping.SetDataPtr(data, lenHeader + i * lenEntry);
var oldProgNr = mapping.GetWord("offProgNr");
// name can be either an 8-bit ASCII with unspecified encoding or big-endian 16-bit unicode
// name is normally in 8-bit ASCII with unspecified encoding, but there has been an instance where some names in the file were in 16 bit big-endian unicode
var off = mapping.BaseOffset + mapping.GetOffsets("offName")[0];
var name = data[off + 0] == 0 ? (data[off + 1] == 0 ? "" : Encoding.BigEndianUnicode.GetString(data, off, lenName)) : DefaultEncoding.GetString(data, off, lenName);
name = name.TrimEnd('\0');
var ch = new Channel(signalSource, i, oldProgNr, name);
ch.RecordOrder = i;
ch.DbFileOffset = mapping.BaseOffset;
var favPos = mapping.GetWord("offFav");
if (favPos > 0)
ch.SetOldPosition(1, favPos);
@@ -179,24 +236,219 @@ namespace ChanSort.Loader.Philips
this.DataRoot.AddChannel(list, ch);
}
var offChecksum = data.Length - lenFooter + offFooterChecksum;
var expectedChecksum = BitConverter.ToUInt16(data, offChecksum);
var actualChecksum = CalcChecksum(data, 0, offChecksum);
if (actualChecksum != expectedChecksum)
throw new FileLoadException($"File {path} contains invalid checksum. Expected {expectedChecksum:x4} but calculated {actualChecksum:x4}");
this.fileByList[list] = path;
this.dbFileByList[list] = path;
}
#endregion
#region GetValuesFromDvbFileHeader()
private bool GetValuesFromDvbFileHeader(IniFile.Section sec, byte[] data, out int lenHeader, out int lenEntry, out int numEntries, out int checksumOffset)
{
lenHeader = 0;
lenEntry = 0;
numEntries = 0;
checksumOffset = -1;
// _fta.db or _pkg.db may contain only a tiny header without the required header fields or actual data
if (data.Length < sec.GetInt("channelBlockSize") + 4)
return false;
lenHeader = sec.GetInt("lenHeader");
var lenFooter = sec.GetInt("lenFooter");
// the size of the channel entry can vary, so it must be calculated (seen 476 and 480 so far in mgr_chan_s_pkg.db and 472 in mgr_chan_dvbt.db)
var mapping = new DataMapping(sec, data);
var blockSize = mapping.GetDword("channelBlockSize");
numEntries = mapping.GetWord("numTvChannels") + mapping.GetWord("numRadioChannels");
if (numEntries == 0)
{
lenEntry = sec.GetInt("lenEntry");
if (lenEntry == 0)
return false;
numEntries = (int)(blockSize / lenEntry);
}
else
{
lenEntry = (int)(blockSize / numEntries);
}
if (blockSize % numEntries != 0)
return false;
var offFooterChecksum = sec.GetInt("offFooterChecksum");
checksumOffset = data.Length - lenFooter + offFooterChecksum;
return true;
}
#endregion
#region LoadFlash()
private void LoadFlash(string path, string sectionName, ChannelList channelList, int dbChannelRecordLength)
{
if (dbChannelRecordLength == 0) // if the .db file wasn't read, ignore the FLASH file
return;
var data = File.ReadAllBytes(path);
if (data.Length < 4)
return;
var expectedChecksum = BitConverter.ToUInt32(data, data.Length - 4);
var actualChecksum = CalcChecksum(data, 0, data.Length - 4);
if (actualChecksum != expectedChecksum)
throw new FileLoadException($"File {path} contains invalid checksum. Expected {expectedChecksum:x8} but calculated {actualChecksum:x8}");
var settings = this.ini.GetSection(sectionName + ":" + dbChannelRecordLength, true);
var mapping = new DataMapping(settings, data);
ReadTransponderFromFlash(settings, mapping);
var idMapping = ReadChannelIdMappingFromFlash(settings, data);
var filename = Path.GetFileName(path);
ReadChannelBlocksFromFlash(filename, channelList, settings, data, mapping, idMapping);
this.flashFileByList[channelList] = Tuple.Create(path, dbChannelRecordLength);
}
#endregion
#region ReadTransponderFromFlash()
private void ReadTransponderFromFlash(IniFile.Section settings, DataMapping mapping)
{
// read transponders (mostly for validating data that was already read from .db file)
var off = settings.GetInt("offTransponderTable");
var num = settings.GetInt("numTransponderTable");
var len = settings.GetInt("lenTransponder");
mapping.BaseOffset = off;
for (int i = 0; i < num; i++)
{
var id = mapping.GetWord("transponderId");
if (id != 0xFFFF)
{
var tp = new Transponder(id);
tp.FrequencyInMhz = mapping.GetWord("freq");
tp.SymbolRate = mapping.GetWord("symbolRate");
tp.OriginalNetworkId = mapping.GetWord("onid");
tp.TransportStreamId = mapping.GetWord("tsid");
this.DataRoot.AddTransponder(null, tp);
}
mapping.BaseOffset += len;
}
}
#endregion
#region ReadChannelIdMappingFromFlash()
private static Dictionary<int, IndexToIdMapping> ReadChannelIdMappingFromFlash(IniFile.Section settings, byte[] data)
{
// read a table that maps the channel index from the .db file to a channel ID and transponder ID in the FLASH file
// and convert this into a reverse-lookup that allows to map a channel ID to a channel index and transponder ID
var idMapping = new Dictionary<int, IndexToIdMapping>();
var off = settings.GetInt("offChannelTransponderTable");
var num = settings.GetInt("numChannelTransponderTable");
for (int i = 0; i < num; i++)
{
var chanId = BitConverter.ToUInt16(data, off + i * 4 + 2);
if (chanId == 0xFFFF) continue;
var transpId = BitConverter.ToUInt16(data, off + i * 4 + 0);
var flags = transpId & 0x1F;
transpId >>= 5;
idMapping[chanId] = new IndexToIdMapping(i, transpId, flags);
}
return idMapping;
}
#endregion
#region ReadChannelBlocksFromFlash()
private void ReadChannelBlocksFromFlash(string filename, ChannelList channelList, IniFile.Section settings, byte[] data, DataMapping mapping, Dictionary<int,IndexToIdMapping> idMapping)
{
// channel data is spread across multiple 64KB data blocks which all have a small header, then 734 channel records and a footer
var off = settings.GetInt("offChannelBlock");
var len = settings.GetInt("lenChannelBlock");
for (int block = 0; off + (block + 1) * len <= data.Length; block++)
{
mapping.BaseOffset = off + block * len + settings.GetInt("offChannel");
ReadChannelsFromFlashChannelBlock(filename, channelList, mapping, idMapping, block);
}
}
#endregion
#region ReadChannelsFromFlashChannelBlock
private void ReadChannelsFromFlashChannelBlock(string filename, ChannelList channelList, DataMapping mapping, Dictionary<int,IndexToIdMapping> channelIdMapping, int block)
{
var settings = mapping.Settings;
var numChannelsInBlock = settings.GetInt("numChannel");
var lenChannel = settings.GetInt("lenChannel");
mapping.BaseOffset -= lenChannel;
for (int i = 0; i < numChannelsInBlock; i++)
{
mapping.BaseOffset += lenChannel;
var id = mapping.GetWord("channelId");
var flags = mapping.GetByte("flags"); // in the sample files this is always 63 except for "dead" records, where it is 0
if (id == 0xFFFF || flags == 0)
continue;
if (!channelIdMapping.TryGetValue(id, out var idMapping))
{
this.DataRoot.Warnings.AppendLine($"Channel record in {filename}, block {block}, index {i:d4} has no entry in the ID mapping table");
continue;
}
var index = idMapping.ChannelIndex;
if (idMapping.ChannelIndex >= channelList.Channels.Count)
{
this.DataRoot.Warnings.AppendLine($"Channel record in {filename}, block {block}, index {i:d4} refers to non-existing channel index {index} in the .db file");
continue;
}
var ch = (Channel)channelList.Channels[idMapping.ChannelIndex];
ch.FlashFileOffset = mapping.BaseOffset;
var hasDiff = false;
var sid = mapping.GetWord("sid");
var progNr = (mapping.GetWord("progNr") & 0x3FFF);
hasDiff |= ch.ServiceId != sid;
ch.PcrPid = mapping.GetWord("pcrPid");
ch.VideoPid = mapping.GetWord("vpid");
ch.AudioPid = mapping.GetWord("apid");
hasDiff |= ch.OldProgramNr != progNr;
var isRadio = (idMapping.Flags & 0x08) != 0;
if (isRadio)
{
ch.SignalSource |= SignalSource.Radio;
ch.ServiceTypeName = "Radio";
}
else
{
ch.SignalSource |= SignalSource.Tv;
ch.ServiceTypeName = "TV";
}
if (!this.DataRoot.Transponder.TryGetValue(idMapping.TransponderId, out var tp))
this.DataRoot.Warnings.AppendLine($"Channel record in {filename}, block {block}, index {i:d4}: could not find transponder record with id {idMapping.TransponderId}");
else
{
hasDiff |= ch.OriginalNetworkId != tp.OriginalNetworkId;
hasDiff |= ch.TransportStreamId != tp.TransportStreamId;
hasDiff |= ch.FreqInMhz != tp.FrequencyInMhz;
}
if (hasDiff)
this.DataRoot.Warnings.AppendLine($"Channel record in {filename}, block {block}, index {i:d4} does not match data in .db file: " +
$"ProgNr={progNr}|{ch.OldProgramNr}, onid={tp?.OriginalNetworkId}|{ch.OriginalNetworkId}, tsid={tp?.TransportStreamId}|{ch.TransportStreamId}, " +
$"sid={sid}|{ch.ServiceId}, freq={tp?.FrequencyInMhz}|{ch.FreqInMhz}");
}
}
#endregion
#region CalcChecksum()
/// <summary>
/// The checksum is the 16-bit sum over the byte-values in the file data from offset 0 to right before the checksum field
/// The checksum is the 32-bit sum over the byte-values in the file data from offset 0 to right before the checksum field
/// </summary>
private ushort CalcChecksum(byte[] data, int start, int len)
private uint CalcChecksum(byte[] data, int start, int len)
{
ushort checksum = 0;
uint checksum = 0;
while (len > 0)
{
checksum += data[start++];
@@ -207,76 +459,109 @@ namespace ChanSort.Loader.Philips
}
#endregion
public override IEnumerable<string> GetDataFilePaths() => this.fileByList.Values.ToList();
public override IEnumerable<string> GetDataFilePaths() => this.dbFileByList.Values.Union(this.flashFileByList.Values.Select(tup => tup.Item1));
#region Save()
public override void Save(string tvOutputFile)
{
foreach (var listAndFile in this.fileByList)
// update *.db files
foreach (var listAndFile in this.dbFileByList)
{
var list = listAndFile.Key;
var file = listAndFile.Value;
var secName = Path.GetFileName(file).ToLowerInvariant();
SaveDvb(file, secName, list);
}
}
// update FLASH_* files
foreach (var listAndFile in this.flashFileByList)
{
var list = listAndFile.Key;
var file = listAndFile.Value.Item1;
var dbChannelRecordSize = listAndFile.Value.Item2;
var secName = Path.GetFileName(file).ToLowerInvariant();
SaveFlash(file, secName, list, dbChannelRecordSize);
}
}
#endregion
#region SaveDvb()
private void SaveDvb(string file, string secName, ChannelList list)
{
var data = File.ReadAllBytes(file);
var sec = ini.GetSection(secName);
var lenHeader = sec.GetInt("lenHeader");
var lenFooter = sec.GetInt("lenFooter");
var lenEntry = sec.GetInt("lenEntry");
var offFooterChecksum = sec.GetInt("offFooterChecksum");
if (!GetValuesFromDvbFileHeader(sec, data, out var lenHeader, out var lenEntry, out _, out var offChecksum))
return;
var mapping = new DataMapping(ini.GetSection(secName + "_entry"));
if (sec.GetBool("reorderRecordsByChannelNumber"))
foreach (var chan in list.Channels)
{
// physically reorder channels
var newData = new byte[data.Length];
Array.Copy(data, newData, lenHeader);
var off = lenHeader + lenEntry * list.Channels.Count;
Array.Copy(data, off, newData, off, lenFooter);
int i = 0;
foreach (var ch in list.Channels.OrderBy(c => c.NewProgramNr))
{
off = lenHeader + i * lenEntry;
Array.Copy(data, lenHeader + ch.RecordOrder * lenEntry, newData, off, lenEntry);
mapping.SetDataPtr(newData, off);
mapping.SetWord("offProgNr", ch.NewProgramNr);
//mapping.SetWord("offPrevProgNr", ch.NewProgramNr - 1);
mapping.SetWord("offFav", Math.Max(0, ch.GetPosition(1)));
ch.RecordOrder = i;
++i;
}
data = newData;
}
else
{
// update channel data
foreach (var ch in list.Channels)
{
mapping.SetDataPtr(data, lenHeader + ch.RecordOrder * lenEntry);
mapping.SetWord("offProgNr", ch.NewProgramNr);
//mapping.SetWord("offPrevProgNr", ch.NewProgramNr - 1);
mapping.SetWord("offFav", Math.Max(0, ch.GetPosition(1)));
}
if (chan is not Channel ch)
continue;
mapping.SetDataPtr(data, lenHeader + (int)ch.RecordIndex * lenEntry);
mapping.SetWord("offProgNr", ch.NewProgramNr);
mapping.SetWord("offFav", Math.Max(0, ch.GetPosition(1)));
}
// update checksum
var offChecksum = data.Length - lenFooter + offFooterChecksum;
// update checksum (only 16 bits are stored)
var checksum = CalcChecksum(data, 0, offChecksum);
data[offChecksum] = (byte)checksum;
data[offChecksum + 0] = (byte)checksum;
data[offChecksum + 1] = (byte)(checksum >> 8);
File.WriteAllBytes(file, data);
}
#endregion
#region SaveFlash()
private void SaveFlash(string file, string secName, ChannelList list, int dbChannelRecordLength)
{
var data = File.ReadAllBytes(file);
if (data.Length == 0)
return;
var sec = ini.GetSection(secName + ":" + dbChannelRecordLength, true);
var mapping = new DataMapping(sec, data);
// in-place update of channel data
foreach (var chan in list.Channels)
{
if (chan is not Channel ch)
continue; // skip proxy channels
if (ch.FlashFileOffset == 0)
continue;
mapping.BaseOffset = ch.FlashFileOffset;
mapping.SetWord("progNr", ch.NewProgramNr | (mapping.GetWord("progNr") & ~0x3FFF));
//mapping.SetWord("fav", Math.Max(0, ch.GetPosition(1)));
}
// update checksum (full 32 bit)
var offChecksum = data.Length - 4;
var checksum = CalcChecksum(data, 0, offChecksum);
data[offChecksum + 0] = (byte)checksum;
data[offChecksum + 1] = (byte)(checksum >> 8);
data[offChecksum + 2] = (byte)(checksum >> 16);
data[offChecksum + 3] = (byte)(checksum >> 24);
File.WriteAllBytes(file, data);
}
#endregion
#region IndexToIdMapping
class IndexToIdMapping
{
public readonly int ChannelIndex;
public readonly int TransponderId;
public readonly int Flags;
public IndexToIdMapping(int channelIndex, int transponderId, int flags)
{
this.ChannelIndex = channelIndex;
this.TransponderId = transponderId;
this.Flags = flags;
}
}
#endregion
}
}

View File

@@ -19,6 +19,7 @@ namespace ChanSort.Loader.Philips
It uses the "oldpresetnumber" from the XML to lookup the channel from the .BIN file and then apply the new "presetnumber".
The .BIN file itself is compressed with some unknown cl_Zip compression / archive format and can't be edited with ChanSort.
Deleting a channel is not possible by modifiying the .xml file. Omitting a channel only results in duplicate numbers with the TV still showing the missing channels at their old numbers.
The channel nodes in the .XML must be kept in the original order with "oldpresetnumber" keeping the original value and only "presetnumber" being updated.
<Channel>
<Setup oldpresetnumber="1" presetnumber="1" name="Das Erste" ></Setup>
@@ -183,7 +184,7 @@ namespace ChanSort.Loader.Philips
if (File.Exists(xmlPath))
{
try { File.SetAttributes(xmlPath, FileAttributes.Archive);}
catch { /**/ }
catch(Exception ex) { this.logMessages.AppendLine("Failed to reset file attributes for " + xmlPath + ": " + ex.Message); }
this.FileName = xmlPath;
var baseName = Path.GetFileNameWithoutExtension(xmlPath).ToUpperInvariant();
if (baseName.StartsWith("CM_"))

View File

@@ -62,14 +62,14 @@ namespace ChanSort.Loader.Samsung.Scm
this.OldProgramNr = (short)data.GetWord(_ProgramNr);
this.Name = data.GetString(_Name, data.Settings.GetInt("lenName"));
this.Favorites = this.ParseRawFavorites();
this.Lock = data.GetFlag(_Lock);
this.Lock = data.GetFlag(_Lock, false);
int hiddenPrimary = data.GetByte(_Hidden);
if (hiddenPrimary == 255)
this.Hidden = data.GetByte(_HiddenAlt) != 0;
else
this.Hidden = hiddenPrimary != 0;
this.Skip = data.GetFlag(_Skip);
this.Encrypted = data.GetFlag(_Encrypted);
this.Skip = data.GetFlag(_Skip, false);
this.Encrypted = data.GetFlag(_Encrypted, false);
this.IsDeleted = data.GetFlag(_Deleted, false) || !data.GetFlag(_IsActive, true);
this.AddDebug(data.Data, data.BaseOffset + 25, 3);
}

View File

@@ -390,7 +390,7 @@ namespace ChanSort.Loader.Samsung.Scm
int count = data.Length / c.avbtFineTuneLength;
for (int i = 0; i < count; i++)
{
bool isCable = mapping.GetFlag("offIsCable", "maskIsCable"); // HACK: this is just a guess
bool isCable = mapping.GetFlag("IsCable",false); // HACK: this is just a guess
int slot = mapping.GetWord("offSlotNr");
float freq = mapping.GetFloat("offFrequency");
var dict = isCable ? avbcFrequency : avbtFrequency;

View File

@@ -12,13 +12,12 @@ namespace ChanSort.Ui
private static readonly string ConfigFilePath;
#region class ColumnInfo
public class ColumnInfo
{
[XmlAttribute("name")] public string Name { get; set; }
[XmlAttribute("width")] public int Width { get; set; }
[XmlAttribute("visible")]//[Obsolete("For XML serialization only. Use Visible instead")]
[XmlAttribute("visible")]//[Obsolete("For XML serialization only. Use Visible instead")] // won't be serialized/deserialized when marked [Obsolete]
public bool VisibleXml
{
get => this.Visible ?? true;
@@ -27,9 +26,7 @@ namespace ChanSort.Ui
[XmlIgnore]
public bool? Visible { get; set; }
}
#endregion
@@ -76,9 +73,6 @@ namespace ChanSort.Ui
public bool CheckForUpdates { get; set; } = true;
public int FontSizeDelta { get; set; } = 1;
// deprecated LeftGridLayout and RightGridLayout because they don't preserve order of invisible columns
//public string LeftGridLayout { get; set; }
//public string RightGridLayout { get; set; }
[XmlArrayItem("Column")]
public List<ColumnInfo> LeftColumns { get; set; } = new();

View File

@@ -1,16 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using ChanSort.Api;
using DevExpress.Data;
using DevExpress.XtraGrid.Columns;
using DevExpress.XtraGrid.Views.Grid;
namespace ChanSort
{
@@ -19,14 +9,16 @@ namespace ChanSort
private List<GridColumn> columnOrder = new();
private int ignoreEvents;
#region StoreDefaultColumnOrder()
private void StoreDefaultColumnOrder()
{
// for this to work, the columns in absolute index order must represent the intended visible order
this.columnOrder.Clear();
this.columnOrder.AddRange(this.Columns.OrderBy(c => c.AbsoluteIndex));
}
#endregion
#region SetColumnOrder
#region SetColumnOrder()
public void SetColumnOrder(IEnumerable<string> names, bool updateVisibleIndex = true)
{
// must handle situations where new columns were added to the program, which are not included in the config
@@ -85,14 +77,19 @@ namespace ChanSort
}
#endregion
#region GetColumnOrder()
public List<GridColumn> GetColumnOrder() => this.columnOrder.ToList();
#endregion
#region RaiseColumnPositionChanged()
protected override void RaiseColumnPositionChanged(GridColumn column)
{
this.OnColumnPositionChanged(column);
base.RaiseColumnPositionChanged(column);
}
#endregion
#region OnColumnPositionChanged()
private void OnColumnPositionChanged(GridColumn col)
{
// internal reordering should be ignored
@@ -117,7 +114,9 @@ namespace ChanSort
}
list.Insert(i, col);
}
#endregion
#region SetColumnVisiblity()
public void SetColumnVisibility(GridColumn column, bool visible)
{
if (column.Visible == visible)
@@ -144,29 +143,6 @@ namespace ChanSort
// fallback
column.VisibleIndex = this.VisibleColumns.Count;
}
//protected override void SetColumnVisibleIndex(GridColumn column, int newValue)
//{
// int oldVisIndex = column.VisibleIndex;
// int newIdx = newValue > column.VisibleIndex ? newValue - 1 : newValue;
// base.SetColumnVisibleIndex(column, newValue);
// if (newIdx < 0 || oldVisIndex == newIdx)
// {
// // hide or no change: keep as-is
// }
// else if (newIdx >= 0)
// {
// // move
// columnOrder.Remove(column);
// if (newIdx == 0)
// columnOrder.Insert(0, column);
// else
// {
// var afterCol = this.VisibleColumns[column.VisibleIndex - 1];
// columnOrder.Insert(columnOrder.IndexOf(afterCol) + 1, column);
// }
// }
//}
#endregion
}
}

View File

@@ -15,36 +15,36 @@ struct SHeader
struct SChannel_fta
{
uint32 curProgNr;
uint32 u1;
uint8 u2[8];
uint32 favNr;
uint32 curProgNr;
uint32 u1;
uint8 u2[8];
uint32 favNr;
char chName1[200];
uint16 u3;
uint8 u3b[208];
uint8 u3c[2];
uint16 u3d;
uint8 u4[10];
uint32 freqInMhz1;
//uint16 u5;
uint16 u6;
uint16 symRate;
uint32 oldProgNr;
uint32 channelIndex;
uint16 tsid;
uint16 u7;
uint16 sid;
uint16 onid;
uint16 freqInMhz2;
uint8 padding[6];
uint16 u3;
uint8 u3b[208];
uint8 u3c[2];
uint16 u3d;
uint8 u4[10];
uint32 freqInMhz1;
//uint16 u5;
uint16 u6;
uint16 symRate;
uint32 oldProgNr;
uint32 channelIndex;
uint16 tsid;
uint16 u7;
uint16 sid;
uint16 onid;
uint16 freqInMhz2;
uint8 padding[6];
};
struct SFooter
{
uint32 numDataBlocks;
uint32 numDataBlockBytes;
uint16 bytesumFrom0;
uint16 u_zero;
uint32 numDataBlocks;
uint32 numDataBlockBytes;
uint16 bytesumFrom0;
uint16 u_zero;
};
#pragma script("get_doc_size.js")
@@ -54,95 +54,127 @@ public struct Philips_mgr_chan_s_fta
var docSize = GetDocumentSize();
char filename[32];
SHeader header;
SHeader header;
var recordCount = header.channelBlockSize / sizeof(SChannel_fta);
var recordCount = header.channelBlockSize / sizeof(SChannel_fta);
SChannel_fta channels[recordCount];
SFooter footer;
SFooter footer;
};
//#########################################################
struct SChannel_pkg
struct SChannel_pkg_476
{
uint32 curProgNr;
uint32 u1;
uint8 u2[8];
uint32 favNr;
uint32 curProgNr;
uint32 u1;
uint8 u2[8];
uint32 favNr;
char chName1[200];
uint16 u3;
uint8 u3b[208];
uint8 u3c[2];
uint16 u3d;
uint8 u4[10];
uint32 freqInMhz1;
//uint16 u5;
uint16 u6;
uint16 symRate;
uint32 oldProgNr;
uint32 channelIndex;
uint16 tsid;
uint16 u7;
uint16 sid;
uint16 onid;
uint16 freqInMhz2;
uint8 padding[10];
uint16 u3;
uint8 u3b[208];
uint8 u3c[2];
uint16 u3d;
uint8 u4[10];
uint32 freqInMhz1;
uint16 u6;
uint16 symRate;
uint32 oldProgNr;
uint32 channelIndex;
uint16 tsid;
uint16 u7;
uint16 sid;
uint16 onid;
uint16 freqInMhz2;
uint8 padding[6];
// some files have this additional size of 4 bytes
//uint8 padding2[4];
};
public struct Philips_mgr_chan_s_pkg
public struct Philips_mgr_chan_s_pkg_476
{
var docSize = GetDocumentSize();
char filename[32];
SHeader header;
SHeader header;
var recordCount = header.channelBlockSize / sizeof(SChannel_pkg_476);
SChannel_pkg_476 channels[recordCount];
SFooter footer;
};
var recordCount = header.channelBlockSize / sizeof(SChannel_pkg);
SChannel_pkg channels[recordCount];
SFooter footer;
struct SChannel_pkg_480
{
uint32 curProgNr;
uint32 u1;
uint8 u2[8];
uint32 favNr;
char chName1[200];
uint16 u3;
uint8 u3b[208];
uint8 u3c[2];
uint16 u3d;
uint8 u4[10];
uint32 freqInMhz1;
uint16 u6;
uint16 symRate;
uint32 oldProgNr;
uint32 channelIndex;
uint16 tsid;
uint16 u7;
uint16 sid;
uint16 onid;
uint16 freqInMhz2;
uint8 padding[6];
// some files have this additional size of 4 bytes
uint8 padding2[4];
};
public struct Philips_mgr_chan_s_pkg_480
{
char filename[32];
SHeader header;
var recordCount = header.channelBlockSize / sizeof(SChannel_pkg_480);
SChannel_pkg_480 channels[recordCount];
SFooter footer;
};
//#########################################################
struct CChannel
{
uint32 curProgNr;
uint32 u1;
uint8 u2[8];
uint32 favNr;
union
{
char chName1[200];
uint32 curProgNr;
uint32 u1;
uint8 u2[8];
uint32 favNr;
union
{
char chName1[200];
#pragma byte_order (BigEndian)
big_endian wchar_t chName2[100];
big_endian wchar_t chName2[100];
#pragma byte_order ()
struct
{
uint8 zero;
wchar_t chName3[99];
uint8 zero2;
} chName4;
} chName;
uint16 u3;
uint16 u3b;
char provider[200];
uint8 u4[16];
uint32 freqInHz;
uint16 u6;
uint16 not_symRate;
uint32 oldProgNr;
uint8 u7[4];
uint32 channelIndex;
uint16 tsid;
uint16 symRate_maybe;
uint16 sid;
uint16 onid;
//uint16 freqInMhz2;
//uint16 u9;
uint32 u10;
struct
{
uint8 zero;
wchar_t chName3[99];
uint8 zero2;
} chName4;
} chName;
uint16 u3;
uint16 u3b;
char provider[200];
uint8 u4[16];
uint32 freqInHz;
uint16 u6;
uint16 not_symRate;
uint32 oldProgNr;
uint8 u7[4];
uint32 channelIndex;
uint16 tsid;
uint16 symRate_maybe;
uint16 sid;
uint16 onid;
//uint16 freqInMhz2;
//uint16 u9;
uint32 u10;
};
@@ -151,75 +183,225 @@ public struct Philips_mgr_chan_dvbt
var docSize = GetDocumentSize();
char filename[32];
SHeader header;
SHeader header;
var recordCount = header.channelBlockSize / sizeof(CChannel);
var recordCount = header.channelBlockSize / sizeof(CChannel);
CChannel channels[recordCount];
SFooter footer;
SFooter footer;
};
#pragma byte_order(LittleEndian)
//*****************************************************************************************
// FLASH files
//*****************************************************************************************
struct ProgNr
{
WORD nr : 14;
WORD flags : 2;
WORD nr : 14;
WORD flags : 2;
};
struct s_channelTransponder
{
struct
{
WORD u1 : 3;
WORD isRadio: 1;
WORD u2 : 1;
WORD transponderId : 11;
} info;
WORD channelId;
};
struct s_transponder
{
WORD id;
BYTE source_maybe;
BYTE satId_maybe;
WORD freqInMhz;
WORD symbolRate;
BYTE u2[2];
WORD onid;
WORD tsid;
WORD nid;
BYTE u3[5];
WORD freqInMhz2;
BYTE u4[8];
// optional:
BYTE u5[5];
};
struct s_satTransponder
{
WORD satId_maybe;
WORD transponderId;
};
struct s_unknown
{
WORD u1;
BYTE u2[2];
DWORD zero;
};
struct s_channel
{
WORD id;
BYTE u1[4];
BYTE u2[2];
WORD sid;
WORD pcrPid;
WORD vpid;
BYTE u3[8];
ProgNr progNr;
BYTE u3b[7];
WORD apid1_maybe;
char lang1[3];
BYTE u4[45];
WORD apid2_maybe;
char lang2[3];
BYTE u5[3];
// optional
BYTE u6[4];
};
struct s_channelBlock
{
char ddtc[4];
s_channel channels[734];
BYTE filler[0x20000-0x1ff32];
};
#define numChannelTransponderMap 5000
#define numSatTransponderMap 1090
#define numTransponder 1100
#define numSatData 4592
#define numUnknown 310*8
public struct Philips_FLASH_DTVINFO_S_FTA
{
char ddtc[4];
var off0 = current_offset;
struct
{
struct
{
WORD u1 : 3;
WORD isRadio: 1;
WORD u2 : 1;
WORD transponderIndex : 11;
} info;
WORD chanIdx;
} chanIndexInfo[(0x5f2c - off0)/4];
off0 = current_offset;
struct s_transponder
{
var off1 = current_offset;
WORD id;
WORD u1;
WORD freqInMhz;
WORD symbolRate;
BYTE u2[2];
WORD onid;
WORD tsid;
WORD nid;
BYTE u3[5];
WORD freqInMhz2;
BYTE u4[8];
} transponder[(0x10000 - off0)/sizeof(s_transponder)];
BYTE filler[4];
char ddtc2[4];
struct s_channel
{
WORD idx1;
BYTE u1[4];
BYTE u2[2];
WORD sid;
WORD pcrPid;
WORD vpid;
BYTE u3[8];
ProgNr progNr;
BYTE u3b[7];
WORD apid1_maybe;
char lang1[3];
BYTE u4[45];
WORD apid2_maybe;
char lang2[3];
BYTE u5[3];
} channel[*];
};
char ddtc[4];
s_channelTransponder channelTranponderMap[numChannelTransponderMap];
s_satTransponder satTransponderMap[numSatTransponderMap];
s_transponder transponder[numTransponder];
BYTE satData[numSatData];
s_unknown unkData[numUnknown];
s_channelBlock channelBlocks[*];
};
#undef numChannelTransponderMap
#define numChannelTransponderMap 2000
#undef numSatTransponderMap
#define numSatTransponderMap 590
#undef numTransponder
#define numTransponder 550
#undef numSatData
#define numSatData 4592
#undef numUnknown
#define numUnknown 310
public struct Philips_FLASH_DTVINFO_S_PKG
{
char ddtc[4];
s_channelTransponder channelTranponderMap[numChannelTransponderMap];
s_satTransponder satTransponderMap[numSatTransponderMap];
s_transponder transponder[numTransponder];
BYTE satData[numSatData];
var off0 = current_offset;
BYTE unk[0x10000 - off0];
s_channelBlock channelBlocks[*];
};
/////////////////////////////////////////////////////////////////
struct s_channelTransponder2
{
struct
{
WORD u1 : 3;
WORD isRadio: 1;
WORD u2 : 1;
WORD transponderId : 11;
} info;
WORD channelId;
};
struct s_transponder2
{
WORD id;
BYTE source_maybe;
BYTE satId_maybe;
WORD freqInMhz;
WORD symbolRate;
BYTE u2[2];
WORD onid;
WORD tsid;
WORD nid;
BYTE u3[5];
WORD freqInMhz2;
BYTE u4[8];
BYTE u5[5];
};
struct s_satTransponder2
{
WORD satId_maybe;
WORD transponderId;
};
struct s_unknown2
{
WORD u1;
BYTE u2[2];
DWORD zero;
};
struct s_channel2
{
WORD id;
BYTE u1[4];
BYTE u2[2];
WORD sid;
WORD pcrPid;
WORD vpid;
BYTE u3[10];
ProgNr progNr;
BYTE u3b[8];
WORD apid1_maybe;
char lang1[3];
BYTE u4[45];
WORD apid2_maybe;
char lang2[3];
BYTE u5[3];
BYTE u6[1];
};
struct s_channelBlock2
{
char ddtc[4];
s_channel2 channels[703];
BYTE filler[0x20000 - 0x1ff67];
};
#undef numChannelTransponderMap
#define numChannelTransponderMap 5000
#undef numSatTransponderMap
#define numSatTransponderMap 60
#undef numTransponder
#define numTransponder 1100
#undef numSatData
#define numSatData 3512
#undef numUnknown
#define numUnknown 2180
public struct Philips_FLASH_DTVINFO_S_FTA_for_pkg
{
char ddtc[4];
s_channelTransponder2 channelTranponderMap[numChannelTransponderMap];
s_satTransponder2 satTransponderMap[numSatTransponderMap];
s_transponder2 transponder[numTransponder];
BYTE satData[numSatData];
BYTE unknown[numUnknown];
s_channelBlock2 channelBlocks[*];
};