diff --git a/readme.md b/readme.md index 1e95320..9f43367 100644 --- a/readme.md +++ b/readme.md @@ -78,6 +78,10 @@ System requirements "Install Windows DLL or component" and install the "dotnet48" package and ignore dozens of message boxes - right-click on ChanSort.exe and select "open with", "all applications", "A wine application" +**Mac** +- macOS is not directly supported, but you can use Parallels or UTM to set up a VM with Windows 10/11 on the Mac +- instructions for Macs with m1/ARM CPU: https://history-computer.com/how-to-run-windows-on-m1-macs/ + **Hardware**: - USB stick/SD-card to transfer the channel list between your TV and PC. A stick <= 32 GB with FAT32 file system is STRONGLY recommended. (Some TVs write garbage to NTFS and don't support exFAT at all) diff --git a/readme_de.md b/readme_de.md index bccdf8e..bf2fb85 100644 --- a/readme_de.md +++ b/readme_de.md @@ -78,6 +78,10 @@ Systemvoraussetzungen "Installiere Windows DLL oder Komponente", installiere das "dotnet48" Paket and ignore dutzende Popup-Dialoge - Rechtsklick auf ChanSort.exe, wähle "Öffnen mit", "Alle Anwendungen", "Eine wine Anwendung" +**Mac** +- macOS wird nicht direkt unterstützt, aber mit Parallels oder UTM kann eine VM mit Windows 10/11 am Mac genutzt werden +- Anleitung für Macs mit m1/ARM CPU: https://history-computer.com/how-to-run-windows-on-m1-macs/ + **Hardware**: - USB Stick/SD-Karte zur Übertragung der Senderliste zwischen TV und PC (Ein Stick <= 32 GB mit FAT32-Formatierung ist DRINGEND empfohlen. (Einige TVs schreiben Müll auf NTFS bzw. unterstützen exFAT gar nicht) diff --git a/readme_tr-TR.md b/readme_tr-TR.md index a8163ba..f7a19ff 100644 --- a/readme_tr-TR.md +++ b/readme_tr-TR.md @@ -73,6 +73,10 @@ Sistem Gereksinimleri - winetricks'i başlatın, wineprefix'i seçin ya da oluşturun (32 bit ya da 64 bit), "Install Windows DLL or component"i seçin ve "dotnet48" paketini yükleyin, bu sırada çıkan düzinelerce uyarı mesajını görmezden gelin - ChanSort.exe'ye sağ tıklayın ve "open with", "all applications", "A wine application" sırasınca seçin +**Mac** +- macOS doğrudan desteklenmez, ancak Mac'te Windows 10/11 ile bir VM kurmak için Parallels veya UTM kullanabilirsiniz +- m1/ARM CPU'lu Mac'ler için talimatlar: https://history-computer.com/how-to-run-windows-on-m1-macs/ + **Donanım**: - TV'niz ile PC'niz arasında kanal listesini aktarabilmek için USB bellek/SD-kart. En azından 32 GB bir bellek şiddetle tavsiye olunur. (Bazı TV'ler çöplerini NTFS dosya ssitemine yazar ve exFAT'ı desteklemezler bile.) diff --git a/source/ChanSort.Loader.TCL/ChanSort.Loader.TCL.csproj b/source/ChanSort.Loader.TCL/ChanSort.Loader.TCL.csproj index 0daaa28..8b50fb7 100644 --- a/source/ChanSort.Loader.TCL/ChanSort.Loader.TCL.csproj +++ b/source/ChanSort.Loader.TCL/ChanSort.Loader.TCL.csproj @@ -7,7 +7,6 @@ - diff --git a/source/ChanSort.Loader.TCL/DtvDataSerializer.cs b/source/ChanSort.Loader.TCL/DtvDataSerializer.cs index f9514cf..e6166c3 100644 --- a/source/ChanSort.Loader.TCL/DtvDataSerializer.cs +++ b/source/ChanSort.Loader.TCL/DtvDataSerializer.cs @@ -1,20 +1,8 @@ -//#define Win10_TAR -#define GNU_TAR -//#define TestBuild +//#define TestBuild using System.Text; using Microsoft.Data.Sqlite; using ChanSort.Api; -#if Win10_TAR -using System.Diagnostics; -#elif GNU_TAR -#else -using SharpCompress.Archives; -using SharpCompress.Archives.Tar; -using SharpCompress.Common; -using SharpCompress.Readers; -using SharpCompress.Writers.Tar; -#endif namespace ChanSort.Loader.TCL { @@ -22,9 +10,22 @@ namespace ChanSort.Loader.TCL * This class loads TCL / Thomson channel lists from a directory or a .tar file containing cloneCRC.bin, DtvData.db and satellite.db. * * None of the sample files contained more than a single input source (DVB-C/T/S), so for the time being this loader puts everything into a single list + * + * When a channel is hidden through the TV's menu, it will result in: EditFlag |= 0x08, unlockedFlag=1, IsSkipped=1 + * When a channel is added to favorites, it will: EditFlag |= 0x01, IsFavor=1, but will keep FavChannelNo=65535 + * */ class DtvDataSerializer : SerializerBase { + private const int CrcMaxDataLength = 0x4B000; + + [Flags] + enum EditFlags + { + Favorite = 0x01, + Skip = 0x08 + } + private readonly ChannelList channels = new (SignalSource.All, "All"); private string dbDir; private string dtvFile; @@ -34,9 +35,7 @@ namespace ChanSort.Loader.TCL private readonly HashSet tableNames = new(); private readonly StringBuilder protocol = new(); -#if GNU_TAR - private GnuTar gnuTar; -#endif + private GnuTar tar; #region ctor() public DtvDataSerializer(string inputFile) : base(inputFile) @@ -45,19 +44,18 @@ namespace ChanSort.Loader.TCL #if TestBuild this.Features.DeleteMode = DeleteMode.NotSupported; #else - this.Features.DeleteMode = DeleteMode.Physically; + this.Features.DeleteMode = DeleteMode.FlagWithoutPrNr; #endif - this.Features.CanSkipChannels = false; - this.Features.CanLockChannels = false; + this.Features.CanSkipChannels = true; + this.Features.CanLockChannels = true; this.Features.CanHideChannels = true; this.Features.FavoritesMode = FavoritesMode.Flags; this.Features.MaxFavoriteLists = 1; this.DataRoot.AddChannelList(this.channels); - channels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.Skip)); - channels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.Encrypted)); channels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.AudioPid)); channels.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.Provider)); + channels.VisibleColumnFieldNames.Add(nameof(ChannelInfo.ServiceType)); channels.VisibleColumnFieldNames.Add(nameof(ChannelInfo.Source)); } #endregion @@ -139,22 +137,8 @@ namespace ChanSort.Loader.TCL this.TempPath = Path.Combine(Path.GetTempPath(), "ChanSort_" + DateTime.Now.ToString("yyyyMMdd-HHmmss")); Directory.CreateDirectory(this.TempPath); -#if Win10_TAR - var psi = new ProcessStartInfo("tar"); - psi.UseShellExecute = true; - psi.WindowStyle = ProcessWindowStyle.Hidden; - psi.WorkingDirectory = this.TempPath; - psi.Arguments = $"xf \"{this.FileName}\""; - var proc = Process.Start(psi); - proc.WaitForExit(); -#elif GNU_TAR - this.gnuTar = new GnuTar(); - gnuTar.ExtractToDirectory(this.FileName, this.TempPath); -#else - using var tar = TarArchive.Open(this.FileName); - var rdr = tar.ExtractAllEntries(); - rdr.WriteAllToDirectory(this.TempPath, new ExtractionOptions { ExtractFullPath = true }); -#endif + this.tar = new GnuTar(); + tar.ExtractToDirectory(this.FileName, this.TempPath); } #endregion @@ -168,13 +152,13 @@ namespace ChanSort.Loader.TCL var crc = Crc16.CCITT; var data = File.ReadAllBytes(dtvFile); - var actual = crc.Calc(data); + var actual = crc.Calc(data, 0, Math.Min(data.Length, CrcMaxDataLength)); var expected = BitConverter.ToUInt16(crcData, 2); if (actual != expected) { var msg = $"Invalid CRC16-CCITT check sum for {dtvFile}. Expected {expected:X4} but calculated {actual:X4}"; protocol.AppendLine(msg); - //throw LoaderException.Fail(msg); + throw LoaderException.Fail(msg); } if (satFile != null) @@ -186,7 +170,7 @@ namespace ChanSort.Loader.TCL { var msg = $"Invalid CRC16-CCITT check sum for {satFile}. Expected {expected:X4} but calculated {actual:X4}"; protocol.AppendLine(msg); - //throw LoaderException.Fail(msg); + throw LoaderException.Fail(msg); } } } @@ -287,12 +271,14 @@ namespace ChanSort.Loader.TCL #region ReadChannels() private void ReadChannels(SqliteCommand cmd) { - cmd.CommandText = $@" + cmd.CommandText = @" select p.u32Index, p.ProgNum, p.ServiceName, p.ShortServiceName, p.ServiceID, p.VideoType, p.PCRPID, p.VideoPID, p.unlockedFlag, p.LCN, p.LCNAssignmentType, p.EditFlag, m.OriginalNetworkId, m.TransportStreamId, m.Freq, m.SymbolRate, - c.RouteName + c.RouteName, + a.RealServiceType, a.IsScramble, a.VisibleFlag, a.IsDelete, a.IsSkipped, a.IsLock, a.IsFavor, a.IsRename, a.IsMove, a.NumSelectFlag, a.FavChannelNo from ProgramInfoTbl p +left outer join AtrributeTbl a on a.u32index=p.u32index left outer join MuxInfoTbl m on m.u16MuxTblID=p.u16MuxTblID left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute "; @@ -303,7 +289,7 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute var handle = r.GetInt32(0); var oldProgNr = r.GetInt32(1); if (oldProgNr == 65535) - continue; + oldProgNr = -1; var name = r.GetString(2)?.TrimEnd(' ', '\0'); ChannelInfo channel = new ChannelInfo(0, handle, oldProgNr, name); @@ -314,13 +300,15 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute channel.PcrPid = r.GetInt32(6); channel.VideoPid = r.GetInt32(7); channel.Hidden = r.GetBoolean(8); - var edit = r.GetInt32(11); - channel.Favorites = (edit & 0x01) != 0 ? Favorites.A : 0; - channel.AddDebug($"LCN={r.GetValue(9)}, AT={r.GetValue(10)}, Edit={edit:x4}"); + var edit = (EditFlags)r.GetInt32(11); + channel.Favorites = (edit & EditFlags.Favorite) != 0 ? Favorites.A : 0; + channel.Skip = (edit & EditFlags.Skip) != 0; + channel.AddDebug($"LCN={r.GetValue(9)}, edit={(int)edit:x4}"); // DVB var ixD = 12; var ixC = ixD + 4; + var ixA = ixC + 1; if (!r.IsDBNull(ixD)) { channel.OriginalNetworkId = r.GetInt32(ixD + 0); @@ -332,7 +320,24 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute channel.Source = r.GetString(ixC); } - if (!channel.IsDeleted) + // AtrributeTbl (actual typo in the TV's table name!) + if (!r.IsDBNull(ixA)) + { + channel.ServiceType = r.GetInt32(ixA + 0); + channel.ServiceTypeName = LookupData.Instance.GetServiceTypeDescription(channel.ServiceType); + channel.SignalSource |= LookupData.Instance.IsRadioTvOrData(channel.ServiceType); + channel.Encrypted = r.GetInt32(ixA + 1) != 0; + channel.Hidden = !r.GetBoolean(ixA + 2); + channel.IsDeleted |= r.GetBoolean(ixA + 3); + channel.Skip |= r.GetBoolean(ixA + 4); + channel.Lock = r.GetBoolean(ixA + 5); + if (r.GetBoolean(ixA + 6)) + channel.Favorites |= Favorites.A; + channel.IsNameModified = r.GetBoolean(ixA + 7); + channel.AddDebug($", FavChannelNo={r.GetInt32(ixA + 10)}"); + } + + //if (!channel.IsDeleted) this.DataRoot.AddChannel(this.channels, channel); } } @@ -348,11 +353,7 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute conn.Open(); using var trans = conn.BeginTransaction(); using var cmd = conn.CreateCommand(); -#if TestBuild - SqliteCommand cmd2 = null; -#else using var cmd2 = conn.CreateCommand(); -#endif this.WriteChannels(cmd, cmd2, this.channels); trans.Commit(); @@ -368,11 +369,11 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute #endregion #region WriteChannels() - private void WriteChannels(SqliteCommand cmd, SqliteCommand cmdDelete, ChannelList channelList) + private void WriteChannels(SqliteCommand cmd, SqliteCommand cmdAttrib, ChannelList channelList) { cmd.CommandText = "update PrograminfoTbl set ProgNum=@nr" #if !TestBuild - + ", ServiceName=@name, unlockedFlag=@hide, EditFlag=(EditFlag & 0xFFFFFFFE) | @editflag" + + ", ServiceName=@name, unlockedFlag=@hide, EditFlag=(EditFlag & " + ~(EditFlags.Favorite | EditFlags.Skip) + ") | @editflag" #endif + " where u32Index=@handle"; cmd.Parameters.Add("@handle", SqliteType.Integer); @@ -385,9 +386,16 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute cmd.Prepare(); #if !TestBuild - cmdDelete.CommandText = @"delete from PrograminfoTbl where u32Index=@handle;"; - cmdDelete.Parameters.Add("@handle", SqliteType.Integer); - cmdDelete.Prepare(); + cmdAttrib.CommandText = @"update AtrributeTbl set VisibleFlag=@vis, IsDelete=@del, IsMove=IsMove | @mov, IsSkipped=@skip, IsLock=@lock, IsRename=@ren, IsFavor=@fav where u32Index=@handle;"; + cmdAttrib.Parameters.Add("@handle", SqliteType.Integer); + cmdAttrib.Parameters.Add("@vis", SqliteType.Integer); + cmdAttrib.Parameters.Add("@del", SqliteType.Integer); + cmdAttrib.Parameters.Add("@mov", SqliteType.Integer); + cmdAttrib.Parameters.Add("@skip", SqliteType.Integer); + cmdAttrib.Parameters.Add("@lock", SqliteType.Integer); + cmdAttrib.Parameters.Add("@ren", SqliteType.Integer); + cmdAttrib.Parameters.Add("@fav", SqliteType.Integer); + cmdAttrib.Prepare(); #endif foreach (ChannelInfo channel in channelList.Channels) @@ -395,28 +403,28 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute if (channel.IsProxy) // ignore reference list proxy channels continue; - if (channel.IsDeleted) - { + channel.UpdateRawData(); + cmd.Parameters["@handle"].Value = channel.RecordIndex; + cmd.Parameters["@nr"].Value = channel.IsDeleted ? 65535 : channel.NewProgramNr; #if !TestBuild - cmdDelete.Parameters["@handle"].Value = channel.RecordIndex; - cmdDelete.ExecuteNonQuery(); + var bytes = Encoding.UTF8.GetBytes(channel.Name); + var blob = new byte[64]; + Tools.MemCopy(bytes, 0, blob, 0, 64); + cmd.Parameters["@name"].Value = blob; + cmd.Parameters["@hide"].Value = channel.Hidden; + cmd.Parameters["@editflag"].Value = (int)( (channel.Favorites == 0 ? 0 : EditFlags.Favorite) | (channel.Skip ? EditFlags.Skip : 0) ); + + cmdAttrib.Parameters["@handle"].Value = channel.RecordIndex; + cmdAttrib.Parameters["@vis"].Value = channel.Hidden ? 0 : 1; + cmdAttrib.Parameters["@del"].Value = channel.IsDeleted ? 1 : 0; + cmdAttrib.Parameters["@skip"].Value = channel.Skip ? 1 : 0; + cmdAttrib.Parameters["@lock"].Value = channel.Lock ? 1 : 0; + cmdAttrib.Parameters["@ren"].Value = channel.IsNameModified ? 1 : 0; + cmdAttrib.Parameters["@mov"].Value = channel.OldProgramNr != channel.NewProgramNr ? 1 : 0; + cmdAttrib.Parameters["@fav"].Value = channel.Favorites != 0 ? 1 : 0; + cmdAttrib.ExecuteNonQuery(); #endif - } - else - { - channel.UpdateRawData(); - cmd.Parameters["@handle"].Value = channel.RecordIndex; - cmd.Parameters["@nr"].Value = channel.NewProgramNr; -#if !TestBuild - var bytes = Encoding.UTF8.GetBytes(channel.Name); - var blob = new byte[64]; - Tools.MemCopy(bytes, 0, blob, 0, 64); - cmd.Parameters["@name"].Value = blob; - cmd.Parameters["@hide"].Value = channel.Hidden; - cmd.Parameters["@editflag"].Value = channel.Favorites == 0 ? 0 : 0x0001; -#endif - cmd.ExecuteNonQuery(); - } + cmd.ExecuteNonQuery(); } } #endregion @@ -431,7 +439,7 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute return; var dtvData = File.ReadAllBytes(dtvFile); - var crc = Crc16.CCITT.Calc(dtvData); + var crc = Crc16.CCITT.Calc(dtvData, 0, Math.Min(dtvData.Length, CrcMaxDataLength)); var crcData = File.ReadAllBytes(this.crcFile); crcData[2] = (byte)(crc & 0xFF); crcData[3] = (byte)(crc >> 8); @@ -444,21 +452,7 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute { // delete old .tar file and create a new one from temp dir File.Delete(this.FileName); -#if Win10_TAR - var psi = new ProcessStartInfo("tar"); - psi.UseShellExecute = true; - psi.WindowStyle = ProcessWindowStyle.Hidden; - psi.WorkingDirectory = this.TempPath; - psi.Arguments = $"cf \"{this.FileName}\" *"; - var proc = Process.Start(psi); - proc.WaitForExit(); -#elif GNU_TAR - this.gnuTar.UpdateFromDirectory(this.FileName); -#else - using var tar = TarArchive.Create(); - tar.AddAllFromDirectory(this.TempPath); - tar.SaveTo(this.FileName, new TarWriterOptions(CompressionType.None, true)); -#endif + this.tar.UpdateFromDirectory(this.FileName); } #endregion } diff --git a/source/ChanSort.Loader.TCL/GnuTar.cs b/source/ChanSort.Loader.TCL/GnuTar.cs index 33668ca..f2aab9e 100644 --- a/source/ChanSort.Loader.TCL/GnuTar.cs +++ b/source/ChanSort.Loader.TCL/GnuTar.cs @@ -224,7 +224,9 @@ internal class GnuTar strm.Write(" "u8.ToArray(), 0, 8); // placeholder for checksum strm.WriteByte((byte)e.TypeFlag); WriteString(strm, e.LinkName, 100, this.Encoding); - strm.Write(Encoding.ASCII.GetBytes(e.Magic), 0, 6); + var bytes = Encoding.ASCII.GetBytes(e.Magic); + strm.Write(bytes, 0, Math.Min(bytes.Length, 6)); + strm.Write(Padding, 0, 6 - bytes.Length); WriteString(strm, e.Version, 2); WriteString(strm, e.Username, 32); WriteString(strm, e.Groupname, 32);