Beispiel #1
0
        public static void Pack(string inputDirectoryPath, string outputDirectoryPath, bool doList, bool doVerbose)
        {
            Log.Information("Packing directory {0} ...", inputDirectoryPath);

            string libraryName    = Path.GetFileNameWithoutExtension(inputDirectoryPath);
            string outputFileName = libraryName + ".nicnt";
            string outputFilePath = Path.Combine(outputDirectoryPath, outputFileName);

            Log.Information("Packing into file {0} ...", outputFilePath);

            // read the ContentVersion.txt file
            string contentVersionPath = Path.Combine(inputDirectoryPath, "ContentVersion.txt");
            string version            = "1.0";

            if (File.Exists(contentVersionPath))
            {
                version = IOUtils.ReadTextFromFile(contentVersionPath);
                if (doVerbose)
                {
                    Log.Debug("Read version: '" + version + "' from ContentVersion.txt");
                }
            }
            else
            {
                Log.Information("No ContentVersion.txt file found - using default version: '" + version + "'");
            }

            // read the [LibraryName].xml file (should have the same name as the input directory)
            string       productHintsXmlFileName = libraryName + ".xml";
            string       productHintsXmlFilePath = Path.Combine(inputDirectoryPath, productHintsXmlFileName);
            string       productHintsXml         = "";
            ProductHints productHints            = null;

            if (File.Exists(productHintsXmlFilePath))
            {
                productHintsXml = IOUtils.ReadTextFromFile(productHintsXmlFilePath);
                Log.Information(string.Format("Found ProductHints Xml with length {0} characters.", productHintsXml.Length));
                if (doVerbose)
                {
                    Log.Debug("ProductHints Xml:\n" + productHintsXml);
                }

                // get the product hints as an object
                productHints = ProductHintsFactory.ReadFromString(productHintsXml);

                // get the productHints as string
                productHintsXml = ProductHintsFactory.ToString(productHints);

                // test output
                // var xmlBytesTest = Encoding.UTF8.GetBytes(productHintsXml);
                // BinaryFile.ByteArrayToFile(Path.Combine(inputDirectoryPath, libraryName + "-test.xml"), xmlBytesTest);

                // check that the directory name is the same as the Name and RegKey in the xml file
                if (productHints.Product.Name != productHints.Product.RegKey || libraryName != productHints.Product.Name)
                {
                    Log.Error(string.Format("Fixing '{0}' due to incorrect values! (Library-Name: '{1}', Xml-Name: '{2}', Xml-RegKey: '{3}')", productHintsXmlFileName, libraryName, productHints.Product.Name, productHints.Product.RegKey));

                    // change into the library name
                    productHints.Product.Name   = libraryName;
                    productHints.Product.RegKey = libraryName;

                    // update the productHints as string
                    productHintsXml = ProductHintsFactory.ToString(productHints);

                    // and overwrite
                    var xmlBytes = Encoding.UTF8.GetBytes(productHintsXml);
                    BinaryFile.ByteArrayToFile(productHintsXmlFilePath, xmlBytes);
                }
            }
            else
            {
                Log.Error("Mandatory ProductHints XML file not found at: " + productHintsXmlFilePath);
                return;
            }

            // read the files in the resources directory
            var    resourceList           = new List <NICNTResource>();
            string resourcesDirectoryPath = Path.Combine(inputDirectoryPath, "Resources");

            if (Directory.Exists(resourcesDirectoryPath))
            {
                var resourcesFilePaths = Directory.GetFiles(resourcesDirectoryPath, "*.*", SearchOption.AllDirectories);
                int counter            = 1;
                foreach (var filePath in resourcesFilePaths)
                {
                    var res = new NICNTResource();

                    string name = Path.GetFileName(filePath);

                    // remove the counter in front
                    string escapedFileNameWithNumber = Regex.Replace(name, @"^\d{3}(.*?)$", "$1", RegexOptions.IgnoreCase);

                    // convert the windows supported unix filename to the original unix filename
                    string unescapedFileName = ToUnixFileName(escapedFileNameWithNumber);
                    res.Name = unescapedFileName;

                    var bytes = File.ReadAllBytes(filePath);
                    res.Data   = bytes;
                    res.Length = bytes.Length;

                    res.Count = counter;

                    resourceList.Add(res);
                    counter++;
                }

                if (resourceList.Count > 0)
                {
                    if (doVerbose)
                    {
                        Log.Debug("Added " + resourceList.Count + " files found in the Resources directory.");
                    }
                }
                else
                {
                    if (doVerbose)
                    {
                        Log.Information("No files in the Resource directory found!");
                    }
                }
            }
            else
            {
                if (doVerbose)
                {
                    Log.Information("No Resources directory found!");
                }
            }

            // check for mandatory .db.cache
            if (!resourceList.Any(m => m.Name.ToLower() == ".db.cache"))
            {
                XmlDocument xml = new XmlDocument();
                // Adding the XmlDeclaration (version encoding and standalone) is not necessary as it is added using the XmlWriterSettings
                // XmlNode docNode = xml.CreateXmlDeclaration("1.0", "UTF-8", "no");
                // xml.AppendChild(docNode);
                XmlElement root = xml.CreateElement("soundinfos");
                root.SetAttribute("version", "110");
                xml.AppendChild(root);

                XmlElement all = xml.CreateElement("all");
                root.AppendChild(all);

                // format the xml string
                string xmlString = BeautifyXml(xml);

                var res = new NICNTResource();
                res.Name = ".db.cache";
                var bytes = Encoding.UTF8.GetBytes(xmlString);
                res.Data   = bytes;
                res.Length = bytes.Length;
                // res.EndIndex is calculated during the write resource operation later
                res.Count = resourceList.Count + 1;

                resourceList.Add(res);
                if (doVerbose)
                {
                    Log.Information("No .db.cache found - using default .db.cache:\n" + xml.OuterXml);
                }
            }

            using (BinaryFile bf = new BinaryFile(outputFilePath, BinaryFile.ByteOrder.LittleEndian, true))
            {
                bf.Write(NKS_NICNT_MTD);                             // 2F 5C 20 4E 49 20 46 43 20 4D 54 44 20 20 2F 5C   /\ NI FC MTD  /\ 
                bf.Write(new byte[50]);                              // 50 zero bytes

                bf.WriteStringPadded(version, 66, Encoding.Unicode); // zero padded string

                Int32 unknown1 = 1;
                bf.Write(unknown1);

                bf.Write(new byte[8]); // 8 zero bytes

                Int32 startOffset = 512000;
                bf.Write(startOffset);

                Int32 unknown3 = startOffset;
                bf.Write(unknown3);

                bf.Write(new byte[104]); // 104 zero bytes

                bf.Write(productHintsXml);

                bf.Write(new byte[startOffset + 256 - bf.Position]); // 512000 + 256 zero bytes - current pos

                bf.Write(NKS_NICNT_MTD);                             // 2F 5C 20 4E 49 20 46 43 20 4D 54 44 20 20 2F 5C   /\ NI FC MTD  /\ 
                bf.Write(new byte[116]);                             // 116 zero bytes

                Int64 unknown4 = 2;
                bf.Write(unknown4);

                bf.Write(new byte[4]); // 4 zero bytes

                Int64 unknown5 = 1;
                bf.Write(unknown5);

                bf.Write(new byte[104]); // 104 zero bytes

                Int64 unknown6 = 1;
                bf.Write(unknown6);

                // write delimiter
                bf.Write(StringUtils.HexStringToByteArray("F0F0F0F0F0F0F0F0"));

                Int64 totalResourceCount = resourceList.Count;
                bf.Write(totalResourceCount);

                Int64 totalResourceLength = resourceList.Sum(item => item.Length); // sum of bytes in the resourceList
                bf.Write(totalResourceLength);

                bf.Write(NKS_NICNT_TOC); // 2F 5C 20 4E 49 20 46 43 20 54 4F 43 20 20 2F 5C  /\ NI FC TOC  /\

                bf.Write(new byte[600]); // 600 zero bytes

                Int64 resCounter   = 1;
                Int64 lastEndIndex = 0;
                foreach (var res in resourceList)
                {
                    bf.Write(resCounter);

                    bf.Write(new byte[16]);                                // 16 zero bytes

                    bf.WriteStringPadded(res.Name, 600, Encoding.Unicode); // zero padded string

                    Int64 resUnknown = 0;
                    bf.Write(resUnknown);

                    Int64 resEndIndex = lastEndIndex + res.Length; // aggregated end index
                    bf.Write(resEndIndex);

                    lastEndIndex = resEndIndex;
                    resCounter++;
                }

                // write delimiter
                bf.Write(StringUtils.HexStringToByteArray("F1F1F1F1F1F1F1F1"));

                Int64 unknown13 = 0;
                bf.Write(unknown13);

                Int64 unknown14 = 0;
                bf.Write(unknown14);

                bf.Write(NKS_NICNT_TOC); // 2F 5C 20 4E 49 20 46 43 20 54 4F 43 20 20 2F 5C  /\ NI FC TOC  /\

                bf.Write(new byte[592]); // 592 zero bytes

                foreach (var res in resourceList)
                {
                    Log.Information(String.Format("Resource '{0}' @ position {1} [{2} bytes]", res.Name, bf.Position, res.Length));
                    bf.Write(res.Data);
                }
            }
        }
Beispiel #2
0
        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));
                }
            }
        }