Beispiel #1
0
 private void WriteStringBuffer()
 {
     m_tableheaders[0].crc32 = Crc32Algorithm.Compute(m_strings);
     m_stream.Write(m_strings, 0, m_strings.Length);
 }
Beispiel #2
0
        private void ProcessIncoming(byte[] localMsg, IPEndPoint clientEP)
        {
            try {
                int currIdx = 0;
                if (localMsg[0] != 'D' || localMsg[1] != 'S' || localMsg[2] != 'U' || localMsg[3] != 'C')
                {
                    return;
                }
                else
                {
                    currIdx += 4;
                }

                uint protocolVer = BitConverter.ToUInt16(localMsg, currIdx);
                currIdx += 2;

                if (protocolVer > MaxProtocolVersion)
                {
                    return;
                }

                uint packetSize = BitConverter.ToUInt16(localMsg, currIdx);
                currIdx += 2;

                if (packetSize < 0)
                {
                    return;
                }

                packetSize += 16;                 //size of header
                if (packetSize > localMsg.Length)
                {
                    return;
                }
                else if (packetSize < localMsg.Length)
                {
                    byte[] newMsg = new byte[packetSize];
                    Array.Copy(localMsg, newMsg, packetSize);
                    localMsg = newMsg;
                }

                uint crcValue = BitConverter.ToUInt32(localMsg, currIdx);
                //zero out the crc32 in the packet once we got it since that's whats needed for calculation
                localMsg[currIdx++] = 0;
                localMsg[currIdx++] = 0;
                localMsg[currIdx++] = 0;
                localMsg[currIdx++] = 0;

                uint crcCalc = Crc32Algorithm.Compute(localMsg);
                if (crcValue != crcCalc)
                {
                    return;
                }

                uint clientId = BitConverter.ToUInt32(localMsg, currIdx);
                currIdx += 4;

                uint messageType = BitConverter.ToUInt32(localMsg, currIdx);
                currIdx += 4;

                if (messageType == (uint)MessageType.DSUC_VersionReq)
                {
                    byte[] outputData = new byte[8];
                    int    outIdx     = 0;
                    Array.Copy(BitConverter.GetBytes((uint)MessageType.DSUS_VersionRsp), 0, outputData, outIdx, 4);
                    outIdx += 4;
                    Array.Copy(BitConverter.GetBytes((ushort)MaxProtocolVersion), 0, outputData, outIdx, 2);
                    outIdx += 2;
                    outputData[outIdx++] = 0;
                    outputData[outIdx++] = 0;

                    SendPacket(clientEP, outputData, 1001);
                }
                else if (messageType == (uint)MessageType.DSUC_ListPorts)
                {
                    // Requested information on gamepads - return MAC address
                    int numPadRequests = BitConverter.ToInt32(localMsg, currIdx);
                    currIdx += 4;
                    if (numPadRequests < 0 || numPadRequests > 4)
                    {
                        return;
                    }

                    int requestsIdx = currIdx;
                    for (int i = 0; i < numPadRequests; i++)
                    {
                        byte currRequest = localMsg[requestsIdx + i];
                        if (currRequest < 0 || currRequest > 4)
                        {
                            return;
                        }
                    }

                    byte[] outputData = new byte[16];
                    for (byte i = 0; i < numPadRequests; i++)
                    {
                        byte currRequest = localMsg[requestsIdx + i];
                        var  padData     = controllers[i];                   //controllers[currRequest];

                        int outIdx = 0;
                        Array.Copy(BitConverter.GetBytes((uint)MessageType.DSUS_PortInfo), 0, outputData, outIdx, 4);
                        outIdx += 4;

                        outputData[outIdx++] = (byte)padData.PadId;
                        outputData[outIdx++] = (byte)padData.constate;
                        outputData[outIdx++] = (byte)padData.model;
                        outputData[outIdx++] = (byte)padData.connection;

                        var addressBytes = padData.PadMacAddress.GetAddressBytes();
                        if (addressBytes.Length == 6)
                        {
                            outputData[outIdx++] = addressBytes[0];
                            outputData[outIdx++] = addressBytes[1];
                            outputData[outIdx++] = addressBytes[2];
                            outputData[outIdx++] = addressBytes[3];
                            outputData[outIdx++] = addressBytes[4];
                            outputData[outIdx++] = addressBytes[5];
                        }
                        else
                        {
                            outputData[outIdx++] = 0;
                            outputData[outIdx++] = 0;
                            outputData[outIdx++] = 0;
                            outputData[outIdx++] = 0;
                            outputData[outIdx++] = 0;
                            outputData[outIdx++] = 0;
                        }

                        outputData[outIdx++] = (byte)padData.battery;                        //(byte)padData.BatteryStatus;
                        outputData[outIdx++] = 0;

                        SendPacket(clientEP, outputData, 1001);
                    }
                }
                else if (messageType == (uint)MessageType.DSUC_PadDataReq)
                {
                    byte            regFlags = localMsg[currIdx++];
                    byte            idToReg  = localMsg[currIdx++];
                    PhysicalAddress macToReg = null;
                    {
                        byte[] macBytes = new byte[6];
                        Array.Copy(localMsg, currIdx, macBytes, 0, macBytes.Length);
                        currIdx += macBytes.Length;
                        macToReg = new PhysicalAddress(macBytes);
                    }

                    lock (clients) {
                        if (clients.ContainsKey(clientEP))
                        {
                            clients[clientEP].RequestPadInfo(regFlags, idToReg, macToReg);
                        }
                        else
                        {
                            var clientTimes = new ClientRequestTimes();
                            clientTimes.RequestPadInfo(regFlags, idToReg, macToReg);
                            clients[clientEP] = clientTimes;
                        }
                    }
                }
            } catch (Exception e) { }
        }
