diff --git a/source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.csproj b/source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.csproj
new file mode 100644
index 0000000..0fb28d5
--- /dev/null
+++ b/source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net48
+ latest
+
+
+ ..\Debug\
+ MinimumRecommendedRules.ruleset
+
+
+ ..\Release\
+ MinimumRecommendedRules.ruleset
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
diff --git a/source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.ini b/source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.ini
new file mode 100644
index 0000000..1095e1d
--- /dev/null
+++ b/source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.ini
@@ -0,0 +1,33 @@
+[dbm:163772]
+isDvbS=false
+offChecksum=0x0000
+offDataLength=0x0002
+offData=0x0006
+
+offTransponderBitmap=0x0006
+lenTransponderBitmap=16
+offTransponderData=0x0016
+numTransponder=100
+lenTransponderData=36
+
+offChannelBitmap=0x0E3C
+lenChannelBitmap=126
+offChannelData=0x0EBA
+numChannel=1000
+lenChannelData=160
+
+[transponder:163772]
+offFreq=0
+offSymRate=8
+
+[channel:163772]
+offName=0
+lenName=64
+offProgNr=64
+offLcn=66
+offTransponderIndex=70
+offTsid=96
+offOnid=98
+offSid=100
+offPcrPid=104
+offVideoPid=106
diff --git a/source/ChanSort.Loader.DBM/DbmPlugin.cs b/source/ChanSort.Loader.DBM/DbmPlugin.cs
new file mode 100644
index 0000000..cf3a7b5
--- /dev/null
+++ b/source/ChanSort.Loader.DBM/DbmPlugin.cs
@@ -0,0 +1,22 @@
+using ChanSort.Api;
+
+namespace ChanSort.Loader.DBM
+{
+ public class DbmPlugin : ISerializerPlugin
+ {
+ /*
+ * Various brands use variations of an underlying .DBM binary file format for DVB-C and DVB-S tuners.
+ *
+ * Known models include Xoro, TechniSat, ...
+ */
+
+ public string DllName { get; set; }
+ public string PluginName => "DBM (Xoro, TechniSat, ...)";
+ public string FileFilter => "*.dbm";
+
+ public SerializerBase CreateSerializer(string inputFile)
+ {
+ return new DbmSerializer(inputFile);
+ }
+ }
+}
diff --git a/source/ChanSort.Loader.DBM/DbmSerializer.cs b/source/ChanSort.Loader.DBM/DbmSerializer.cs
new file mode 100644
index 0000000..bf0744f
--- /dev/null
+++ b/source/ChanSort.Loader.DBM/DbmSerializer.cs
@@ -0,0 +1,217 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using ChanSort.Api;
+
+namespace ChanSort.Loader.DBM
+{
+ /*
+
+
+ */
+ public class DbmSerializer : SerializerBase
+ {
+ private readonly IniFile ini;
+ private IniFile.Section sec;
+ private DataMapping mapping;
+ private readonly ChannelList allChannels = new ChannelList(SignalSource.All, "All");
+
+ private byte[] data;
+ private int fileSize;
+
+ private readonly StringBuilder logMessages = new StringBuilder();
+
+
+ #region ctor()
+ public DbmSerializer(string inputFile) : base(inputFile)
+ {
+ this.Features.ChannelNameEdit = ChannelNameEditMode.None;
+ this.Features.CanSkipChannels = false;
+ this.Features.CanLockChannels = false;
+ this.Features.CanHideChannels = false;
+ this.Features.DeleteMode = DeleteMode.NotSupported;
+ this.Features.CanHaveGaps = false;
+ this.Features.FavoritesMode = FavoritesMode.None;
+ this.Features.MaxFavoriteLists = 0;
+ this.Features.AllowGapsInFavNumbers = false;
+ this.Features.CanEditFavListNames = false;
+
+ this.DataRoot.AddChannelList(this.allChannels);
+
+ foreach (var list in this.DataRoot.ChannelLists)
+ {
+ list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.AudioPid));
+ list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.ChannelOrTransponder));
+ list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.Provider));
+ list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.Encrypted));
+ list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.ServiceType));
+ list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.ServiceTypeName));
+ }
+
+ string iniFile = Assembly.GetExecutingAssembly().Location.Replace(".dll", ".ini");
+ this.ini = new IniFile(iniFile);
+ }
+ #endregion
+
+ // loading
+
+ #region Load
+ public override void Load()
+ {
+ var info = new FileInfo(this.FileName);
+ this.fileSize = (int)info.Length;
+
+ this.sec = ini.GetSection("dbm:" + this.fileSize);
+ if (sec == null)
+ throw LoaderException.Fail($"No configuration for .DBM files with size {info.Length} in .ini file");
+
+ if (!sec.GetBool("isDvbS"))
+ allChannels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.Satellite));
+
+ this.data = File.ReadAllBytes(this.FileName);
+ this.mapping = new DataMapping(sec);
+ this.mapping.SetDataPtr(data, 0);
+
+ ValidateChecksum(data, sec);
+
+ LoadTransponder(sec, data);
+ LoadChannels(sec, data);
+ }
+ #endregion
+
+ #region ValidateChecksum()
+ private void ValidateChecksum(byte[] data, IniFile.Section sec)
+ {
+ var expectedChecksum = BitConverter.ToUInt16(data, 0);
+ var calculatedChecksum = CalcChecksum(data, sec.GetInt("offData"), (int)mapping.GetDword("offDataLength"));
+ if (expectedChecksum != calculatedChecksum)
+ {
+ var msg = $"Invalid checksum. Expected {expectedChecksum:x4} but calculated {calculatedChecksum:x4}";
+ throw LoaderException.Fail(msg);
+ }
+ }
+
+ #endregion
+
+ #region LoadTransponder()
+
+ private void LoadTransponder(IniFile.Section sec, byte[] data)
+ {
+ var num = sec.GetInt("numTransponder");
+ var offBitmap = sec.GetInt("offTransponderBitmap");
+
+ var map = new DataMapping(ini.GetSection("transponder:" + this.fileSize));
+ map.SetDataPtr(data, sec.GetInt("offTransponderData"));
+ var recordSize = sec.GetInt("lenTransponderData");
+
+ for (int i = 0; i < num; i++)
+ {
+ if ((data[offBitmap + i / 8] & (1 << (i & 0x07))) != 0)
+ {
+ var t = new Transponder(i);
+ t.FrequencyInMhz = (decimal)map.GetDword("offFreq") / 1000;
+ t.SymbolRate = (int)map.GetDword("offSymRate");
+ this.DataRoot.AddTransponder(null, t);
+ }
+
+ map.BaseOffset += recordSize;
+ }
+ }
+ #endregion
+
+ #region LoadChannels()
+
+ private void LoadChannels(IniFile.Section sec, byte[] data)
+ {
+ var num = sec.GetInt("numChannel");
+ var offBitmap = sec.GetInt("offChannelBitmap");
+
+ var sec2 = ini.GetSection("channel:" + this.fileSize);
+ var map = new DataMapping(sec2);
+ map.SetDataPtr(data, sec.GetInt("offChannelData"));
+ var recordSize = sec.GetInt("lenChannelData");
+
+ var dec = new DvbStringDecoder(this.DefaultEncoding);
+
+ for (int i = 0; i < num; i++)
+ {
+ if ((data[offBitmap + i / 8] & (1 << (i & 0x07))) != 0)
+ {
+ var c = new ChannelInfo(SignalSource.Any, i, -1, null);
+ dec.GetChannelNames(data, map.BaseOffset + sec2.GetInt("offName"), sec2.GetInt("lenName"), out var longName, out var shortName);
+ c.Name = longName;
+ c.ShortName = shortName;
+ c.OldProgramNr = map.GetWord("offProgNr") + 1;
+ c.OriginalNetworkId = map.GetWord("offOnid");
+ c.TransportStreamId = map.GetWord("offTsid");
+ c.ServiceId = map.GetWord("offSid");
+ c.PcrPid = map.GetWord("offPcrPid");
+ c.VideoPid = map.GetWord("offVideoPid");
+
+ var transpIdx = map.GetByte("offTransponderIndex");
+ var tp = this.DataRoot.Transponder.TryGet(transpIdx);
+ if (tp != null)
+ {
+ c.FreqInMhz = tp.FrequencyInMhz;
+ c.SymbolRate = tp.SymbolRate;
+ }
+
+ this.DataRoot.AddChannel(this.allChannels, c);
+ }
+
+ map.BaseOffset += recordSize;
+ }
+ }
+ #endregion
+
+ // saving
+
+ #region Save()
+ public override void Save()
+ {
+ var sec2 = ini.GetSection("channel:" + this.fileSize);
+ var map = new DataMapping(sec2);
+ var baseOffset = sec.GetInt("offChannelData");
+ map.SetDataPtr(data, baseOffset);
+ var recordSize = sec.GetInt("lenChannelData");
+
+ foreach (var chan in this.allChannels.Channels)
+ {
+ if (chan.IsProxy) continue;
+ map.BaseOffset = baseOffset + (int)chan.RecordIndex * recordSize;
+ map.SetWord("offProgNr", chan.NewProgramNr - 1);
+ }
+
+ var calculatedChecksum = CalcChecksum(data, sec.GetInt("offData"), (int)mapping.GetDword("offDataLength"));
+ mapping.SetWord("offChecksum", calculatedChecksum);
+ File.WriteAllBytes(this.FileName, this.data);
+ }
+ #endregion
+
+ // common
+
+ #region CalcChecksum
+ ///
+ /// The checksum is the byte sum over the data bytes (offset 6 to end-2) plus 0x55 added to it
+ ///
+ public ushort CalcChecksum(byte[] bytes, int start, int count)
+ {
+ var sum = 0x55u;
+ while (count-- > 0)
+ sum += bytes[start++];
+ return (ushort)sum;
+ }
+ #endregion
+
+
+ // framework support methods
+
+ #region GetFileInformation
+ public override string GetFileInformation()
+ {
+ return base.GetFileInformation() + this.logMessages.Replace("\n", "\r\n");
+ }
+ #endregion
+ }
+}
diff --git a/source/ChanSort.sln b/source/ChanSort.sln
index ef21989..8a0bd0c 100644
--- a/source/ChanSort.sln
+++ b/source/ChanSort.sln
@@ -33,7 +33,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Solution.props = Solution.props
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Loader", "Test.Loader\Test.Loader.csproj", "{68CFCB2F-B52A-43A1-AA5C-5D64A1D655D2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.Loader", "Test.Loader\Test.Loader.csproj", "{68CFCB2F-B52A-43A1-AA5C-5D64A1D655D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChanSort.Loader.Samsung", "ChanSort.Loader.Samsung\ChanSort.Loader.Samsung.csproj", "{A1C9A98D-368A-44E8-9B7F-7EACA46C9EC5}"
EndProject
@@ -101,7 +101,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.Loader.CmdbBin", "Test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChanSort.Loader.Unsupported", "ChanSort.Loader.Unsupported\ChanSort.Loader.Unsupported.csproj", "{D7C32DAE-5D77-46A0-BC16-C95D9C7EFDD5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.TCL", "ChanSort.Loader.TCL\ChanSort.Loader.TCL.csproj", "{460D9527-F7EF-4277-9382-FB609A44D66A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChanSort.Loader.TCL", "ChanSort.Loader.TCL\ChanSort.Loader.TCL.csproj", "{460D9527-F7EF-4277-9382-FB609A44D66A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.DBM", "ChanSort.Loader.DBM\ChanSort.Loader.DBM.csproj", "{C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -1260,6 +1262,36 @@ Global
{460D9527-F7EF-4277-9382-FB609A44D66A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{460D9527-F7EF-4277-9382-FB609A44D66A}.Release|x86.ActiveCfg = Release|Any CPU
{460D9527-F7EF-4277-9382-FB609A44D66A}.Release|x86.Build.0 = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Debug|x86.ActiveCfg = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Debug|x86.Build.0 = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Release|Any CPU.Build.0 = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Release|x86.ActiveCfg = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.All_Release|x86.Build.0 = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Debug|x86.Build.0 = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.NoDevExpress_Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.NoDevExpress_Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.NoDevExpress_Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.NoDevExpress_Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.NoDevExpress_Debug|x86.ActiveCfg = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.NoDevExpress_Debug|x86.Build.0 = Debug|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Release|x86.ActiveCfg = Release|Any CPU
+ {C1FF21EB-1CA6-4CE9-8BA8-9FAF71C5D6A6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/source/ChanSort/ChanSort.csproj b/source/ChanSort/ChanSort.csproj
index 43f05fb..36b044a 100644
--- a/source/ChanSort/ChanSort.csproj
+++ b/source/ChanSort/ChanSort.csproj
@@ -129,6 +129,7 @@
+
diff --git a/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/dbm-163772-dvbc.h b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/dbm-163772-dvbc.h
new file mode 100644
index 0000000..5d62919
--- /dev/null
+++ b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/dbm-163772-dvbc.h
@@ -0,0 +1,52 @@
+#include "chansort.h"
+
+struct s_Transponder
+{
+ var off0 = current_offset;
+ dword Freq;
+ byte unk1[4];
+ dword SymRate;
+ var off1 = current_offset;
+
+ byte unk[36 - (off1 - off0)];
+};
+
+struct s_Channel
+{
+ var off0 = current_offset;
+ byte Name[64];
+ word progNrMinus1;
+ word lcn;
+ byte u3[2];
+ byte transponderIndex;
+ byte u4[25];
+ word tsid;
+ word onid;
+ word sid;
+
+ byte u5[2];
+ word pcrPidMaybe;
+ word vpidMaybe;
+
+ var off1 = current_offset;
+ byte unk[160 - (off1-off0)];
+};
+
+
+public struct DBM_163772_DvbC
+{
+ word BytesumPlus0x55;
+ dword DataLengthForBytesum;
+
+ byte TransponderBitmap[16];
+ s_Transponder TransponderData[100];
+
+ var off0 = current_offset;
+ byte unk1[0x0E3C - off0];
+
+ byte ChannelBitmap[126];
+ s_Channel ChannelData[1000];
+
+ byte Extra[*];
+};
+