public static PropertyListContext HeaderFromBinaryReader(this PropertyListContext result, BinaryReader reader)
        {
            result.MagicNumber       = reader.ReadAsciiString(6);
            result.FileFormatVersion = reader.ReadAsciiString(2);

            return(result);
        }
        public static PropertyListContext OffsetTableFromBinaryReader(this PropertyListContext result, BinaryReader reader)
        {
            reader.BaseStream.Seek(result.OffsetTableStart, SeekOrigin.Begin);

            result.Offsets = reader.ReadIntArrayBigEndian(result.OffsetTableOffsetSize, (int)result.NumObjects);

            return(result);
        }
        /// <remarks>
        /// set 1100 nnnn[int], objref*
        /// nnnn is count, unless '1111', then int count follows
        /// </remarks>
        private PropertyContext ReadSet(BinaryReader reader, PropertyListContext ctx, long position, int count)
        {
            count = ReadObjectSize(reader, count);

            var valueRefs = reader.ReadIntArrayBigEndian(ctx.ObjectRefSize, count);
            int totalSize = reader.GetOffsetFromCurrentPosition(position);

            var value = ParseArrayCore(reader, ctx, count, valueRefs);

            return(CreatePropertyContext(position, count, totalSize, PropertyType.Set, value));
        }
        private object ParseArrayCore(BinaryReader reader, PropertyListContext ctx, int count, int[] valueRefs)
        {
            var result = new object[count];

            for (int i = 0; i < count; i++)
            {
                var valueRef     = valueRefs[i];
                var valueContext = ParseObjectByOffsetIndex(reader, ctx, valueRef);

                result[i] = valueContext.Value;
            }

            return(result);
        }
        public static PropertyListContext TrailerFromBinaryReader(this PropertyListContext result, BinaryReader reader)
        {
            reader.BaseStream.Seek(-32, SeekOrigin.End);

            var data = reader.ReadBytes(32);

            result.SortVersion           = data[5];
            result.OffsetTableOffsetSize = data[6];
            result.ObjectRefSize         = data[7];
            result.NumObjects            = BinaryPrimitives.ReadInt64BigEndian(data.AsSpan(8, 8));
            result.TopObjectOffset       = BinaryPrimitives.ReadInt64BigEndian(data.AsSpan(16, 8));
            result.OffsetTableStart      = BinaryPrimitives.ReadInt64BigEndian(data.AsSpan(24, 8));

            return(result);
        }
 private void TraceContext(PropertyListContext context)
 {
     Trace.TraceInformation("MagicNumber:{0}", context.MagicNumber);
     Trace.TraceInformation("FileFormatVersion:{0}", context.FileFormatVersion);
     Trace.TraceInformation("SortVersion:{0}", context.SortVersion);
     Trace.TraceInformation("OffsetTableOffsetSize:{0}", context.OffsetTableOffsetSize);
     Trace.TraceInformation("ObjectRefSize:{0}", context.ObjectRefSize);
     Trace.TraceInformation("NumObjects:{0}", context.NumObjects);
     Trace.TraceInformation("TopObjectOffset:{0}", context.TopObjectOffset);
     Trace.TraceInformation("OffsetTableStart:{0:x8}", context.OffsetTableStart);
     for (int i = 0; i < context.Offsets.Length; i++)
     {
         Trace.TraceInformation("Offset[{0}]:{1:x8}", i, context.Offsets[i]);
     }
 }
        private object ParseDictionaryCore(BinaryReader reader, PropertyListContext ctx, int count, int[] keyRefs, int[] valueRefs)
        {
            var result = new Dictionary <string, object>();

            for (int i = 0; i < count; i++)
            {
                var keyRef   = keyRefs[i];
                var valueRef = valueRefs[i];

                var keyContext   = ParseObjectByOffsetIndex(reader, ctx, keyRef);
                var valueContext = ParseObjectByOffsetIndex(reader, ctx, valueRef);

                var key   = (string)keyContext.Value;
                var value = valueContext.Value;

                result.Add(key, value);
            }

            return(result);
        }
        internal PropertyContext LoadFrom(BinaryReader reader)
        {
            PropertyContext result = default;

            if (reader.BaseStream.Length >= MinimumViableFileSize)
            {
                var context = new PropertyListContext()
                              .HeaderFromBinaryReader(reader);

                if (context.IsSupportedBinaryPropertyList())
                {
                    context = context.TrailerFromBinaryReader(reader)
                              .OffsetTableFromBinaryReader(reader);

                    result = ParseObjectByOffsetIndex(reader, context, (int)context.TopObjectOffset);
                }
            }

            return(result);
        }
        internal PropertyListContext Parse(BinaryReader reader)
        {
            PropertyListContext result = default;

            if (reader.BaseStream.Length >= MinimumViableFileSize)
            {
                result = new PropertyListContext()
                         .HeaderFromBinaryReader(reader);

                if (result.IsSupportedBinaryPropertyList())
                {
                    result = result.TrailerFromBinaryReader(reader)
                             .OffsetTableFromBinaryReader(reader);

#if TRACE_BINARYPROPERTYLISTREADER
                    TraceContext(result);
#endif

                    result.Root = ParseObjectByOffsetIndex(reader, result, (int)result.TopObjectOffset);
                }
            }

            return(result);
        }
 public static bool IsSupportedBinaryPropertyList(this PropertyListContext item)
 {
     return(string.Equals(MagicNumber, item.MagicNumber, StringComparison.OrdinalIgnoreCase) &&
            string.Equals(VersionNumber, item.FileFormatVersion, StringComparison.Ordinal));
 }
 private PropertyContext ParseObjectByOffsetIndex(BinaryReader reader, PropertyListContext ctx, int index)
 {
     return(ParseObjectByPosition(reader, ctx, ctx.Offsets[index]));
 }
        private PropertyContext ParseObject(BinaryReader reader, PropertyListContext ctx, long position)
        {
            PropertyContext result;

            var marker = reader.ReadByte();
            var msn    = marker & 0xf0;
            var lsn    = marker & 0x0f;

#if TRACE_BINARYPROPERTYLISTREADER
            Trace.TraceInformation("marker:{0:x2},msn:{1:x2},lsn:{2:x2}", marker, msn, lsn);
#endif

            switch (msn)
            {
            case 0b0000_0000:
                switch (lsn)
                {
                case 0b0000_0000:
                    result = CreatePropertyContext(position, 0, 1, PropertyType.Null, null);
                    break;

                case 0b0000_1000:
                    result = CreatePropertyContext(position, 0, 1, PropertyType.Bool, false);
                    break;

                case 0b0000_1001:
                    result = CreatePropertyContext(position, 0, 1, PropertyType.Bool, true);
                    break;

                case 0b0000_1111:
                    result = CreatePropertyContext(position, 0, 1, PropertyType.Fill, null);
                    break;

                default:
                    throw new InvalidDataException("Unrecognised object type " + marker.ToString("x2"));
                }
                break;

            case 0b0001_0000:
                result = ReadInteger(reader, position, lsn);
                break;

            case 0b0010_0000:
                result = ReadReal(reader, position, lsn);
                break;

            case 0b0011_0000:
                result = ReadDate(reader, position, lsn);
                break;

            case 0b0100_0000:
                result = ReadData(reader, position, lsn);
                break;

            case 0b0101_0000:
                result = ReadAsciiString(reader, position, lsn);
                break;

            case 0b0110_0000:
                result = ReadUnicodeString(reader, position, lsn);
                break;

            case 0b1000_0000:
                result = ReadUid(reader, position, lsn);
                break;

            case 0b1010_0000:
                result = ReadArray(reader, ctx, position, lsn);
                break;

            case 0b1100_0000:
                result = ReadSet(reader, ctx, position, lsn);
                break;

            case 0b1101_0000:
                result = ReadDictionary(reader, ctx, position, lsn);
                break;

            default:
                throw new InvalidDataException("Unrecognised object type " + marker.ToString("x2"));
            }

            return(result);
        }
        private PropertyContext ParseObjectByPosition(BinaryReader reader, PropertyListContext ctx, long position)
        {
            reader.BaseStream.Seek(position, SeekOrigin.Begin);

            return(ParseObject(reader, ctx, position));
        }