Beispiel #3
0
        public static Ird Parse(byte[] content)
        {
            if (content == null)
            {
                throw new ArgumentNullException(nameof(content));
            }

            if (content.Length < 200)
            {
                throw new ArgumentException("Data is too small to be a valid IRD structure", nameof(content));
            }

            if (BitConverter.ToInt32(content, 0) != Ird.Magic)
            {
                using (var compressedStream = new MemoryStream(content, false))
                    using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
                        using (var decompressedStream = new MemoryStream())
                        {
                            gzip.CopyTo(decompressedStream);
                            content = decompressedStream.ToArray();
                        }
            }
            if (BitConverter.ToInt32(content, 0) != Ird.Magic)
            {
                throw new FormatException("Not a valid IRD file");
            }

            var result = new Ird();

            using (var stream = new MemoryStream(content, false))
                using (var reader = new BinaryReader(stream, Encoding.UTF8))
                {
                    reader.ReadInt32(); // magic
                    result.Version       = reader.ReadByte();
                    result.ProductCode   = Encoding.ASCII.GetString(reader.ReadBytes(9));
                    result.TitleLength   = reader.ReadByte();
                    result.Title         = Encoding.UTF8.GetString(reader.ReadBytes(result.TitleLength));
                    result.UpdateVersion = Encoding.ASCII.GetString(reader.ReadBytes(4)).Trim();
                    result.GameVersion   = Encoding.ASCII.GetString(reader.ReadBytes(5)).Trim();
                    result.AppVersion    = Encoding.ASCII.GetString(reader.ReadBytes(5)).Trim();
                    if (result.Version == 7)
                    {
                        result.Id = reader.ReadInt32();
                    }
                    result.HeaderLength       = reader.ReadInt32();
                    result.Header             = reader.ReadBytes(result.HeaderLength);
                    result.FooterLength       = reader.ReadInt32();
                    result.Footer             = reader.ReadBytes(result.FooterLength);
                    result.RegionCount        = reader.ReadByte();
                    result.RegionMd5Checksums = new List <byte[]>(result.RegionCount);
                    for (var i = 0; i < result.RegionCount; i++)
                    {
                        result.RegionMd5Checksums.Add(reader.ReadBytes(16));
                    }
                    result.FileCount = reader.ReadInt32();
                    result.Files     = new List <IrdFile>(result.FileCount);
                    for (var i = 0; i < result.FileCount; i++)
                    {
                        var file = new IrdFile();
                        file.Offset      = reader.ReadInt64();
                        file.Md5Checksum = reader.ReadBytes(16);
                        result.Files.Add(file);
                    }
                    result.Unknown = reader.ReadInt32();
                    if (result.Version == 9)
                    {
                        result.Pic = reader.ReadBytes(115);
                    }
                    result.Data1 = reader.ReadBytes(16);
                    result.Data2 = reader.ReadBytes(16);
                    if (result.Version < 9)
                    {
                        result.Pic = reader.ReadBytes(115);
                    }
                    result.Uid = reader.ReadInt32();
                    var dataLength = reader.BaseStream.Position;
                    result.Crc32 = reader.ReadUInt32();

                    var crc32 = Crc32Algorithm.Compute(content, 0, (int)dataLength);
                    if (result.Crc32 != crc32)
                    {
                        throw new InvalidDataException($"Corrupted IRD data, expected {result.Crc32:x8}, but was {crc32:x8}");
                    }
                }
            return(result);
        }
