using System; using System.Collections.Generic; using System.Data; using System.Data.SQLite; using System.IO; using System.Text; using System.Windows.Forms; using ChanSort.Api; namespace ChanSort.Loader.Panasonic { class Serializer : SerializerBase { private const string ERR_FileFormatOrEncryption = "File uses an unknown format or encryption"; private static readonly int[] headerCypherTable; private readonly ChannelList avbtChannels = new ChannelList(SignalSource.AnalogT | SignalSource.Tv | SignalSource.Radio, "Analog Antenna"); private readonly ChannelList avbcChannels = new ChannelList(SignalSource.AnalogC | SignalSource.Tv | SignalSource.Radio, "Analog Cable"); private readonly ChannelList dvbtChannels = new ChannelList(SignalSource.DvbT | SignalSource.Tv | SignalSource.Radio, "DVB-T"); private readonly ChannelList dvbcChannels = new ChannelList(SignalSource.DvbC | SignalSource.Tv | SignalSource.Radio, "DVB-C"); private readonly ChannelList dvbsChannels = new ChannelList(SignalSource.DvbS | SignalSource.Tv | SignalSource.Radio, "DVB-S"); private readonly ChannelList satipChannels = new ChannelList(SignalSource.SatIP | SignalSource.Tv | SignalSource.Radio, "SAT>IP"); private readonly ChannelList freesatChannels = new ChannelList(SignalSource.DvbS | SignalSource.Freesat | SignalSource.Tv | SignalSource.Radio, "Freesat"); private string workFile; private CypherMode cypherMode; private byte[] fileHeader = new byte[0]; private int dbSizeOffset; private bool littleEndianByteOrder; private string charEncoding; enum CypherMode { None, HeaderAndChecksum, Encryption, Unknown } #region static ctor / headerCypherTable static Serializer() { headerCypherTable = new[] { 0, 79764919, 159529838, 222504665, 319059676, 398814059, 445009330, 507990021, 638119352, 583659535, 797628118, 726387553, 890018660, 835552979, 1015980042, 944750013, 1276238704, 1221641927, 1167319070, 1095957929, 1595256236, 1540665371, 1452775106, 1381403509, 1780037320, 1859660671, 1671105958, 1733955601, 2031960084, 2111593891, 1889500026, 1952343757, -1742489888, -1662866601, -1851683442, -1788833735, -1960329156, -1880695413, -2103051438, -2040207643, -1104454824, -1159051537, -1213636554, -1284997759, -1389417084, -1444007885, -1532160278, -1603531939, -734892656, -789352409, -575645954, -646886583, -952755380, -1007220997, -827056094, -898286187, -231047128, -151282273, -71779514, -8804623, -515967244, -436212925, -390279782, -327299027, 881225847, 809987520, 1023691545, 969234094, 662832811, 591600412, 771767749, 717299826, 311336399, 374308984, 453813921, 533576470, 25881363, 88864420, 134795389, 214552010, 2023205639, 2086057648, 1897238633, 1976864222, 1804852699, 1867694188, 1645340341, 1724971778, 1587496639, 1516133128, 1461550545, 1406951526, 1302016099, 1230646740, 1142491917, 1087903418, -1398421865, -1469785312, -1524105735, -1578704818, -1079922613, -1151291908, -1239184603, -1293773166, -1968362705, -1905510760, -2094067647, -2014441994, -1716953613, -1654112188, -1876203875, -1796572374, -525066777, -462094256, -382327159, -302564546, -206542021, -143559028, -97365931, -17609246, -960696225, -1031934488, -817968335, -872425850, -709327229, -780559564, -600130067, -654598054, 1762451694, 1842216281, 1619975040, 1682949687, 2047383090, 2127137669, 1938468188, 2001449195, 1325665622, 1271206113, 1183200824, 1111960463, 1543535498, 1489069629, 1434599652, 1363369299, 622672798, 568075817, 748617968, 677256519, 907627842, 853037301, 1067152940, 995781531, 51762726, 131386257, 177728840, 240578815, 269590778, 349224269, 429104020, 491947555, -248556018, -168932423, -122852000, -60002089, -500490030, -420856475, -341238852, -278395381, -685261898, -739858943, -559578920, -630940305, -1004286614, -1058877219, -845023740, -916395085, -1119974018, -1174433591, -1262701040, -1333941337, -1371866206, -1426332139, -1481064244, -1552294533, -1690935098, -1611170447, -1833673816, -1770699233, -2009983462, -1930228819, -2119160460, -2056179517, 1569362073, 1498123566, 1409854455, 1355396672, 1317987909, 1246755826, 1192025387, 1137557660, 2072149281, 2135122070, 1912620623, 1992383480, 1753615357, 1816598090, 1627664531, 1707420964, 295390185, 358241886, 404320391, 483945776, 43990325, 106832002, 186451547, 266083308, 932423249, 861060070, 1041341759, 986742920, 613929101, 542559546, 756411363, 701822548, -978770311, -1050133554, -869589737, -924188512, -693284699, -764654318, -550540341, -605129092, -475935807, -413084042, -366743377, -287118056, -257573603, -194731862, -114850189, -35218492, -1984365303, -1921392450, -2143631769, -2063868976, -1698919467, -1635936670, -1824608069, -1744851700, -1347415887, -1418654458, -1506661409, -1561119128, -1129027987, -1200260134, -1254728445, -1309196108 }; } #endregion #region ctor() public Serializer(string inputFile) : base(inputFile) { DepencencyChecker.AssertVc2010RedistPackageX86Installed(); this.Features.ChannelNameEdit = ChannelNameEditMode.None; // due to the chaos with binary data inside the "sname" string column, writing back a name has undesired side effects this.Features.CanHaveGaps = false; this.Features.EncryptedFlagEdit = true; this.DataRoot.SortedFavorites = true; this.DataRoot.AddChannelList(this.avbtChannels); this.DataRoot.AddChannelList(this.avbcChannels); this.DataRoot.AddChannelList(this.dvbtChannels); this.DataRoot.AddChannelList(this.dvbcChannels); this.DataRoot.AddChannelList(this.dvbsChannels); this.DataRoot.AddChannelList(this.satipChannels); this.DataRoot.AddChannelList(this.freesatChannels); } #endregion public override string DisplayName => "Panasonic .db/.bin Loader"; #region Load() public override void Load() { this.workFile = this.GetUncypheredWorkFile(); this.CreateDummySatellites(); string channelConnString = "Data Source=" + this.workFile; using (var conn = new SQLiteConnection(channelConnString)) { conn.Open(); using (var cmd = conn.CreateCommand()) { RepairCorruptedDatabaseImage(cmd); InitCharacterEncoding(cmd); cmd.CommandText = "SELECT count(1) FROM sqlite_master WHERE type = 'table' and name in ('svl', 'tsl')"; if (Convert.ToInt32(cmd.ExecuteScalar()) != 2) throw new FileLoadException("File doesn't contain the expected TSL/SVL tables"); this.ReadChannels(cmd); } } } #endregion #region GetUncypheredWorkFile() private string GetUncypheredWorkFile() { this.cypherMode = this.GetCypherMode(this.FileName); if (cypherMode == CypherMode.Unknown) throw new FileLoadException(ERR_FileFormatOrEncryption); if (cypherMode == CypherMode.None) return this.FileName; var tempFile = this.FileName + ".tmp"; File.Delete(tempFile); Application.ApplicationExit += CleanTempFile; if (cypherMode == CypherMode.Encryption) this.CypherFile(this.FileName, tempFile, false); else this.RemoveHeader(this.FileName, tempFile); return tempFile; } #endregion #region GetCypherMode() private CypherMode GetCypherMode(string file) { using (var stream = File.OpenRead(file)) using (var rdr = new BinaryReader(stream)) { uint value = (uint)rdr.ReadInt32(); if (value == 0x694C5153) return CypherMode.None; // "SQLi" if (value == 0x42445350) return CypherMode.HeaderAndChecksum; // "PSDB" if (value == 0xA07DCB50) return CypherMode.Encryption; return CypherMode.Unknown; } } #endregion #region CypherFile() /// /// XOR-based cypher which can be used to alternately crypt/decrypt data /// private void CypherFile(string input, string output, bool encrypt) { byte[] fileContent = File.ReadAllBytes(input); if (!encrypt && this.CalcChecksum(fileContent, fileContent.Length) != 0) throw new FileLoadException("Checksum validation failed"); int chiffre = 0x0388; int step = 0; for (int i = 0; i < fileContent.Length - 4; i++) { byte b = fileContent[i]; byte n = (byte) (b ^ (chiffre >> 8)); fileContent[i] = n; if (++step < 256) chiffre += (encrypt ? n : b) + 0x96A3; else { chiffre = 0x0388; step = 0; } } if (encrypt) this.UpdateChecksum(fileContent); File.WriteAllBytes(output, fileContent); } #endregion #region RemoveHeader() private void RemoveHeader(string inputFile, string outputFile) { var data = File.ReadAllBytes(inputFile); if (this.CalcChecksum(data, data.Length) != 0) throw new FileLoadException("Checksum validation failed"); int offset; if (!this.ValidateFileSize(data, false, out offset) && !this.ValidateFileSize(data, true, out offset)) throw new FileLoadException("File size validation failed"); using (var stream = new FileStream(outputFile, FileMode.Create, FileAccess.Write)) stream.Write(data, offset, data.Length - offset - 4); this.fileHeader = new byte[offset]; Array.Copy(data, 0, this.fileHeader, 0, offset); } #endregion #region ValidateFileSize() private bool ValidateFileSize(byte[] data, bool littleEndian, out int offset) { this.littleEndianByteOrder = littleEndian; offset = 30 + Tools.GetInt16(data, 28, littleEndian); if (offset >= data.Length) return false; this.dbSizeOffset = offset; int dbSize = Tools.GetInt32(data, offset, littleEndian); offset += 4; return data.Length == offset + dbSize + 4; } #endregion #region CalcChecksum() private uint CalcChecksum(byte[] data, int length) { uint v = 0xffffffff; for (int i = 0; i < length; i++) { byte b = data[i]; v = (v << 8) ^ (uint)headerCypherTable[((v >> 24) ^ b) & 0xFF]; } return v; } #endregion #region CleanTempFile() private void CleanTempFile(object sender, EventArgs e) { try { if (this.workFile != null) File.Delete(this.workFile); } catch { } } #endregion #region CreateDummySatellites() private void CreateDummySatellites() { for (int i = 1; i <= 4; i++) { var sat = new Satellite(i); sat.Name = "LNB "+i; sat.OrbitalPosition = i.ToString(); this.DataRoot.Satellites.Add(i, sat); } } #endregion #region InitCharacterEncoding() private void InitCharacterEncoding(SQLiteCommand cmd) { cmd.CommandText = "PRAGMA encoding"; this.charEncoding = cmd.ExecuteScalar() as string; } #endregion #region RepairCorruptedDatabaseImage() private void RepairCorruptedDatabaseImage(SQLiteCommand cmd) { cmd.CommandText = "REINDEX"; cmd.ExecuteNonQuery(); } #endregion #region ReadChannels() private void ReadChannels(SQLiteCommand cmd) { string[] fieldNames = { "rowid", "major_channel", "physical_ch","sname", "freq", "skip", "running_status","free_CA_mode","child_lock", "profile1index","profile2index","profile3index","profile4index","stype", "onid", "tsid", "sid", "ntype", "ya_svcid", "delivery" }; const string sql = @" select s.rowid,s.major_channel,s.physical_ch,cast(s.sname as blob),t.freq,s.skip,s.running_status,s.free_CA_mode,s.child_lock, profile1index,profile2index,profile3index,profile4index,s.stype,s.onid,s.tsid,s.svcid,s.ntype,s.ya_svcid,delivery from SVL s left outer join TSL t on s.ntype=t.ntype and s.physical_ch=t.physical_ch and s.tsid=t.tsid order by s.ntype,major_channel "; var fields = this.GetFieldMap(fieldNames); cmd.CommandText = sql; using (var r = cmd.ExecuteReader()) { while (r.Read()) { ChannelInfo channel = new DbChannel(r, fields, this.DataRoot, this.DefaultEncoding); if (!channel.IsDeleted) { var channelList = this.DataRoot.GetChannelList(channel.SignalSource); if (channelList != null) this.DataRoot.AddChannel(channelList, channel); } } } } #endregion #region GetFieldMap() private IDictionary GetFieldMap(string[] fieldNames) { Dictionary field = new Dictionary(); for (int i = 0; i < fieldNames.Length; i++) field[fieldNames[i]] = i; return field; } #endregion #region DefaultEncoding public override Encoding DefaultEncoding { get { return base.DefaultEncoding; } set { base.DefaultEncoding = value; foreach (var list in this.DataRoot.ChannelLists) { foreach(var channel in list.Channels) channel.ChangeEncoding(value); } } } #endregion #region Save() public override void Save(string tvOutputFile) { this.FileName = tvOutputFile; string channelConnString = "Data Source=" + this.workFile; using (var conn = new SQLiteConnection(channelConnString)) { conn.Open(); using (var cmd = conn.CreateCommand()) { using (var trans = conn.BeginTransaction()) { this.WriteChannels(cmd, this.avbtChannels); this.WriteChannels(cmd, this.avbcChannels); this.WriteChannels(cmd, this.dvbtChannels); this.WriteChannels(cmd, this.dvbcChannels); this.WriteChannels(cmd, this.dvbsChannels); this.WriteChannels(cmd, this.satipChannels); this.WriteChannels(cmd, this.freesatChannels); trans.Commit(); } this.RepairCorruptedDatabaseImage(cmd); } } this.WriteCypheredFile(); } #endregion #region WriteChannels() private void WriteChannels(SQLiteCommand cmd, ChannelList channelList) { cmd.CommandText = "update SVL set major_channel=@progNr, profile1index=@fav1, profile2index=@fav2, profile3index=@fav3, profile4index=@fav4, child_lock=@lock, skip=@skip where rowid=@rowid"; cmd.Parameters.Clear(); cmd.Parameters.Add(new SQLiteParameter("@rowid", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("@progNr", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("@fav1", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("@fav2", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("@fav3", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("@fav4", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("@lock", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("@skip", DbType.Int32)); cmd.Prepare(); foreach (ChannelInfo channelInfo in channelList.Channels) { var channel = channelInfo as DbChannel; if (channel == null) // skip reference list proxy channels continue; if (channel.NewProgramNr < 0 || channel.OldProgramNr < 0) continue; cmd.Parameters["@rowid"].Value = channel.RecordIndex; cmd.Parameters["@progNr"].Value = channel.NewProgramNr; for (int fav = 0; fav < 4; fav++) cmd.Parameters["@fav" + (fav + 1)].Value = Math.Max(0, channel.FavIndex[fav]); cmd.Parameters["@lock"].Value = channel.Lock; cmd.Parameters["@skip"].Value = channel.Skip; cmd.ExecuteNonQuery(); } // delete unassigned channels cmd.CommandText = "delete from SVL where rowid=@rowid"; cmd.Parameters.Clear(); cmd.Parameters.Add(new SQLiteParameter("@rowid", DbType.Int32)); foreach (ChannelInfo channel in channelList.Channels) { if (channel.NewProgramNr == -1 && channel.OldProgramNr >= 0) { cmd.Parameters["@rowid"].Value = channel.RecordIndex; cmd.ExecuteNonQuery(); } } } #endregion #region WriteCypheredFile() private void WriteCypheredFile() { switch (this.cypherMode) { case CypherMode.None: break; case CypherMode.Encryption: this.CypherFile(this.workFile, this.FileName, true); break; case CypherMode.HeaderAndChecksum: this.WriteFileWithHeaderAndChecksum(); break; } } #endregion #region WriteFileWithHeaderAndChecksum() private void WriteFileWithHeaderAndChecksum() { long workFileSize = new FileInfo(this.workFile).Length; byte[] data = new byte[this.fileHeader.Length + workFileSize + 4]; Array.Copy(fileHeader, data, fileHeader.Length); using (var stream = new FileStream(this.workFile, FileMode.Open, FileAccess.Read)) stream.Read(data, fileHeader.Length, (int)workFileSize); Tools.SetInt32(data, this.dbSizeOffset, (int)workFileSize, this.littleEndianByteOrder); this.UpdateChecksum(data); using (var stream = new FileStream(this.FileName, FileMode.Create, FileAccess.Write)) stream.Write(data, 0, data.Length); } #endregion #region UpdateChecksum() private void UpdateChecksum(byte[] data) { uint checksum = this.CalcChecksum(data, data.Length - 4); data[data.Length - 1] = (byte)(checksum & 0xFF); data[data.Length - 2] = (byte)((checksum >> 8) & 0xFF); data[data.Length - 3] = (byte)((checksum >> 16) & 0xFF); data[data.Length - 4] = (byte)((checksum >> 24) & 0xFF); } #endregion #region GetFileInformation() public override string GetFileInformation() { StringBuilder sb = new StringBuilder(); sb.Append(base.GetFileInformation()); sb.Append("Content type: "); switch (this.GetCypherMode(this.FileName)) { case CypherMode.None: sb.AppendLine("unencrypted SQLite database"); break; case CypherMode.Encryption: sb.AppendLine("encrypted SQLite database"); break; case CypherMode.HeaderAndChecksum: sb.AppendLine("embedded SQLite database"); sb.Append("Byte order: ").AppendLine(this.littleEndianByteOrder ? "little-endian (least significant byte first)" : "big-endian (most significant byte first)"); break; } sb.Append("Character encoding: ").AppendLine(this.charEncoding); return sb.ToString(); } #endregion } }