Example #1
0
        //------------------------------------------------------------------------------------------xx.02.2005
        /// <summary>Reads the table "hmtx" (glyph widths).</summary>
        /// <remarks>The glyph widths are normalized to 1000 units.</remarks>
        /// <param name="openTypeReader">Open Type Reader</param>
        private void ReadTable_hmtx(OpenTypeReader openTypeReader)
        {
            aiGlyphWidth = new Int32[horizontalHeader.iNumberOfHMetrics];

            TableDirectory tableDirectory = dict_TableDirectory["hmtx"];

            openTypeReader.Seek(tableDirectory.iOffset);
            for (Int32 i = 0; i < aiGlyphWidth.Length; i++)
            {
                Int32 iWidth = openTypeReader.iReadUSHORT();
                aiGlyphWidth[i] = iWidth;
                openTypeReader.iReadUSHORT(); // left side bearing
            }
        }
Example #2
0
        //------------------------------------------------------------------------------------------16.02.2005
        #region Table "name"
        //----------------------------------------------------------------------------------------------------

        //------------------------------------------------------------------------------------------xx.02.2005
        /// <summary>Reads the table "name".</summary>
        /// <param name="openTypeReader">Open Type Reader</param>
        private void ReadTable_name(OpenTypeReader openTypeReader)
        {
            TableDirectory tableDirectory = dict_TableDirectory["name"];

            openTypeReader.Seek(tableDirectory.iOffset + 2);
            Int32 iNumberOfRecords = openTypeReader.iReadUSHORT();
            Int32 iStringOffset    = openTypeReader.iReadUSHORT();

            for (Int32 i = 0; i < iNumberOfRecords; i++)
            {
                openTypeReader.Seek(tableDirectory.iOffset + 6 + i * 12);
                Int32 iPlatformId = openTypeReader.iReadUSHORT();
                Int32 iEncodingId = openTypeReader.iReadUSHORT();
                Int32 iLanguageId = openTypeReader.iReadUSHORT();
                Int32 iNameId     = openTypeReader.iReadUSHORT();
                Int32 iLength     = openTypeReader.iReadUSHORT();
                Int32 iOffset     = openTypeReader.iReadUSHORT();
                if (iNameId == 4)
                {
                    openTypeReader.Seek(tableDirectory.iOffset + iStringOffset + iOffset);
                    String sName;
                    if (iPlatformId == 0 || (iPlatformId == 2 && iEncodingId == 1) || iPlatformId == 3)
                    {
                        sName = openTypeReader.sReadUnicodeString(iLength);
                    }
                    else
                    {
                        sName = openTypeReader.sReadCHAR(iLength);
                    }
                    sName = sName.Replace(Environment.NewLine, ";");
                    //Console.WriteLine(iPlatformId + "\t" + iEncodingId + "\t" + iLanguageId + "\t" + iNameId + "\t" + sName);
                    sFullFontName = sName.Replace(' ', '_');
                }
                else if (iNameId == 6)
                {
                    openTypeReader.Seek(tableDirectory.iOffset + iStringOffset + iOffset);
                    if (iPlatformId == 0 || iPlatformId == 3)
                    {
                        sBaseFontName = openTypeReader.sReadUnicodeString(iLength);
                    }
                    else
                    {
                        sBaseFontName = openTypeReader.sReadCHAR(iLength);
                    }
                }
            }
        }