Beispiel #4
0
        /// <summary>
        /// Schedule Agent
        /// </summary>
        private void scheduleAgent()
        {
            try
            {
                if (!ShouldCheckSchedule())
                {
                    ClientInfo.Instance.ScheduleStatus = "Sleeping: last check was not required.";
                    return;
                }

                Trace.WriteLine(new LogMessage("ScheduleAgent - Run", "Thread Woken and Lock Obtained"), LogType.Audit.ToString());

                ClientInfo.Instance.ScheduleStatus = "Running: Get Data from Xibo Server";

                using (xmds.xmds xmds = new xmds.xmds())
                {
                    xmds.Credentials           = null;
                    xmds.Url                   = ApplicationSettings.Default.XiboClient_xmds_xmds + "&method=schedule";
                    xmds.UseDefaultCredentials = false;

                    string scheduleXml = xmds.Schedule(ApplicationSettings.Default.ServerKey, _hardwareKey);

                    // Set the flag to indicate we have a connection to XMDS
                    ApplicationSettings.Default.XmdsLastConnection = DateTime.Now;

                    ClientInfo.Instance.ScheduleStatus = "Running: Data Received";

                    // Calculate and store a CRC32
                    _lastCheckSchedule = Crc32Algorithm.Compute(Encoding.UTF8.GetBytes(scheduleXml)).ToString();

                    // Hash of the result
                    // TODO: we can probably remove this at some point in the future, given later CMS instances output CRC32's to indicate whether
                    // the schedule has changed.
                    string md5NewSchedule     = Hashes.MD5(scheduleXml);
                    string md5CurrentSchedule = Hashes.MD5(ScheduleManager.GetScheduleXmlString(_scheduleLocation));

                    // Compare the results of the HASH
                    if (md5CurrentSchedule != md5NewSchedule)
                    {
                        Trace.WriteLine(new LogMessage("Schedule Agent - Run", "Received new schedule"));

                        ClientInfo.Instance.ScheduleStatus = "Running: New Schedule Received";

                        // Write the result to the schedule xml location
                        ScheduleManager.WriteScheduleXmlToDisk(_scheduleLocation, scheduleXml);

                        // Indicate to the schedule manager that it should read the XML file
                        _scheduleManager.RefreshSchedule = true;
                    }

                    ClientInfo.Instance.ScheduleStatus = "Sleeping";
                }
            }
            catch (WebException webEx)
            {
                // Increment the quantity of XMDS failures and bail out
                ApplicationSettings.Default.IncrementXmdsErrorCount();

                // Log this message, but dont abort the thread
                Trace.WriteLine(new LogMessage("ScheduleAgent - Run", "WebException in Run: " + webEx.Message), LogType.Info.ToString());

                ClientInfo.Instance.ScheduleStatus = "Error: " + webEx.Message;
            }
            catch (Exception ex)
            {
                // Log this message, but dont abort the thread
                Trace.WriteLine(new LogMessage("ScheduleAgent - Run", "Exception in Run: " + ex.Message), LogType.Error.ToString());
                ClientInfo.Instance.ScheduleStatus = "Error. " + ex.Message;
            }
        }
