//------------------------------------------------------------------------------------------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 } }
//------------------------------------------------------------------------------------------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); } } } }
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); }
//------------------------------------------------------------------------------------------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); }