mirror of
https://github.com/PredatH0r/ChanSort.git
synced 2026-01-19 22:02:04 +01:00
- added support for .DBM file (for now only the TechniSat DVB-C version)
This commit is contained in:
27
source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.csproj
Normal file
27
source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<OutputPath>..\Debug\</OutputPath>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>..\Release\</OutputPath>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ChanSort.Api\ChanSort.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="ChanSort.Loader.DBM.ini">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
33
source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.ini
Normal file
33
source/ChanSort.Loader.DBM/ChanSort.Loader.DBM.ini
Normal file
@@ -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
|
||||
22
source/ChanSort.Loader.DBM/DbmPlugin.cs
Normal file
22
source/ChanSort.Loader.DBM/DbmPlugin.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
217
source/ChanSort.Loader.DBM/DbmSerializer.cs
Normal file
217
source/ChanSort.Loader.DBM/DbmSerializer.cs
Normal file
@@ -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
|
||||
/// <summary>
|
||||
/// The checksum is the byte sum over the data bytes (offset 6 to end-2) plus 0x55 added to it
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
<ProjectReference Include="..\ChanSort.Api\ChanSort.Api.csproj" />
|
||||
<ProjectReference Include="..\ChanSort.Loader.Android\ChanSort.Loader.Android.csproj" />
|
||||
<ProjectReference Include="..\ChanSort.Loader.CmdbBin\ChanSort.Loader.CmdbBin.csproj" />
|
||||
<ProjectReference Include="..\ChanSort.Loader.DBM\ChanSort.Loader.DBM.csproj" />
|
||||
<ProjectReference Include="..\ChanSort.Loader.Enigma2\ChanSort.Loader.Enigma2.csproj" />
|
||||
<ProjectReference Include="..\ChanSort.Loader.Grundig\ChanSort.Loader.Grundig.csproj" />
|
||||
<ProjectReference Include="..\ChanSort.Loader.Hisense\ChanSort.Loader.Hisense.csproj" />
|
||||
|
||||
@@ -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[*];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user