diff --git a/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.csproj b/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.csproj
index c5cc766..cc3e471 100644
--- a/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.csproj
+++ b/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.csproj
@@ -59,4 +59,9 @@
+
+
+ ..\..\..\..\..\Program Files\DevExpress 22.1\Components\Bin\Framework\DevExpress.PivotGrid.v22.1.Core.dll
+
+
\ No newline at end of file
diff --git a/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.ini b/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.ini
index 8353049..27a0d89 100644
--- a/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.ini
+++ b/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.ini
@@ -1,4 +1,8 @@
-[Header]
+; ========================================
+; HIS_SVL.BIN, HIS_TSL.BIN and HIS_FAV.BIN
+; ========================================
+
+[Header]
RecordSize=40
ID=0
Name=2
@@ -86,3 +90,42 @@ DisplayNumber=4
DisplayNumberLength=10
ChannelName=15
ChannelNameLength=64
+
+
+; ========================================
+; HIS_DVB.BIN
+; ========================================
+[HIS_DVB.BIN]
+HeaderSize=16
+Version=0
+NumChannelsDvbT=4
+NumChannelsDvbC=8
+NumChannelsDvbS=12
+
+[HIS_DVB.BIN_Record]
+RecordSizeDvbT=498
+RecordSizeDvbC=498
+RecordSizeDvbS=562
+Skip=21
+Lock=22
+TvRadioData=29
+Fav=30
+ServiceType=35
+PcrPid=36
+VideoPid=38
+ProgNum=40
+PmtPid=46
+ServiceId=48
+;@62: languages[3]
+AudioPid=68
+Name=286
+NameLength=50
+Provider=336
+ProviderLength=50
+Tsid=398
+Onid=400
+Frequency=408
+SymbolRate=420
+SatName=500
+SatNameLength=32
+SatOrbitalPos=548
\ No newline at end of file
diff --git a/source/ChanSort.Loader.Hisense/HisBin/HisDvbBinSerializer.cs b/source/ChanSort.Loader.Hisense/HisBin/HisDvbBinSerializer.cs
new file mode 100644
index 0000000..5bacd1b
--- /dev/null
+++ b/source/ChanSort.Loader.Hisense/HisBin/HisDvbBinSerializer.cs
@@ -0,0 +1,321 @@
+using System.IO;
+using System.Linq;
+using System.Text;
+using ChanSort.Api;
+
+namespace ChanSort.Loader.Hisense.HisBin;
+
+/*
+ * Loads Hisense HIS_DVB.BIN channel lists
+ *
+ * See also the his-dvb.h file in Information/FileStructures_for_HHD_Hex_Editor_Neo
+ *
+ * Some properties of these lists:
+ * - channel records are physically ordered by their program number
+ * - TV and radio are managed in separate lists, both starting at 1
+ * - channel and provider names are raw DVB strings including control bytes
+ */
+public class HisDvbBinSerializer : SerializerBase
+{
+ private readonly ChannelList antTvChannels = new(SignalSource.DvbT | SignalSource.Tv, "Antenna TV");
+ private readonly ChannelList antRadioChannels = new(SignalSource.DvbT | SignalSource.Radio, "Antenna Radio");
+ private readonly ChannelList antDataChannels = new(SignalSource.DvbT | SignalSource.Data, "Antenna Data");
+ private readonly ChannelList cabTvChannels = new(SignalSource.DvbC | SignalSource.Tv, "Cable TV");
+ private readonly ChannelList cabRadioChannels = new(SignalSource.DvbC | SignalSource.Radio, "Cable Radio");
+ private readonly ChannelList cabDataChannels = new(SignalSource.DvbC | SignalSource.Data, "Cable Data");
+ private readonly ChannelList satTvChannels = new(SignalSource.DvbS | SignalSource.Tv, "Sat TV");
+ private readonly ChannelList satRadioChannels = new(SignalSource.DvbS | SignalSource.Radio, "Sat Radio");
+ private readonly ChannelList satDataChannels = new(SignalSource.DvbS | SignalSource.Data, "Sat Data");
+
+ private SubListInfo[] subListInfos;
+
+ private byte[] fileContent;
+ private int headerRecordSize, antRecordSize, cabRecordSize, satRecordSize;
+
+ private const string ERR_badFileFormat = "The content of the file doesn't match the expected format.";
+
+ private IniFile ini;
+ private DataMapping headerMapping, dvbMapping;
+ private DvbStringDecoder dvbStringDecoder;
+
+ #region class SubListInfo
+ private struct SubListInfo
+ {
+ public int Count;
+ public int Size;
+ public ChannelList TvList;
+ public ChannelList RadioList;
+ public ChannelList DataList;
+ public bool IsSat;
+
+ public SubListInfo(int count, int size, ChannelList tvList, ChannelList radioList, ChannelList dataList, bool isSat)
+ {
+ Count = count;
+ Size = size;
+ TvList = tvList;
+ RadioList = radioList;
+ DataList = dataList;
+ IsSat = isSat;
+ }
+ }
+ #endregion
+
+ #region ctor()
+ public HisDvbBinSerializer(string inputFile) : base(inputFile)
+ {
+ this.Features.ChannelNameEdit = ChannelNameEditMode.None;
+ this.Features.CanSkipChannels = true;
+ this.Features.CanLockChannels = true;
+ this.Features.CanHideChannels = false;
+ this.Features.FavoritesMode = FavoritesMode.Flags;
+ this.Features.MaxFavoriteLists = 1;
+ this.Features.DeleteMode = DeleteMode.Physically;
+ this.Features.CanHaveGaps = true;
+ this.Features.AllowGapsInFavNumbers = false;
+ this.ReadConfigurationFromIniFile();
+
+ this.DataRoot.AddChannelList(antTvChannels);
+ this.DataRoot.AddChannelList(antRadioChannels);
+ this.DataRoot.AddChannelList(antDataChannels);
+ this.DataRoot.AddChannelList(cabTvChannels);
+ this.DataRoot.AddChannelList(cabRadioChannels);
+ this.DataRoot.AddChannelList(cabDataChannels);
+ this.DataRoot.AddChannelList(satTvChannels);
+ this.DataRoot.AddChannelList(satRadioChannels);
+ this.DataRoot.AddChannelList(satDataChannels);
+ foreach (var list in this.DataRoot.ChannelLists)
+ {
+ list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.ChannelOrTransponder));
+ list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.Encrypted));
+ list.VisibleColumnFieldNames.Add(nameof(ChannelInfo.ServiceType));
+ }
+ }
+ #endregion
+
+ #region ReadConfigurationFromIniFile()
+
+ private void ReadConfigurationFromIniFile()
+ {
+ string iniFile = this.GetType().Assembly.Location.ToLower().Replace(".dll", ".ini");
+ this.ini = new IniFile(iniFile);
+ this.headerMapping = new DataMapping(ini.GetSection("HIS_DVB.BIN"));
+ this.headerRecordSize = headerMapping.Settings.GetInt("HeaderSize");
+
+ this.dvbMapping = new DataMapping(ini.GetSection("HIS_DVB.BIN_Record"));
+ this.antRecordSize = this.dvbMapping.Settings.GetInt("RecordSizeDvbT");
+ this.cabRecordSize = this.dvbMapping.Settings.GetInt("RecordSizeDvbC");
+ this.satRecordSize = this.dvbMapping.Settings.GetInt("RecordSizeDvbS");
+ this.dvbMapping.DefaultEncoding = this.DefaultEncoding;
+ }
+ #endregion
+
+
+ #region Load()
+
+ public override void Load()
+ {
+ this.fileContent = File.ReadAllBytes(this.FileName);
+
+ this.headerMapping.SetDataPtr(this.fileContent, 0);
+
+ var antChannelCount = this.headerMapping.GetWord("NumChannelsDvbT");
+ var cabChannelCount = this.headerMapping.GetWord("NumChannelsDvbC");
+ var satChannelCount = this.headerMapping.GetWord("NumChannelsDvbS");
+
+ var expectedSize = headerRecordSize + antChannelCount * antRecordSize + cabChannelCount * cabRecordSize + satChannelCount * satRecordSize;
+ if (this.fileContent.Length != expectedSize)
+ throw new FileLoadException(ERR_badFileFormat);
+
+ this.dvbStringDecoder = new DvbStringDecoder(this.DefaultEncoding);
+
+ var nameOffset = dvbMapping.Settings.GetInt("Name");
+ var nameLength = dvbMapping.Settings.GetInt("NameLength");
+ var providerOffset = dvbMapping.Settings.GetInt("Provider");
+ var providerLength = dvbMapping.Settings.GetInt("ProviderLength");
+
+ this.subListInfos = new SubListInfo[]
+ {
+ new (antChannelCount, antRecordSize, antTvChannels, antRadioChannels, antDataChannels, false),
+ new (cabChannelCount, cabRecordSize, cabTvChannels, cabRadioChannels, cabDataChannels, false),
+ new (satChannelCount, satRecordSize, satTvChannels, satRadioChannels, satDataChannels, true)
+ };
+
+ var off = headerRecordSize;
+ foreach (var info in this.subListInfos)
+ {
+ for (int index = 0; index < info.Count; index++)
+ {
+ dvbMapping.SetDataPtr(fileContent, off);
+ var ci = ReadChannel(index, info.IsSat, nameOffset, nameLength, providerOffset, providerLength);
+ if (ci != null)
+ {
+ var src = ci.SignalSource & SignalSource.MaskTvRadioData;
+ var channels = src == SignalSource.Tv ? info.TvList : src == SignalSource.Radio ? info.RadioList : info.DataList;
+ this.DataRoot.AddChannel(channels, ci);
+ }
+
+ off += info.Size;
+ }
+ }
+ }
+ #endregion
+
+ #region ReadChannel()
+ private ChannelInfo ReadChannel(int index, bool isSat, int nameOffset, int nameLength, int providerOffset, int providerLength)
+ {
+ ChannelInfo ci = new ChannelInfo(0, index, 0, "");
+ ci.RawDataOffset = dvbMapping.BaseOffset;
+
+ var type = dvbMapping.GetByte("TvRadioData");
+ if (type == 1)
+ ci.SignalSource |= SignalSource.Tv;
+ else if (type == 2)
+ ci.SignalSource |= SignalSource.Radio;
+ else
+ ci.SignalSource |= SignalSource.Data;
+
+ ci.Favorites = dvbMapping.GetByte("Fav") != 0 ? Favorites.A : 0;
+ ci.Skip = dvbMapping.GetByte("Skip") != 0;
+ ci.Lock = dvbMapping.GetByte("Lock") != 0;
+ ci.ServiceType = dvbMapping.GetByte("ServiceType");
+ ci.PcrPid = dvbMapping.GetWord("PcrPid");
+ ci.VideoPid = dvbMapping.GetWord("VideoPid");
+ ci.OldProgramNr = dvbMapping.GetWord("ProgNum");
+ ci.ServiceId = dvbMapping.GetWord("ServiceId");
+ ci.AudioPid = dvbMapping.GetWord("AudioPid");
+
+ this.dvbStringDecoder.GetChannelNames(fileContent, ci.RawDataOffset + nameOffset, nameLength, out var longName, out var shortName);
+ ci.Name = longName;
+ ci.ShortName = shortName;
+
+ this.dvbStringDecoder.GetChannelNames(fileContent, ci.RawDataOffset + providerOffset, providerLength, out var provider, out _);
+ ci.Provider = provider;
+
+ ci.TransportStreamId = dvbMapping.GetWord("Tsid");
+ ci.OriginalNetworkId = dvbMapping.GetWord("Onid");
+ ci.FreqInMhz = dvbMapping.GetDword("Frequency");
+ if (ci.FreqInMhz > 20000) // DVB-C/T has value in kHZ, DVB-S in MHz
+ ci.FreqInMhz /= 1000;
+ ci.SymbolRate = dvbMapping.GetWord("SymbolRate");
+
+ if (isSat)
+ ci.Satellite = dvbMapping.GetString("SatName", dvbMapping.Settings.GetInt("SatNameLength"));
+
+ return ci;
+ }
+ #endregion
+
+
+ // Saving ====================================
+
+ #region Save()
+ public override void Save()
+ {
+ using var mem = new MemoryStream(this.fileContent.Length);
+ using var writer = new BinaryWriter(mem);
+ writer.Write(this.fileContent, 0, this.headerRecordSize);
+
+ foreach (var info in this.subListInfos)
+ {
+ int newIndex = 0;
+ foreach (var list in new[] { info.TvList, info.RadioList, info.DataList })
+ {
+ var order = list.Channels.OrderBy(c => c, new DelegateComparer(OrderChannelsComparer)).ToList();
+
+ foreach (var channel in order)
+ {
+ if (channel.IsDeleted)
+ continue;
+
+ // copy original data
+ var offset = writer.BaseStream.Position;
+ writer.Write(this.fileContent, channel.RawDataOffset, info.Size);
+ writer.Flush();
+
+ // prepare to overwrite with some new values
+ dvbMapping.SetDataPtr(mem.GetBuffer(), (int)offset);
+ dvbMapping.SetWord("ProgNum", channel.NewProgramNr);
+ dvbMapping.SetByte("Skip", channel.Skip ? 1 : 0);
+ dvbMapping.SetByte("Lock", channel.Lock ? 1 : 0);
+ dvbMapping.SetByte("Fav", channel.Favorites != 0 ? 1 : 0);
+
+ channel.RecordIndex = newIndex++;
+ channel.RawDataOffset = (int)offset;
+ }
+
+ // update number of channels in header
+ headerMapping.SetDataPtr(mem.GetBuffer(), 0);
+ headerMapping.SetDword("NumSatChannels", newIndex);
+ }
+ }
+
+ // write to file
+ this.fileContent = new byte[mem.Length];
+ Tools.MemCopy(mem.GetBuffer(), 0, this.fileContent, 0, (int)mem.Length);
+ File.WriteAllBytes(this.FileName, this.fileContent);
+ }
+ #endregion
+
+ #region OrderChannelsComparer()
+ private int OrderChannelsComparer(ChannelInfo a, ChannelInfo b)
+ {
+ // deleted channels to the end
+ if (a.NewProgramNr < 0)
+ return b.NewProgramNr == 0 ? a.RecordIndex.CompareTo(b.RecordIndex) : +1;
+ if (b.NewProgramNr < 0)
+ return -1;
+
+ return a.NewProgramNr.CompareTo(b.NewProgramNr);
+ }
+ #endregion
+
+
+ // Infrastructure ============================
+
+ #region DefaultEncoding
+ public override Encoding DefaultEncoding
+ {
+ get => base.DefaultEncoding;
+ set
+ {
+ if (value == this.DefaultEncoding)
+ return;
+ base.DefaultEncoding = value;
+ this.dvbMapping.DefaultEncoding = value;
+
+ if (this.dvbStringDecoder != null)
+ {
+ this.dvbStringDecoder.DefaultEncoding = value;
+ this.ReparseNames();
+ }
+ }
+ }
+ #endregion
+
+ #region ReparseNames()
+ private void ReparseNames()
+ {
+ var nameOffset = dvbMapping.Settings.GetInt("Name");
+ var nameLength = dvbMapping.Settings.GetInt("NameLength");
+ var providerOffset = dvbMapping.Settings.GetInt("Provider");
+ var providerLength = dvbMapping.Settings.GetInt("ProviderLength");
+
+ foreach (var list in this.DataRoot.ChannelLists)
+ {
+ if (list.IsMixedSourceFavoritesList)
+ continue;
+ foreach (var chan in list.Channels)
+ {
+ dvbMapping.BaseOffset = chan.RawDataOffset;
+
+ this.dvbStringDecoder.GetChannelNames(this.fileContent, chan.RawDataOffset + nameOffset, nameLength, out var longName, out var shortName);
+ chan.Name = longName;
+ chan.ShortName = shortName;
+
+ this.dvbStringDecoder.GetChannelNames(this.fileContent, chan.RawDataOffset + providerOffset, providerLength, out var provider, out _);
+ chan.Provider = provider;
+ }
+ }
+ }
+ #endregion
+}
diff --git a/source/ChanSort.Loader.Hisense/HisBin/HisBinSerializer.cs b/source/ChanSort.Loader.Hisense/HisBin/HisSvlBinSerializer.cs
similarity index 99%
rename from source/ChanSort.Loader.Hisense/HisBin/HisBinSerializer.cs
rename to source/ChanSort.Loader.Hisense/HisBin/HisSvlBinSerializer.cs
index 8d3ce06..c972841 100644
--- a/source/ChanSort.Loader.Hisense/HisBin/HisBinSerializer.cs
+++ b/source/ChanSort.Loader.Hisense/HisBin/HisSvlBinSerializer.cs
@@ -22,7 +22,7 @@ namespace ChanSort.Loader.Hisense.HisBin;
* - favorite lists allow mixing channels from different inputs and also radio and TV
* - character encoding is implicit and can be UTF8 or latin-1
*/
-public class HisBinSerializer : SerializerBase
+public class HisSvlBinSerializer : SerializerBase
{
private readonly ChannelList dvbtChannels = new (SignalSource.DvbT | SignalSource.Tv | SignalSource.Radio, "DVB-T");
private readonly ChannelList dvbcChannels = new (SignalSource.DvbC | SignalSource.Tv | SignalSource.Radio, "DVB-C");
@@ -45,7 +45,7 @@ public class HisBinSerializer : SerializerBase
private readonly Dictionary transponder = new ();
#region ctor()
- public HisBinSerializer(string inputFile) : base(inputFile)
+ public HisSvlBinSerializer(string inputFile) : base(inputFile)
{
this.Features.ChannelNameEdit = ChannelNameEditMode.All;
this.Features.CanSkipChannels = true;
diff --git a/source/ChanSort.Loader.Hisense/HisensePlugin.cs b/source/ChanSort.Loader.Hisense/HisensePlugin.cs
index 7b4d9e7..0d7dde8 100644
--- a/source/ChanSort.Loader.Hisense/HisensePlugin.cs
+++ b/source/ChanSort.Loader.Hisense/HisensePlugin.cs
@@ -19,8 +19,11 @@ namespace ChanSort.Loader.Hisense
if (name.Contains("servicelist")) // models 2017 and later
return new ServicelistDb.ServicelistDbSerializer(inputFile);
- if (name.StartsWith("his_") && name.EndsWith(".bin"))
- return new HisBin.HisBinSerializer(inputFile);
+ if (name.StartsWith("his_dvb") && name.EndsWith(".bin")) // HIS_DVB.BIN
+ return new HisBin.HisDvbBinSerializer(inputFile);
+
+ if (name.StartsWith("his_") && name.EndsWith(".bin")) // HIS_SVL.BIN, HIS_TSL.BIN, HIS_FAV.BIN
+ return new HisBin.HisSvlBinSerializer(inputFile);
return null;
}
}
diff --git a/source/ChanSort.sln b/source/ChanSort.sln
index 07ae1a0..6533f50 100644
--- a/source/ChanSort.sln
+++ b/source/ChanSort.sln
@@ -115,6 +115,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HDD Hex Edit Neo", "HDD Hex
Information\FileStructures_for_HHD_Hex_Editor_Neo\dtv_cmdb_1-bin.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\dtv_cmdb_1-bin.h
Information\FileStructures_for_HHD_Hex_Editor_Neo\dtv_cmdb_2-bin.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\dtv_cmdb_2-bin.h
Information\FileStructures_for_HHD_Hex_Editor_Neo\get_doc_size.js = Information\FileStructures_for_HHD_Hex_Editor_Neo\get_doc_size.js
+ Information\FileStructures_for_HHD_Hex_Editor_Neo\his-dvb.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\his-dvb.h
Information\FileStructures_for_HHD_Hex_Editor_Neo\his-svl.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\his-svl.h
Information\FileStructures_for_HHD_Hex_Editor_Neo\LaSat_lst.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\LaSat_lst.h
Information\FileStructures_for_HHD_Hex_Editor_Neo\panasonic_idtvChannelBin.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\panasonic_idtvChannelBin.h
diff --git a/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/his-dvb.h b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/his-dvb.h
new file mode 100644
index 0000000..bcb4d09
--- /dev/null
+++ b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/his-dvb.h
@@ -0,0 +1,75 @@
+#include "chansort.h"
+
+[display(format("{0}", trim(lang)))]
+struct LanguageInfo
+{
+ byte lang[4];
+ byte u2[2];
+ word audioPid;
+ byte u2[6];
+};
+
+enum SubList : byte
+{
+ TV=1, Radio=2, Data=3
+};
+
+[display(format("{0} - {1}", progNr, trim(name)))]
+struct DvbChannel
+{
+ byte u1[21];
+ byte skip;
+ byte lock;
+ byte u2[6];
+ SubList list;
+ byte isFav;
+ byte u3[4];
+ ServiceType serviceType;
+ word pcrPid;
+ word videoPid;
+ word progNr;
+ byte u4[4];
+ word pmtPid;
+ word serviceId;
+ byte u5[12];
+ LanguageInfo languages[3];
+ byte u6[182];
+ char name[50];
+ char provider[50];
+ byte u7[10];
+ word u8;
+ word tsid;
+ word onid1;
+ word onid2;
+ byte u9[4];
+ word freq;
+ byte u10[10];
+ dword symbolRate;
+ byte u11[74];
+};
+
+[display(format("{0}.{1} - {2}", dvb.list, dvb.progNr, trim(dvb.name)))]
+struct SatChannel
+{
+ DvbChannel dvb;
+ word u1;
+ char satName[32];
+ byte u11a[2];
+ word lowFreq;
+ word highFreq;
+ byte u11b[10];
+ byte orbitalPos;
+ byte u12[13];
+};
+
+public struct HIS_DVB_BIN
+{
+ dword versionMaybe;
+ dword numAntennaChannels;
+ dword numCableChannels;
+ dword numSatChannels;
+
+ DvbChannel antennaChannels[numAntennaChannels];
+ DvbChannel cableChannels[numCableChannels];
+ SatChannel satChannels[numSatChannels];
+};
diff --git a/source/changelog.md b/source/changelog.md
index d952dac..9acc95f 100644
--- a/source/changelog.md
+++ b/source/changelog.md
@@ -1,6 +1,12 @@
ChanSort Change Log
===================
+2023-01-23
+- added support for Hisense HIS_DVB.BIN channel lists
+
+2023-01-18
+- added support for Hisense HIS_SVL.BIN / HIS_TSL.BIN / HIS_FAV.BIN channel lists
+
2023-01-15
- added support for Vision EDGE 4K set-top-box (DVB-S only)
- TCL: separate lists for DVB-C/T/S, each starting at 1