Beispiel #5
0
        /// <summary>
        /// Run Thread
        /// </summary>
        public void Run()
        {
            Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "Thread Started"), LogType.Info.ToString());

            int retryAfterSeconds = 0;

            while (!_forceStop)
            {
                // If we are restarting, reset
                _manualReset.Reset();

                // Reset backOff
                retryAfterSeconds = 0;

                lock (_locker)
                {
                    // Run the schedule Agent thread
                    scheduleAgent();

                    if (ApplicationSettings.Default.InDownloadWindow)
                    {
                        try
                        {
                            int filesToDownload = _requiredFiles.FilesDownloading;

                            // If we are currently downloading something, we have to wait
                            if (filesToDownload > 0)
                            {
                                ClientInfo.Instance.RequiredFilesStatus = string.Format("Waiting: {0} Active Downloads", filesToDownload.ToString());

                                Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "Currently Downloading Files, skipping collect"), LogType.Audit.ToString());
                            }
                            else if (!ShouldCheckRf())
                            {
                                ClientInfo.Instance.RequiredFilesStatus = "Sleeping: last check was not required.";
                            }
                            else
                            {
                                ClientInfo.Instance.RequiredFilesStatus = "Running: Requesting connection to Xibo Server";

                                using (xmds.xmds xmds = new xmds.xmds())
                                {
                                    xmds.Credentials           = null;
                                    xmds.Url                   = ApplicationSettings.Default.XiboClient_xmds_xmds + "&method=requiredFiles";
                                    xmds.UseDefaultCredentials = false;

                                    // Get required files from XMDS
                                    string requiredFilesXml = xmds.RequiredFiles(ApplicationSettings.Default.ServerKey, _hardwareKey);

                                    // Set the flag to indicate we have a connection to XMDS
                                    ApplicationSettings.Default.XmdsLastConnection = DateTime.Now;

                                    ClientInfo.Instance.RequiredFilesStatus = "Running: Data received from Xibo Server";

                                    // Calculate and store a CRC32
                                    _lastCheckRf = Crc32Algorithm.Compute(Encoding.UTF8.GetBytes(requiredFilesXml)).ToString();

                                    // Load the XML file RF call
                                    XmlDocument xml = new XmlDocument();
                                    xml.LoadXml(requiredFilesXml);

                                    // Create a required files object and set it to contain the RF returned this tick
                                    _requiredFiles = new RequiredFiles();
                                    _requiredFiles.RequiredFilesXml = xml;

                                    // List of Threads to start
                                    // TODO: Track these threads so that we can abort them if the application closes
                                    List <Thread> threadsToStart = new List <Thread>();

                                    // Required files now contains a list of files to download (this will be updated by the various worker threads)
                                    foreach (RequiredFile fileToDownload in _requiredFiles.RequiredFileList)
                                    {
                                        // Skip downloaded files
                                        if (fileToDownload.Complete)
                                        {
                                            continue;
                                        }

                                        // Spawn a thread to download this file.
                                        FileAgent fileAgent = new FileAgent();
                                        fileAgent.FileDownloadLimit = _fileDownloadLimit;
                                        fileAgent.HardwareKey       = _hardwareKey;
                                        fileAgent.RequiredFiles     = _requiredFiles;
                                        fileAgent.RequiredFileId    = fileToDownload.Id;
                                        fileAgent.RequiredFileType  = fileToDownload.FileType;
                                        fileAgent.OnComplete       += new FileAgent.OnCompleteDelegate(fileAgent_OnComplete);
                                        fileAgent.OnPartComplete   += new FileAgent.OnPartCompleteDelegate(fileAgent_OnPartComplete);

                                        // Create the thread and add it to the list of threads to start
                                        Thread thread = new Thread(new ThreadStart(fileAgent.Run));
                                        thread.Name = "FileAgent_" + fileToDownload.FileType + "_Id_" + fileToDownload.Id.ToString();
                                        threadsToStart.Add(thread);
                                    }

                                    // Start the threads after we have built them all - otherwise they will modify the collection we
                                    // are iterating over.
                                    foreach (Thread thread in threadsToStart)
                                    {
                                        thread.Start();
                                    }

                                    // Report what we are doing back to MediaInventory
                                    _requiredFiles.ReportInventory();

                                    // Write Required Files
                                    _requiredFiles.WriteRequiredFiles();

                                    // Write the Cache Manager to Disk
                                    CacheManager.Instance.WriteCacheManager();

                                    // Set the status on the client info screen
                                    if (threadsToStart.Count == 0)
                                    {
                                        ClientInfo.Instance.RequiredFilesStatus = "Sleeping (inside download window)";

                                        // Raise an event to say we've completed
                                        OnFullyProvisioned?.Invoke();
                                    }
                                    else
                                    {
                                        ClientInfo.Instance.RequiredFilesStatus = string.Format("{0} files to download", threadsToStart.Count.ToString());
                                    }

                                    ClientInfo.Instance.UpdateRequiredFiles(RequiredFilesString());
                                }
                            }
                        }
                        catch (WebException webEx) when(webEx.Response is HttpWebResponse httpWebResponse && (int)httpWebResponse.StatusCode == 429)
                        {
                            // Get the header for how long we ought to wait
                            retryAfterSeconds = webEx.Response.Headers["Retry-After"] != null?int.Parse(webEx.Response.Headers["Retry-After"]) : 120;

                            // Log it.
                            Trace.WriteLine(new LogMessage("LogAgent", "Run: 429 received, waiting for " + retryAfterSeconds + " seconds."), LogType.Info.ToString());
                        }
                        catch (WebException webEx)
                        {
                            // Increment the quantity of XMDS failures and bail out
                            ApplicationSettings.Default.IncrementXmdsErrorCount();

                            // Log this message, but dont abort the thread
                            Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "WebException in Run: " + webEx.Message), LogType.Info.ToString());

                            ClientInfo.Instance.RequiredFilesStatus = "Error: " + webEx.Message;
                        }
                        catch (Exception ex)
                        {
                            // Log this message, but dont abort the thread
                            Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "Exception in Run: " + ex.Message), LogType.Error.ToString());

                            ClientInfo.Instance.RequiredFilesStatus = "Error: " + ex.Message;
                        }
                    }
                    else
                    {
                        ClientInfo.Instance.RequiredFilesStatus = string.Format("Outside Download Window {0} - {1}", ApplicationSettings.Default.DownloadStartWindowTime.ToString(), ApplicationSettings.Default.DownloadEndWindowTime.ToString());
                    }
                }

                if (retryAfterSeconds > 0)
                {
                    // Sleep this thread until we've fulfilled our try after
                    _manualReset.WaitOne(retryAfterSeconds * 1000);
                }
                else
                {
                    // Sleep this thread until the next collection interval
                    _manualReset.WaitOne((int)(ApplicationSettings.Default.CollectInterval * ApplicationSettings.Default.XmdsCollectionIntervalFactor() * 1000));
                }
            }

            Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "Thread Stopped"), LogType.Info.ToString());
        }
        public static void MainPatch(JToken configJToken)
        {
            var initPath         = configJToken["initPath"].Value <string>();
            var initVersionPath  = configJToken["initVersionPath"].Value <string>();
            var patchPath        = configJToken["patchPath"].Value <string>();
            var patchVersionPath = configJToken["patchVersionPath"].Value <string>();

            var rootPath = Program.ProjectRootPath;


            #region 生成初始版本

            var initDirPath         = rootPath + "\\" + initPath;        //原版本resource文件夹路径
            var initVersionFilePath = rootPath + "\\" + initVersionPath; //生成的 init.txt 路径

            using (TextWriter writer = new StreamWriter(initVersionFilePath)) {
                var roots = Directory.GetDirectories(initDirPath);
                foreach (var root in roots)
                {
                    var files = Directory.GetFiles(root, "*.*", SearchOption.AllDirectories);
                    foreach (var file in files)
                    {
                        FileStream stream = new FileStream(file, FileMode.Open);
                        byte[]     buffer = new byte[stream.Length];
                        stream.Read(buffer, 0, (int)stream.Length);
                        stream.Close();
                        uint crc32 = Crc32Algorithm.Compute(buffer);
                        Console.WriteLine("处理文件:{0}-{1}", Path.GetFileName(file), crc32);

                        var fileRelatePath = file.Substring(initDirPath.Length + 1);
                        fileRelatePath = fileRelatePath.Replace('\\', '/');
                        writer.WriteLine("{0}:{1}", fileRelatePath, crc32);
                    }
                }
            }

            #endregion


            #region 生成patch文件

            //删除之前创建的
            string gangsterRes000 = "gangsterRes";// Program.VersionFolder;
            var    lastResDir     = rootPath + "\\" + gangsterRes000;
            if (Directory.Exists(lastResDir))
            {
                Directory.Delete(lastResDir, true);
            }

            var patchDirPath         = rootPath + "\\" + patchPath;        //新版本resource文件夹路径
            var patchVersionFilePath = rootPath + "\\" + patchVersionPath; //生成的 1.2.0.txt 路径

            Dictionary <string, uint> initFileCrc = new Dictionary <string, uint>();
            using (TextReader reader = new StreamReader(initVersionFilePath)) {
                while (true)
                {
                    var line = reader.ReadLine();
                    if (string.IsNullOrEmpty(line))
                    {
                        break;
                    }

                    var  parts          = line.Split(':');
                    var  fileRelatePath = parts[0];
                    uint crc32          = uint.Parse(parts[1]);

                    initFileCrc.Add(fileRelatePath, crc32);
                }
            }

            using (TextWriter patchWriter = new StreamWriter(patchVersionFilePath)) {
                var roots = Directory.GetDirectories(patchDirPath);
                foreach (var root in roots)
                {
                    var files = Directory.GetFiles(root, "*.*", SearchOption.AllDirectories);
                    foreach (var file in files)
                    {
                        FileStream stream = new FileStream(file, FileMode.Open);
                        byte[]     buffer = new byte[stream.Length];
                        stream.Read(buffer, 0, (int)stream.Length);
                        stream.Close();

                        uint crc32          = Crc32Algorithm.Compute(buffer);
                        var  fileRelatePath = file.Substring(patchDirPath.Length + 1);
                        fileRelatePath = fileRelatePath.Replace('\\', '/');
                        if (initFileCrc.ContainsKey(fileRelatePath))
                        {
                            if (initFileCrc[fileRelatePath] == crc32)
                            {
                                continue;
                            }
                        }
                        Console.WriteLine("新增Patch文件:{0}-{1}", Path.GetFileName(file), crc32);
                        patchWriter.WriteLine("{0}:{1}", fileRelatePath, crc32);

                        if (file.Contains("exml") || file.Contains("Web") || file.Contains("web"))
                        {
                            continue;
                        }

                        //复制修改的文件到指定文件夹
                        PatchOperation.CopyToNewPath(file, fileRelatePath);
                    }
                }
            }
            Console.WriteLine("生成Patch文件成功");

            #endregion
        }
