/* Function: Load * Loads <Files.nd> and returns whether it was successful. If it wasn't it will still return valid objects, they will just * be empty. */ public bool Load(Path filename, out IDObjects.Manager <File> files) { files = new IDObjects.Manager <File>(Config.Manager.KeySettingsForPaths, false); BinaryFile binaryFile = new BinaryFile(); bool result = true; try { // We'll continue to handle 2.0 files in 2.0.2 since it's easy enough if (binaryFile.OpenForReading(filename, "2.0") == false) { result = false; } else { // [Int32: ID] // [String: Path] // [Byte: Type] // [Int64: Last Modification in Ticks or 0] // (if image) // [UInt32: Width in Pixels or 0 if unknown] // [UInt32: Height in Pixels or 0 if unknown] // ... // [Int32: 0] int id; Path path; FileType type; DateTime lastModification; File file; uint width, height; for (;;) { id = binaryFile.ReadInt32(); if (id == 0) { break; } path = binaryFile.ReadString(); type = (FileType)binaryFile.ReadByte(); lastModification = new DateTime(binaryFile.ReadInt64()); if (type == FileType.Image) { if (binaryFile.Version < "2.0.2") { width = 0; height = 0; } else { width = binaryFile.ReadUInt32(); height = binaryFile.ReadUInt32(); } if (width == 0 || height == 0) { // If this file is from a different version of Natural Docs, no matter which one, reset the last modification // time so they'll be reparsed and take another stab at getting the dimensions if (binaryFile.Version != Engine.Instance.Version) { lastModification = new DateTime(0); } file = new ImageFile(path, lastModification); } else { file = new ImageFile(path, lastModification, width, height); } } else { file = new File(path, type, lastModification); } file.ID = id; files.Add(file); } } } catch { result = false; } finally { binaryFile.Close(); } if (result == false) { files.Clear(); } return(result); }
static readonly byte[] NKS_NICNT_TOC = new byte[] { 0x2F, 0x5C, 0x20, 0x4E, 0x49, 0x20, 0x46, 0x43, 0x20, 0x54, 0x4F, 0x43, 0x20, 0x20, 0x2F, 0x5C }; // /\ NI FC TOC /\ public static void Unpack(string inputFilePath, string outputDirectoryPath, bool doList, bool doVerbose) { using (BinaryFile bf = new BinaryFile(inputFilePath, BinaryFile.ByteOrder.LittleEndian, false)) { var header = bf.ReadBytes(16); if (header.SequenceEqual(NKS_NICNT_MTD)) // 2F 5C 20 4E 49 20 46 43 20 4D 54 44 20 20 2F 5C /\ NI FC MTD /\ { bf.Seek(66, SeekOrigin.Begin); string version = bf.ReadString(66, Encoding.Unicode).TrimEnd('\0'); Log.Information("Version: " + version); string outputFileName = Path.GetFileNameWithoutExtension(inputFilePath); if (!doList) { IOUtils.CreateDirectoryIfNotExist(Path.Combine(outputDirectoryPath, outputFileName)); } // Save version in ContentVersion.txt if (!doList) { IOUtils.WriteTextToFile(Path.Combine(outputDirectoryPath, outputFileName, "ContentVersion.txt"), version); } int unknown1 = bf.ReadInt32(); if (doVerbose) { Log.Debug("Unknown1: " + unknown1); } bf.Seek(144, SeekOrigin.Begin); int startOffset = bf.ReadInt32(); Log.Information("Start Offset: " + startOffset); int unknown3 = bf.ReadInt32(); if (doVerbose) { Log.Debug("Unknown3: " + unknown3); } bf.Seek(256, SeekOrigin.Begin); string productHintsXml = bf.ReadStringNull(); Log.Information(string.Format("Read ProductHints Xml with length {0} characters.", productHintsXml.Length)); if (doVerbose) { Log.Debug("ProductHints Xml:\n" + productHintsXml); } // Save productHints as xml if (!doList) { IOUtils.WriteTextToFile(Path.Combine(outputDirectoryPath, outputFileName, outputFileName + ".xml"), productHintsXml); } // get the product hints as an object var productHints = ProductHintsFactory.ReadFromString(productHintsXml); if (productHints != null && productHints.Product.Icon != null && productHints.Product.Icon.ImageBytes != null) { ProductHintsFactory.UpdateImageFromImageBytes(productHints); var image = productHints.Product.Icon.Image; var imageFormat = productHints.Product.Icon.ImageFormat; if (image != null && imageFormat != null) { Log.Information(string.Format("Found Icon in ProductHints Xml in {0} format. (Dimensions: {1} x {2}, Width: {1} pixels, Height: {2} pixels, Bit depth: {3} bpp)", imageFormat.Name, image.Width, image.Height, image.PixelType.BitsPerPixel)); // save icon to file if (!doList) { var iconFileName = outputFileName + " Icon." + imageFormat.Name.ToLower(); var iconFilePath = Path.Combine(outputDirectoryPath, outputFileName, iconFileName); if (doVerbose) { Log.Debug("Saving Icon to: " + iconFilePath); } // save using ImageSharp // var imageEncoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(imageFormat); // image.Save(iconFilePath, imageEncoder); // save using image bytes BinaryFile.ByteArrayToFile(iconFilePath, productHints.Product.Icon.ImageBytes); } } } bf.Seek(startOffset + 256, SeekOrigin.Begin); var header2 = bf.ReadBytes(16); if (header2.SequenceEqual(NKS_NICNT_MTD)) // 2F 5C 20 4E 49 20 46 43 20 4D 54 44 20 20 2F 5C /\ NI FC MTD /\ { bf.ReadBytes(116); long unknown4 = bf.ReadInt64(); if (doVerbose) { Log.Debug("Unknown4: " + unknown4); } bf.ReadBytes(4); long unknown5 = bf.ReadInt64(); if (doVerbose) { Log.Debug("Unknown5: " + unknown5); } bf.ReadBytes(104); long unknown6 = bf.ReadInt64(); if (doVerbose) { Log.Debug("Unknown6: " + unknown6); } var delimiter1 = bf.ReadBytes(8); if (doVerbose) { Log.Debug("Delimiter1: " + StringUtils.ByteArrayToHexString(delimiter1)); // F0 F0 F0 F0 F0 F0 F0 F0 } if (!delimiter1.SequenceEqual(new byte[] { 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0 })) { Log.Error("Delimiter1 not as expected 'F0 F0 F0 F0 F0 F0 F0 F0' but got " + StringUtils.ToHexAndAsciiString(delimiter1)); } long totalResourceCount = bf.ReadInt64(); Log.Information("Total Resource Count: " + totalResourceCount); long totalResourceLength = bf.ReadInt64(); Log.Information("Total Resource Byte Length: " + totalResourceLength); var resourceList = new List <NICNTResource>(); var header3 = bf.ReadBytes(16); if (header3.SequenceEqual(NKS_NICNT_TOC)) // 2F 5C 20 4E 49 20 46 43 20 54 4F 43 20 20 2F 5C /\ NI FC TOC /\ { bf.ReadBytes(600); long lastEndIndex = 0; for (int i = 0; i < totalResourceCount; i++) { var resource = new NICNTResource(); Log.Information("-------- Index: " + bf.Position + " --------"); long resCounter = bf.ReadInt64(); Log.Information("Resource Counter: " + resCounter); resource.Count = resCounter; bf.ReadBytes(16); string resName = bf.ReadString(600, Encoding.Unicode).TrimEnd('\0'); Log.Information("Resource Name: " + resName); resource.Name = resName; long resUnknown = bf.ReadInt64(); if (doVerbose) { Log.Debug("Resource Unknown: " + resUnknown); } long resEndIndex = bf.ReadInt64(); Log.Information("Resource End Index: " + resEndIndex); resource.EndIndex = resEndIndex; // store calculated length if (lastEndIndex > 0) { resource.Length = resEndIndex - lastEndIndex; } else { // for the very first entry the end index is the same as the byte length resource.Length = resEndIndex; } Log.Information("Calculated Resource Byte Length: " + resource.Length); lastEndIndex = resEndIndex; resourceList.Add(resource); } Log.Information("-------- Index: " + bf.Position + " --------"); var delimiter2 = bf.ReadBytes(8); if (doVerbose) { Log.Debug("Delimiter2: " + StringUtils.ByteArrayToHexString(delimiter2)); // F1 F1 F1 F1 F1 F1 F1 F1 } if (!delimiter2.SequenceEqual(new byte[] { 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1 })) { Log.Error("Delimiter2 not as expected 'F1 F1 F1 F1 F1 F1 F1 F1' but got " + StringUtils.ToHexAndAsciiString(delimiter2)); } long unknown13 = bf.ReadInt64(); if (doVerbose) { Log.Debug("Unknown13: " + unknown13); } long unknown14 = bf.ReadInt64(); if (doVerbose) { Log.Debug("Unknown14: " + unknown14); } var header4 = bf.ReadBytes(16); if (header4.SequenceEqual(NKS_NICNT_TOC)) // 2F 5C 20 4E 49 20 46 43 20 54 4F 43 20 20 2F 5C /\ NI FC TOC /\ { bf.ReadBytes(592); if (!doList) { IOUtils.CreateDirectoryIfNotExist(Path.Combine(outputDirectoryPath, outputFileName, "Resources")); } foreach (var res in resourceList) { // convert the unix filename to a windows supported filename string escapedFileName = FromUnixFileNames(res.Name); // and add the counter in front string escapedFileNameWithNumber = string.Format("{0:D3}{1}", res.Count, escapedFileName); Log.Information(String.Format("Resource '{0}' @ position {1} [{2} bytes]", escapedFileNameWithNumber, bf.Position, res.Length)); res.Data = bf.ReadBytes((int)res.Length); // if not only listing, save files if (!doList) { string outputFilePath = Path.Combine(outputDirectoryPath, outputFileName, "Resources", escapedFileNameWithNumber); BinaryFile outBinaryFile = new BinaryFile(outputFilePath, BinaryFile.ByteOrder.LittleEndian, true); outBinaryFile.Write(res.Data); outBinaryFile.Close(); } } } else { Log.Error(inputFilePath + ": Header4 not as expected '/\\ NI FC TOC /\\' but got " + StringUtils.ToHexAndAsciiString(header4)); } } else { Log.Error(inputFilePath + ": Header3 not as expected '/\\ NI FC TOC /\\' but got " + StringUtils.ToHexAndAsciiString(header3)); } } else { Log.Error(inputFilePath + ": Header2 not as expected '/\\ NI FC MTD /\\' but got " + StringUtils.ToHexAndAsciiString(header2)); } } else { Log.Error(inputFilePath + ": Header not as expected '/\\ NI FC MTD /\\' but got " + StringUtils.ToHexAndAsciiString(header)); } } }