Example #3
0
        public static ConversionStatus Convert(
            Stream input,
            Stream output)
        {
            using var reader = new BEBinaryReader(input);

            var header = new WoffHeader
            {
                Signature         = reader.ReadUInt32(),
                Flavor            = reader.ReadUInt32(),
                Length            = reader.ReadUInt32(),
                TableCount        = reader.ReadUInt16(),
                Reserved          = reader.ReadUInt16(),
                TotalSfntSize     = reader.ReadUInt32(),
                MajorVersion      = reader.ReadUInt16(),
                MinorVersion      = reader.ReadUInt16(),
                MetaOffset        = reader.ReadUInt32(),
                MetaLength        = reader.ReadUInt32(),
                MetaOrignalLength = reader.ReadUInt32(),
                PrivOffset        = reader.ReadUInt32(),
                PrivLength        = reader.ReadUInt32()
            };

            if (header.Signature is not 0x774F4646)
            {
                if (header.Signature is 0x774F4632)
                {
                    return(ConversionStatus.UnsupportedWOFF2);
                }
                else
                {
                    return(ConversionStatus.UnrecognisedFile);
                }
            }


            UInt32 offset = 12;

            // Read Table Headers
            List <TableDirectory> entries = new List <TableDirectory>();

            for (var i = 0; i < header.TableCount; i++)
            {
                var entry = new TableDirectory
                {
                    Tag              = reader.ReadUInt32(),
                    Offset           = reader.ReadUInt32(),
                    CompressedLength = reader.ReadUInt32(),
                    OriginalLength   = reader.ReadUInt32(),
                    OriginalChecksum = reader.ReadUInt32()
                };

                if (Name(entry.Tag) == "DSIG") // Conversion invalidates
                {
                    continue;
                }

                entries.Add(entry);
                offset += (4 * 4);
            }

            // Amend table count after removing DSIG
            header.TableCount = (ushort)entries.Count;

            // Calculate header values
            UInt16 entrySelector = 0;

            while (Math.Pow(2, entrySelector) <= header.TableCount)
            {
                entrySelector++;
            }
            entrySelector--;

            UInt16 searchRange = (UInt16)(Math.Pow(2, entrySelector) * 16);
            UInt16 rangeShift  = (UInt16)(header.TableCount * 16 - searchRange);

            // Create writer
            using var writer = new BEBinaryWriter(output);

            // Write Font Header
            writer.Write(header.Flavor);
            writer.Write(header.TableCount);
            writer.Write(searchRange);
            writer.Write(entrySelector);
            writer.Write(rangeShift);

            // Write Table Headers
            foreach (var entry in entries)
            {
                writer.Write(entry.Tag);
                writer.Write(entry.OriginalChecksum);
                writer.Write(offset);
                writer.Write(entry.OriginalLength);
                entry.OutputOffset = offset;

                offset += entry.OriginalLength;
                if ((offset % 4) != 0)
                {
                    offset += 4 - (offset % 4);
                }
            }

            // Write Table contents
            foreach (var entry in entries)
            {
                input.Seek(entry.Offset, SeekOrigin.Begin);
                byte[] compressed = reader.ReadBytes((int)entry.CompressedLength);
                byte[] uncompressed;
                if (entry.CompressedLength != entry.OriginalLength)
                {
                    // Decompress table
                    using var comp = new MemoryStream(compressed.AsSpan().Slice(2).ToArray()); // Ignore the ZLib header (2 bytes long)
                    using var outs = new MemoryStream();
                    using var def  = new DeflateStream(comp, CompressionMode.Decompress);
                    def.CopyTo(outs);
                    uncompressed = outs.ToArray();
                }
                else
                {
                    uncompressed = compressed;
                }

                if (uncompressed.Length != entry.OriginalLength)
                {
                    return(ConversionStatus.TableLengthMismatch);
                }

                if (entry.ToString() == "name")
                {
                    if (TryFixNameTable(ref uncompressed) is byte[] ammended)
                    {
                        uncompressed = ammended;
                    }
                }

                output.Seek(entry.OutputOffset, SeekOrigin.Begin);
                writer.Write(uncompressed);
                offset = (uint)output.Position;
                if (offset % 4 != 0)
                {
                    uint padding = 4 - (offset % 4);
                    writer.Write(new byte[padding]);
                }
            }

            writer.Flush();
            output.Flush();

            return(ConversionStatus.OK);
        }
