private void WriteStringBuffer() { m_tableheaders[0].crc32 = Crc32Algorithm.Compute(m_strings); m_stream.Write(m_strings, 0, m_strings.Length); }
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) { } }
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); }
/// <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; } }
/// <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 }
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); }