Beispiel #7
0
        private static Status ProcessSingleFile(FileInfo zippedFileInfo, CheckInfo c)
        {
            BcfSource source          = new ZippedFileSource(zippedFileInfo);
            var       dirPath         = Path.Combine(zippedFileInfo.DirectoryName, "unzipped");
            var       unzippedDirInfo = new DirectoryInfo(dirPath);

            if (unzippedDirInfo.Exists)
            {
                source = new FolderSource(unzippedDirInfo);
            }
            else if (c.Options.CheckZipMatch)
            {
                Console.WriteLine($"MISMATCH\t{c.CleanName(zippedFileInfo)}\tUnzipped folder not found.");
                c.Status |= Status.ContentError;
            }

            // this checks the match between zipped and unzipped
            if (c.Options.CheckZipMatch)
            {
                bool rezip = false;
                using (var zip = ZipFile.OpenRead(zippedFileInfo.FullName))
                {
                    foreach (var entry in zip.Entries)
                    {
                        if (Path.EndsInDirectorySeparator(entry.FullName))
                        {
                            continue;
                        }
                        var onDisk     = Path.Combine(unzippedDirInfo.FullName, entry.FullName);
                        var onDiskFile = new FileInfo(onDisk);
                        if (!onDiskFile.Exists)
                        {
                            Console.WriteLine($"MISMATCH\t{c.CleanName(onDiskFile)}\tUncompressed file not found.");
                            c.Status |= Status.ContentMismatchError;
                            if (c.Options.WriteMismatch)
                            {
                                var zipCont      = ReadFully(entry.Open());
                                var mismatchName = onDiskFile.FullName;
                                File.WriteAllBytes(mismatchName, zipCont);
                                Console.WriteLine($"CHANGE\t{c.CleanName(onDiskFile)}\tFile written from zip.");
                            }
                            if (c.Options.ReZip)
                            {
                                rezip = true;
                                break;
                            }
                        }
                        else
                        {
                            var dskCont    = File.ReadAllBytes(onDiskFile.FullName);
                            var zipCont    = ReadFully(entry.Open());
                            var dskCrc     = Crc32Algorithm.Compute(dskCont);
                            var zipCrc     = Crc32Algorithm.Compute(zipCont);
                            var zipCrcFile = entry.Crc32;

                            if (dskCrc != zipCrc)
                            {
                                // might be the same with different line endings
                                System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
                                string dskS = Clean(enc.GetString(dskCont));
                                string zipS = Clean(enc.GetString(zipCont));
                                if (dskS != zipS)
                                {
                                    Console.WriteLine($"MISMATCH\t{c.CleanName(onDiskFile)}\tCompressed/Uncompressed mismatch.");
                                    c.Status |= Status.ContentMismatchError;
                                    if (c.Options.WriteMismatch)
                                    {
                                        var mismatchName = onDiskFile.FullName + ".zipMismatch";
                                        File.WriteAllBytes(mismatchName, zipCont);
                                        Console.WriteLine($"CHANGE\t{c.CleanName(onDiskFile)}.zipMismatch\tFile written from zip for comparison.");
                                    }
                                    if (c.Options.ReZip)
                                    {
                                        rezip = true;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
                if (rezip)
                {
                    vbio.FileSystem.DeleteFile(zippedFileInfo.FullName, vbio.UIOption.OnlyErrorDialogs, vbio.RecycleOption.SendToRecycleBin);
                    ZipFile.CreateFromDirectory(unzippedDirInfo.FullName, zippedFileInfo.FullName, CompressionLevel.Optimal, false);
                    Console.WriteLine($"CHANGE\t{c.CleanName(zippedFileInfo)}\tFile recreated from folder.");
                }
            }

            var version = source.GetVersion();

            if (version == "")
            {
                Console.WriteLine($"VERSION\t{c.CleanName(zippedFileInfo)}\tversion not resolved, further checks stopped.");
                return(Status.ContentError);
            }
            if (c.Options.CheckSchema)
            {
                var schemaPath = $"schemas/{version}";
                if (source is FolderSource && c.Options.UseRepoSchemaVersion == version)
                {
                    schemaPath = GetRepoSchemasFolder(c.Options.ResolvedSource as DirectoryInfo).FullName;
                }
                CheckSchemaCompliance(c, source, version, "bcf", Path.Combine(schemaPath, "markup.xsd"));
                CheckSchemaCompliance(c, source, version, "bcfv", Path.Combine(schemaPath, "visinfo.xsd"));
                CheckSchemaCompliance(c, source, version, "bcfp", Path.Combine(schemaPath, "project.xsd"));
                CheckSchemaCompliance(c, source, version, "version", Path.Combine(schemaPath, "version.xsd"));
            }
            if (c.Options.CheckUniqueGuid && source is FolderSource)
            {
                CheckUniqueIDs(c, unzippedDirInfo, version, "bcf");
                CheckUniqueIDs(c, unzippedDirInfo, version, "bcfv");
            }
            if (c.Options.CheckFileContents)
            {
                // todo: see the PDFFile example to determine what to do with <ReferencedDocument>
                CheckContent(c, source, version, new List <string>()
                {
                    "/Markup/Viewpoints/Viewpoint", "/Markup/Viewpoints/Snapshot",
                }, ".bcf");
                CheckContent(c, source, version, new List <string>()
                {
                    "/VisualizationInfo/Bitmap/Reference"
                }, ".bcfv");
            }
            // no need to check new lines on compressed files
            if (c.Options.CheckNewLines && source is FolderSource)
            {
                CheckMultiLine(c, unzippedDirInfo, "bcf");
                CheckMultiLine(c, unzippedDirInfo, "bcfv");
                CheckMultiLine(c, unzippedDirInfo, "bcfp");
            }
            if (c.Options.CheckImageSize && source is FolderSource)
            {
                // todo: 2021: should check image files from zip
                try
                {
                    CheckImageSizeIsOk(c, unzippedDirInfo);
                }
                catch (Exception ex)
                {
                    var message = $"WARNING\t{c.CleanName(zippedFileInfo)}\tCannot check image files, {ex.Message}";
                    Console.WriteLine(message);
                    if (message.Contains("'Gdip'", StringComparison.InvariantCultureIgnoreCase))
                    {
                        Console.WriteLine("INFO\tTry installing library libgdiplus, e.g.: 'sudo apt-get install -y libgdiplus', further image checks are not going to be perforemed.");
                        c.Options.CheckImageSize = false;
                    }
                }
            }

            return(Status.Ok);
        }