Example #4
0
        //------------------------------------------------------------------------------------------xx.02.2005
        /// <summary>Reads the font data from the file.</summary>
        /// <param name="openTypeReader">Open Type Reader</param>
        private void ReadFontDataFromFile(OpenTypeReader openTypeReader)
        {
            #region TrueType Collection
            if (bTrueTypeCollection)
            {
                String sTTCTag = openTypeReader.sReadTag();
                if (sTTCTag != "ttcf")
                {
                    throw new ReportException("'" + sFontName + " is not a valid TTC font file.");
                }
                UInt32 uVersion  = openTypeReader.uReadULONG();
                Int32  iNumFonts = (Int32)openTypeReader.uReadULONG();
                if (iTrueTypeCollectionIndex >= iNumFonts)
                {
                    throw new ReportException("'" + sFontName + " has invalid TrueType collection index.");
                }
                openTypeReader.Skip(iTrueTypeCollectionIndex * 4);
                Int32 iOffset = (Int32)openTypeReader.uReadULONG();
                openTypeReader.Seek(iOffset);
            }
            #endregion

            #region Offset Table
            UInt32 uSfntVersion = openTypeReader.uReadULONG();
            if (uSfntVersion == 0x4F54544F /* 'OTTO' */)
            {
                fontDataType = FontDataType.CFFdata;
            }
      #if DEBUG
            else if (uSfntVersion == 0x00010000 /* Version 1.0 */)
            {
                fontDataType = FontDataType.TrueTypeOutlines;
            }
            else
            {
                throw new ReportException("'" + sFontName + " is not a valid TTF, OTF or TTC font file.");
            }
      #endif

            iNumTables     = (Int32)openTypeReader.iReadUSHORT();
            iSearchRange   = (Int32)openTypeReader.iReadUSHORT();
            iEntrySelector = (Int32)openTypeReader.iReadUSHORT();
            iRangeShift    = (Int32)openTypeReader.iReadUSHORT();
            #endregion

            #region Table Directory
            for (Int32 iTable = 0; iTable < iNumTables; iTable++)
            {
                TableDirectory tableDirectory_New = new TableDirectory();
                tableDirectory_New.sTag      = openTypeReader.sReadTag();
                tableDirectory_New.uCheckSum = openTypeReader.uReadULONG();
                tableDirectory_New.iOffset   = (Int32)openTypeReader.uReadULONG();
                tableDirectory_New.iLength   = (Int32)openTypeReader.uReadULONG();
                dict_TableDirectory.Add(tableDirectory_New.sTag, tableDirectory_New);
            }
            Boolean bCFF = dict_TableDirectory.ContainsKey("CFF");
            if ((bCFF && fontDataType == FontDataType.TrueTypeOutlines) || (!bCFF && fontDataType == FontDataType.CFFdata))
            {
                throw new ReportException("'" + sFontName + " is not a valid TTC font file.");
            }
            #endregion

            #region Font Header
            TableDirectory tableDirectory = dict_TableDirectory["head"];
            fontHeader = new FontHeader();
            openTypeReader.Seek(tableDirectory.iOffset);
            fontHeader.uTableVersionNumber = openTypeReader.uReadULONG();
            Debug.Assert(fontHeader.uTableVersionNumber == 0x00010000 /* Version 1.0 */);
            openTypeReader.Skip(12);
            fontHeader.iFlags      = openTypeReader.iReadUSHORT();
            fontHeader.iUnitsPerEm = openTypeReader.iReadUSHORT();
            openTypeReader.Skip(16);
            fontHeader.iXMin              = openTypeReader.int16_ReadSHORT();
            fontHeader.iYMin              = openTypeReader.int16_ReadSHORT();
            fontHeader.iXMax              = openTypeReader.int16_ReadSHORT();
            fontHeader.iYMax              = openTypeReader.int16_ReadSHORT();
            fontHeader.iMacStyle          = openTypeReader.iReadUSHORT();
            fontHeader.iLowestRecPPEM     = openTypeReader.iReadUSHORT();
            fontHeader.iFontDirectionHint = openTypeReader.int16_ReadSHORT();
            fontHeader.iIndexToLocFormat  = openTypeReader.int16_ReadSHORT();
            fontHeader.iGlyphDataFormat   = openTypeReader.int16_ReadSHORT();
            #endregion

            #region Horizontal Header
            tableDirectory   = dict_TableDirectory["hhea"];
            horizontalHeader = new HorizontalHeader();
            openTypeReader.Seek(tableDirectory.iOffset);
            horizontalHeader.uTableVersionNumber = openTypeReader.uReadULONG();
            Debug.Assert(horizontalHeader.uTableVersionNumber == 0x00010000 /* Version 1.0 */);
            horizontalHeader.iAscender            = openTypeReader.int16_ReadFWORD();
            horizontalHeader.iDescender           = openTypeReader.int16_ReadFWORD();
            horizontalHeader.iLineGap             = openTypeReader.int16_ReadFWORD();
            horizontalHeader.iAdvanceWidthMax     = openTypeReader.iReadUFWORD();
            horizontalHeader.iMinLeftSideBearing  = openTypeReader.int16_ReadFWORD();
            horizontalHeader.iMinRightSideBearing = openTypeReader.int16_ReadFWORD();
            horizontalHeader.iXMaxExtent          = openTypeReader.int16_ReadFWORD();
            horizontalHeader.iCaretSlopeRise      = openTypeReader.int16_ReadSHORT();
            horizontalHeader.iCaretSlopeRun       = openTypeReader.int16_ReadSHORT();
            horizontalHeader.iCaretOffset         = openTypeReader.int16_ReadSHORT();
            openTypeReader.Skip(10);
            horizontalHeader.iNumberOfHMetrics = openTypeReader.iReadUSHORT();
            #endregion

            #region Windows Metrics (OS/2)
            tableDirectory = dict_TableDirectory["OS/2"];
            winMetrics     = new WinMetrics();
            openTypeReader.Seek(tableDirectory.iOffset);
            winMetrics.iVersion             = openTypeReader.iReadUSHORT();
            winMetrics.iXAvgCharWidth       = openTypeReader.int16_ReadSHORT();
            winMetrics.iUsWeightClass       = openTypeReader.iReadUSHORT();
            winMetrics.iUsWidthClass        = openTypeReader.iReadUSHORT();
            winMetrics.iFsType              = openTypeReader.iReadUSHORT();
            winMetrics.iYSubscriptXSize     = openTypeReader.int16_ReadSHORT();
            winMetrics.iYSubscriptYSize     = openTypeReader.int16_ReadSHORT();
            winMetrics.iYSubscriptXOffset   = openTypeReader.int16_ReadSHORT();
            winMetrics.iYSubscriptYOffset   = openTypeReader.int16_ReadSHORT();
            winMetrics.iYSuperscriptXSize   = openTypeReader.int16_ReadSHORT();
            winMetrics.iYSuperscriptYSize   = openTypeReader.int16_ReadSHORT();
            winMetrics.iYSuperscriptXOffset = openTypeReader.int16_ReadSHORT();
            winMetrics.iYSuperscriptYOffset = openTypeReader.int16_ReadSHORT();
            winMetrics.iYStrikeoutSize      = openTypeReader.int16_ReadSHORT();
            winMetrics.iYStrikeoutPosition  = openTypeReader.int16_ReadSHORT();
            winMetrics.iSFamilyClass        = openTypeReader.int16_ReadSHORT();
            winMetrics.aByte_Panose         = openTypeReader.aByte_ReadBYTE(10);
            if (winMetrics.iVersion == 0)
            {
                openTypeReader.Skip(4); // skip ulCharRange
            }
            else
            {
                winMetrics.uUlUnicodeRange1 = openTypeReader.uReadULONG();
                winMetrics.uUlUnicodeRange2 = openTypeReader.uReadULONG();
                winMetrics.uUlUnicodeRange3 = openTypeReader.uReadULONG();
                winMetrics.uUlUnicodeRange4 = openTypeReader.uReadULONG();
            }
            winMetrics.sAchVendID        = openTypeReader.sReadTag();
            winMetrics.iFsSelection      = openTypeReader.iReadUSHORT();
            winMetrics.iUsFirstCharIndex = openTypeReader.iReadUSHORT();
            winMetrics.iUsLastCharIndex  = openTypeReader.iReadUSHORT();
            winMetrics.iSTypoAscender    = openTypeReader.int16_ReadSHORT();
            winMetrics.iSTypoDescender   = openTypeReader.int16_ReadSHORT();
            winMetrics.iSTypoLineGap     = openTypeReader.int16_ReadSHORT();
            winMetrics.iUsWinAscent      = openTypeReader.iReadUSHORT();
            winMetrics.iUsWinDescent     = openTypeReader.iReadUSHORT();
            if (winMetrics.iVersion > 0)
            {
                winMetrics.iUlCodePageRange1 = openTypeReader.uReadULONG();
                winMetrics.iUlCodePageRange2 = openTypeReader.uReadULONG();
                if (winMetrics.iVersion > 1)
                {
                    winMetrics.iSXHeight      = openTypeReader.int16_ReadSHORT();
                    winMetrics.iSCapHeight    = openTypeReader.int16_ReadSHORT();
                    winMetrics.uUsDefaultChar = openTypeReader.uReadULONG();
                    winMetrics.uUsBreakChar   = openTypeReader.uReadULONG();
                    winMetrics.uUsMaxContext  = openTypeReader.uReadULONG();
                }
            }
            #endregion

            #region Post
            tableDirectory = dict_TableDirectory["post"];
            openTypeReader.Seek(tableDirectory.iOffset + 4);
            Double r1 = openTypeReader.int16_ReadSHORT();
            Double r0 = openTypeReader.iReadUSHORT();
            rItalicAngle        = r1 + r0 / 16384.0;
            iUnderlinePosition  = openTypeReader.int16_ReadFWORD();
            iUnderlineThickness = openTypeReader.int16_ReadFWORD();
            bFixedPitch         = (openTypeReader.uReadULONG() != 0);
            #endregion

            ReadTable_name(openTypeReader);
            ReadTable_hmtx(openTypeReader);
        }
        public static void Main(string[] args)
        {
            bool unpackNestedPacks = true;
            bool verbose           = false;
            bool showHelp          = false;

            var options = new OptionSet()
            {
                { "d|dont-unpack-nested-packs", "don't unpack nested .pack files", v => unpackNestedPacks = v == null },
                { "v|verbose", "be verbose", v => verbose = v != null },
                { "h|help", "show this message and exit", v => showHelp = v != null },
            };

            List <string> extra;

            try
            {
                extra = options.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("{0}: ", GetExecutableName());
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `{0} --help' for more information.", GetExecutableName());
                return;
            }

            if (extra.Count < 1 || extra.Count > 2 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_FILETABLE [output_directory]", GetExecutableName());
                Console.WriteLine("Unpack specified archive.");
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            string inputPath      = extra[0];
            string outputBasePath = extra.Count > 1 ? extra[1] : Path.ChangeExtension(inputPath, null) + "_unpacked";

            FileTableFile table;

            using (var input = File.OpenRead(inputPath))
            {
                table = new FileTableFile();
                table.Deserialize(input);
            }

            var inputBasePath = Path.GetDirectoryName(inputPath);

            // TODO(gibbed):
            //  - generate file index for successful repacking
            //  - better name lookup for name hashes (FNV32)
            //    (don't hardcode the list)
            var names = new string[]
            {
                "MENU_COMMON_PACK",
                "MENU_TEXTURE_MISC_PACK",
                "MN_AT_ORGANIZE",
                "MN_BIRTHDAY",
                "MN_BT_MAIN",
                "MN_BT_RESULT",
                "MN_COMMON",
                "MN_COMMONWIN",
                "MN_EVENT",
                "MN_INPUT",
                "MN_ITEMICON",
                "MN_KEY_LAYOUT",
                "MN_MOVIE",
                "MN_NETWORK",
                "MN_OPTION",
                "MN_ORGANIZE",
                "MN_SHOP2",
                "MN_STAFFROLL",
                "MN_STATUS",
                "MN_TITLE",
                "MN_WARRENREPORT",
                "MN_WORLD",
            };
            var nameHashLookup = names.ToDictionary(v => v.HashFNV32(), v => v);

            var tableManifestPath = Path.Combine(outputBasePath, "@manifest.json");
            var tableManifest     = new FileTableManifest()
            {
                Endian               = table.Endian,
                TitleId1             = table.TitleId1,
                TitleId2             = table.TitleId2,
                Unknown32            = table.Unknown32,
                ParentalLevel        = table.ParentalLevel,
                InstallDataCryptoKey = table.InstallDataCryptoKey,
            };

            foreach (var directory in table.Directories)
            {
                var tableDirectory = new TableDirectory()
                {
                    Id       = directory.Id,
                    BasePath = Path.Combine(outputBasePath, $"{directory.Id}"),
                };

                var fileContainers = new List <IFileContainer>()
                {
                    tableDirectory,
                };

                var binPath = Path.Combine(inputBasePath, $"{directory.Id:X4}.BIN");
                using (var input = File.OpenRead(binPath))
                {
                    var fileQueue = new Queue <QueuedFile>();
                    foreach (var file in directory.Files)
                    {
                        long dataOffset;
                        dataOffset  = directory.DataBaseOffset;
                        dataOffset += (file.DataBlockOffset << directory.DataBlockSize) * FileTableFile.BaseDataBlockSize;

                        fileQueue.Enqueue(new QueuedFile()
                        {
                            Id         = file.Id,
                            Parent     = tableDirectory,
                            NameHash   = file.NameHash,
                            DataOffset = dataOffset,
                            DataSize   = file.DataSize,
                        });
                    }

                    while (fileQueue.Count > 0)
                    {
                        var file   = fileQueue.Dequeue();
                        var parent = file.Parent;

                        var nameBuilder = new StringBuilder();
                        nameBuilder.Append($"{file.Id}");

                        string name = null;
                        if (file.NameHash != null)
                        {
                            if (nameHashLookup.TryGetValue(file.NameHash.Value, out name) == true)
                            {
                                nameBuilder.Append($"_{name}");
                            }
                            else
                            {
                                nameBuilder.Append($"_HASH[{file.NameHash.Value:X8}]");
                            }
                        }

                        if (parent.IdCounts != null)
                        {
                            var idCounts = parent.IdCounts;
                            int idCount;
                            idCounts.TryGetValue(file.Id, out idCount);
                            idCount++;
                            idCounts[file.Id] = idCount;

                            if (idCount > 1)
                            {
                                nameBuilder.Append($"_DUP_{idCount}");
                            }
                        }

                        if (unpackNestedPacks == true && file.DataSize >= 8)
                        {
                            input.Position = file.DataOffset;
                            var fileMagic = input.ReadValueU32(Endian.Little);
                            if (fileMagic == PackFile.Signature || fileMagic.Swap() == PackFile.Signature)
                            {
                                input.Position = file.DataOffset;
                                var nestedPack = HandleNestedPack(input, fileQueue, file.Id, nameBuilder.ToString(), parent);
                                fileContainers.Add(nestedPack);
                                parent.FileManifests.Add(new FileTableManifest.File()
                                {
                                    Id       = file.Id,
                                    NameHash = file.NameHash,
                                    Name     = name,
                                    IsPack   = true,
                                    PackId   = PackId.Create(file.PackRawId),
                                    Path     = CleanPathForManifest(PathHelper.GetRelativePath(parent.BasePath, nestedPack.ManifestPath)),
                                });
                                continue;
                            }
                        }

                        var outputPath = Path.Combine(parent.BasePath, nameBuilder.ToString());

                        var outputParentPath = Path.GetDirectoryName(outputPath);
                        if (string.IsNullOrEmpty(outputParentPath) == false)
                        {
                            Directory.CreateDirectory(outputParentPath);
                        }

                        input.Position = file.DataOffset;
                        var extension = FileDetection.Guess(input, (int)file.DataSize, file.DataSize);
                        outputPath = Path.ChangeExtension(outputPath, extension);

                        if (verbose == true)
                        {
                            Console.WriteLine(outputPath);
                        }

                        input.Position = file.DataOffset;
                        using (var output = File.Create(outputPath))
                        {
                            output.WriteFromStream(input, file.DataSize);
                        }

                        parent.FileManifests.Add(new FileTableManifest.File()
                        {
                            Id       = file.Id,
                            NameHash = file.NameHash,
                            Name     = name,
                            PackId   = PackId.Create(file.PackRawId),
                            Path     = CleanPathForManifest(PathHelper.GetRelativePath(parent.BasePath, outputPath)),
                        });
                    }
                }

                foreach (var fileContainer in fileContainers)
                {
                    WriteManifest(fileContainer.ManifestPath, fileContainer);
                }

                tableManifest.Directories.Add(new FileTableManifest.Directory()
                {
                    Id              = directory.Id,
                    DataBlockSize   = directory.DataBlockSize,
                    IsInInstallData = directory.IsInInstallData,
                    FileManifest    = CleanPathForManifest(
                        PathHelper.GetRelativePath(outputBasePath, tableDirectory.ManifestPath)),
                });
            }

            WriteManifest(tableManifestPath, tableManifest);
        }