Esempio n. 1
0
        private XbfObject ReadRootNodeSection(BinaryReaderEx reader)
        {
            if (Header.MajorFileVersion != 2)
            {
                throw new NotSupportedException("Only XBF v2 files are supported.");
            }

            int startPosition = _firstNodeSectionPosition + NodeSectionTable[0].NodeOffset;
            int endPosition   = _firstNodeSectionPosition + NodeSectionTable[0].PositionalOffset;

            reader.BaseStream.Seek(startPosition, SeekOrigin.Begin);

            _rootObjectStack.Clear();
            _objectStack.Clear();
            _objectCollectionStack.Clear();

            ReadRoot(reader, endPosition);

            if (_objectStack.Count != 1)
            {
                throw new InvalidDataException("_objectStack corrupted");
            }

            if (_objectCollectionStack.Count != 0)
            {
                throw new InvalidDataException("_objectCollectionStack corrupted");
            }

            XbfObject rootObject = _objectStack.Pop();

            return(rootObject);
        }
Esempio n. 2
0
        private XbfObject ReadObjectInNodeSection(BinaryReaderEx reader, XbfNodeSection nodeSection, int offset)
        {
            // Save the current position and skip ahead to the new position
            long originalPosition = reader.BaseStream.Position;
            int  newPosition      = _firstNodeSectionPosition + nodeSection.NodeOffset;
            int  newEndPosition   = _firstNodeSectionPosition + nodeSection.PositionalOffset;

            reader.BaseStream.Position = newPosition + offset;

            int objectStackDepthBefore           = _objectStack.Count;
            int objectCollectionStackDepthBefore = _objectCollectionStack.Count;

            // Read the node from the specified position
            ReadNodes(reader, int.MaxValue, true);

            XbfObject obj = _objectStack.Pop();

            if (_objectStack.Count != objectStackDepthBefore)
            {
                throw new InvalidDataException("_objectStack corrupted");
            }
            if (_objectCollectionStack.Count != objectCollectionStackDepthBefore)
            {
                throw new InvalidDataException("_objectCollectionStack corrupted");
            }

            // Return to the original position
            reader.BaseStream.Position = originalPosition;

            return(obj);
        }
Esempio n. 3
0
        private void ReadResourceDictionary(BinaryReaderEx reader, XbfNodeSection nodeSection, bool extended)
        {
            // Resources with keys
            int resourcesCount = reader.Read7BitEncodedInt();

            for (int i = 0; i < resourcesCount; i++)
            {
                string resourceKey = StringTable[reader.ReadUInt16()];
                int    position    = reader.Read7BitEncodedInt(); // Secondary node stream offset

                XbfObject obj = ReadObjectInNodeSection(reader, nodeSection, position);
                obj.Key = resourceKey;
                _objectCollectionStack.Peek().Add(obj);
            }

            // A subset of the resource keys from above seem to get repeated here, purpose unknown
            int count = reader.Read7BitEncodedInt();

            for (int i = 0; i < count; i++)
            {
                string resourceKey = StringTable[reader.ReadUInt16()];
            }

            // Styles with TargetType and no key
            int styleCount = reader.Read7BitEncodedInt();

            for (int i = 0; i < styleCount; i++)
            {
                string targetType = StringTable[reader.ReadUInt16()];
                int    position   = reader.Read7BitEncodedInt(); // Secondary node stream offset

                XbfObject obj = ReadObjectInNodeSection(reader, nodeSection, position);
                _objectCollectionStack.Peek().Add(obj);
            }

            if (extended)
            {
                if (reader.Read7BitEncodedInt() != 0) // TODO: purpose unknown
                {
                    throw new InvalidDataException("Unexpected value");
                }
            }

            // A subset of the target types from above seem to get repeated here, purpose unknown
            int count2 = reader.Read7BitEncodedInt();

            for (int i = 0; i < count2; i++)
            {
                string targetType = StringTable[reader.ReadUInt16()];
            }
        }
Esempio n. 4
0
        private void ReadDeferredElement(BinaryReaderEx reader, XbfNodeSection nodeSection, bool extended)
        {
            string deferredElementName = StringTable[reader.ReadUInt16()];

            if (extended)
            {
                // The following properties can be ignored as they will appear in the secondary node section again
                int count = reader.Read7BitEncodedInt();
                for (int i = 0; i < count; i++)
                {
                    string propertyName  = GetPropertyName(reader.ReadUInt16());
                    object propertyValue = GetPropertyValue(reader);
                }
            }

            ReadNodeSection(reader, nodeSection);
            XbfObject childObj        = _objectStack.Pop();
            XbfObject deferredElement = _objectStack.Peek();

            deferredElement.Children.Add(childObj);
        }
Esempio n. 5
0
        public XbfReader(string path)
        {
            using (var fileStream = File.OpenRead(path))
                using (var reader = new BinaryReaderEx(fileStream, Encoding.Unicode))
                {
                    Header = new XbfHeader(reader);
                    ReadStringTable(reader);
                    AssemblyTable      = ReadTable(reader, r => new XbfAssembly(this, r));
                    TypeNamespaceTable = ReadTable(reader, r => new XbfTypeNamespace(this, r));
                    TypeTable          = ReadTable(reader, r => new XbfType(this, r));
                    PropertyTable      = ReadTable(reader, r => new XbfProperty(this, r));
                    XmlNamespaceTable  = ReadTable(reader, r => StringTable[r.ReadInt32()]);

                    if (Header.MajorFileVersion >= 2)
                    {
                        // Each node section comes in two parts: the nodes themselves come first, followed by line/column data (positional data
                        // which indicates where the objects were located in the source XAML file).
                        // For each node section, there will be two offset numbers: one for the nodes, and one for the positional data.
                        //
                        // There seem to be a few situations that trigger a separate node section to be generated, including:
                        // - Visual state data (VisualStateGroups, VisualStates, etc.) seem to always generate a separate section.
                        //   Some visual state information is included in the primary node stream (after control character 0x0F) but fully-expanded
                        //   objects are only available in the secondary node streams (one per object that has VisualStateGroups defined).
                        // - Resource collections (i.e., groups of objects with x:Key values) seem generate a separate section when they have more than one item.
                        //   Different types of resources seem to generate multiple resource collections for the same object.
                        //   For example, Brush resources are listed separately from Style resources.
                        //
                        // Note that secondary node sections can also contain references to other node sections as well.

                        // We are now at the position in the stream of the first actual node data. We'll need this position later.
                        NodeSectionTable = ReadTable(reader, r => new XbfNodeSection(this, reader));

                        _firstNodeSectionPosition = (int)reader.BaseStream.Position;

                        RootObject = ReadRootNodeSection(reader);
                    }
                }
        }
Esempio n. 6
0
        private void SkipVisualStateBytes(BinaryReaderEx reader)
        {
            // Number of visual states
            int visualStateCount = reader.Read7BitEncodedInt();

            // The following bytes indicate which visual states belong in each group
            int[] visualStateGroupMemberships = new int[visualStateCount];
            for (int i = 0; i < visualStateCount; i++)
            {
                visualStateGroupMemberships[i] = reader.Read7BitEncodedInt();
            }

            // Number of visual states (again?)
            int visualStateCount2 = reader.Read7BitEncodedInt();

            if (visualStateCount != visualStateCount2)
            {
                throw new InvalidDataException("Visual state counts did not match"); // TODO: What does it mean when this happens? Will it ever happen?
            }
            // Get the VisualState objects
            var visualStates = new XbfObject[visualStateCount2];

            for (int i = 0; i < visualStateCount2; i++)
            {
                int nameID = reader.ReadUInt16();

                reader.Read7BitEncodedInt(); // TODO: purpose unknown
                reader.Read7BitEncodedInt(); // TODO: purpose unknown

                // Get the Setters for this VisualState
                int setterCount = reader.Read7BitEncodedInt();
                for (int j = 0; j < setterCount; j++)
                {
                    int setterOffset = reader.Read7BitEncodedInt();
                }

                // Get the AdaptiveTriggers for this VisualState
                int adaptiveTriggerCount = reader.Read7BitEncodedInt();
                for (int j = 0; j < adaptiveTriggerCount; j++)
                {
                    // I'm not sure what this second count is for -- possibly for the number of properties set on the trigger
                    int count = reader.Read7BitEncodedInt();
                    for (int k = 0; k < count; k++)
                    {
                        reader.Read7BitEncodedInt(); // TODO: purpose unknown
                    }
                }

                // Get the StateTriggers for this VisualState
                int stateTriggerCount = reader.Read7BitEncodedInt();
                for (int j = 0; j < stateTriggerCount; j++)
                {
                    int stateTriggerOffset = reader.Read7BitEncodedInt();
                }

                int offsetCount = reader.Read7BitEncodedInt(); // Always 0 or 2
                for (int j = 0; j < offsetCount; j++)
                {
                    var offset = reader.Read7BitEncodedInt(); // Secondary node stream offset of StateTriggers and Setters collection
                }

                if (reader.Read7BitEncodedInt() != 0) // TODO: purpose unknown
                {
                    throw new InvalidDataException("Unexpected value");
                }

                var vs = new XbfObject();
                vs.TypeName = "VisualState";
                vs.Name     = StringTable[nameID];

                visualStates[i] = vs;
            }

            // Number of VisualStateGroups
            int visualStateGroupCount = reader.Read7BitEncodedInt();

            // Get the VisualStateGroup objects
            var visualStateGroups = new XbfObject[visualStateGroupCount];

            for (int i = 0; i < visualStateGroupCount; i++)
            {
                int nameID = reader.ReadUInt16();

                reader.Read7BitEncodedInt(); // TODO, always 1 or 2

                // The offset within the node section for this VisualStateGroup
                int objectOffset = reader.Read7BitEncodedInt();

                var vsg = new XbfObject();
                vsg.TypeName = "VisualStateGroup";
                vsg.Name     = StringTable[nameID];

                // Get the visual states that belong to this group
                var states = new List <XbfObject>();
                for (int j = 0; j < visualStateGroupMemberships.Length; j++)
                {
                    if (visualStateGroupMemberships[j] == i)
                    {
                        states.Add(visualStates[j]);
                    }
                }
                if (states.Count > 0)
                {
                    vsg.Properties.Add(new XbfObjectProperty("States", states));
                }

                visualStateGroups[i] = vsg;
            }

            int visualTransitionCount = reader.Read7BitEncodedInt();

            for (int i = 0; i < visualTransitionCount; i++)
            {
                string toState   = StringTable[reader.ReadUInt16()];
                string fromState = StringTable[reader.ReadUInt16()];
                int    visualTransitionOffset = reader.Read7BitEncodedInt();
            }

            reader.Read7BitEncodedInt(); // TODO: always 1 or 2

            int count2 = reader.Read7BitEncodedInt();

            for (int i = 0; i < count2; i++)
            {
                int visualStateIndex1 = reader.Read7BitEncodedInt(); // Visual state index or -1
                int visualStateIndex2 = reader.Read7BitEncodedInt(); // Visual state index or -1
                reader.Read7BitEncodedInt();
            }

            int count3 = reader.Read7BitEncodedInt(); // TODO: unknown purpose

            for (int i = 0; i < count3; i++)
            {
                reader.Read7BitEncodedInt();
            }

            reader.Read7BitEncodedInt(); // TODO: unknown purpose

            // At the end we have a list of string references
            int stringCount = reader.Read7BitEncodedInt();

            for (int i = 0; i < stringCount; i++)
            {
                string str = StringTable[reader.ReadUInt16()];
            }

            // At this point we have a list of VisualStateGroup objects in the visualStateGroups variable.
            // These could be added to the result, but we already have them there from parsing the specified node section.
        }
Esempio n. 7
0
        private void ReadStyle(BinaryReaderEx reader, XbfNodeSection nodeSection)
        {
            int setterCount = reader.Read7BitEncodedInt();

            for (int i = 0; i < setterCount; i++)
            {
                int valueType = reader.ReadByte();

                string propertyName  = null;
                string typeName      = null; // Name of type implementing the property, currently ignored
                object propertyValue = null;
                int    valueOffset   = 0;

                switch (valueType)
                {
                case 0x01:     // ThemeResource
                case 0x02:     // StaticResource
                case 0x08:     // General objects
                    propertyName = StringTable[reader.ReadUInt16()];
                    typeName     = GetTypeName(reader.ReadUInt16());
                    valueOffset  = reader.Read7BitEncodedInt();
                    break;

                case 0x11:     // ThemeResource
                case 0x12:     // StaticResource
                case 0x18:     // General objects
                    propertyName = GetPropertyName(reader.ReadUInt16());
                    valueOffset  = reader.Read7BitEncodedInt();
                    break;

                case 0x20:
                    propertyName  = StringTable[reader.ReadUInt16()];
                    typeName      = GetTypeName(reader.ReadUInt16());
                    propertyValue = GetPropertyValue(reader);
                    break;

                case 0x30:
                    propertyName  = GetPropertyName(reader.ReadUInt16());
                    propertyValue = GetPropertyValue(reader);
                    break;

                default:
                    throw new InvalidDataException("Unexpected value");
                }

                // General objects can be read directly with ReadObjectInNodeSection
                if (valueType == 0x08 || valueType == 0x18)
                {
                    propertyValue = ReadObjectInNodeSection(reader, nodeSection, valueOffset);
                }

                if (propertyValue != null)
                {
                    var setter = new XbfObject();
                    setter.TypeName = "Setter";
                    setter.Properties.Add(new XbfObjectProperty("Property", propertyName));
                    setter.Properties.Add(new XbfObjectProperty("Value", propertyValue));
                    _objectCollectionStack.Peek().Add(setter);
                }
                else // StaticResource or ThemeResource need to be read with the Setter already on the stack
                {
                    var setter = new XbfObject();
                    setter.TypeName = "Setter";
                    setter.Properties.Add(new XbfObjectProperty("Property", propertyName));
                    _objectCollectionStack.Peek().Add(setter);

                    _objectStack.Push(setter);
                    ReadNodeInNodeSection(reader, nodeSection, valueOffset);
                    _objectStack.Pop();
                }
            }
        }
Esempio n. 8
0
        private void ReadNodes(BinaryReaderEx reader, int endPosition, bool readSingleObject = false, bool readSingleNode = false)
        {
            XbfObject singleObject = null;

            while (reader.BaseStream.Position < endPosition)
            {
                byte nodeType = reader.ReadByte();
                switch (nodeType)
                {
                case 0x01:     // This only occurs at the beginning of some secondary node sections -- not sure what it means
                    break;

                case 0x04:
                    // This seems to have at least four different interpretations -- not sure how to properly decide which one is correct
                    // The logic below seems to work but is most definitely not the correct way

                    // If the following condition is true, we are in a collection that was started with collectionbegin,
                    // making this node likely to be a verbatim string.
                    if (_objectCollectionStack.Peek() != _objectStack.Peek().Children)     // Verbatim text, appears in TextBlock.Inlines
                    {
                        object text = GetPropertyValue(reader);
                        var    obj  = new XbfObject()
                        {
                            TypeName = "Verbatim"
                        };                                                       // For simplicity, use a fake Verbatim object to store the text
                        obj.Properties.Add(new XbfObjectProperty("Value", text));
                        _objectStack.Push(obj);
                    }
                    else if (_objectStack.Peek() == _rootObjectStack.Peek())     // Class of root object
                    {
                        object cl = GetPropertyValue(reader);
                        _objectStack.Peek().Properties.Add(new XbfObjectProperty("x:Class", cl));
                    }
                    else     // Values encountered here in some files are class modifiers ("private", "internal", "public") or event handler names (e.g. "CancelButton_Click")
                    {
                        object val = GetPropertyValue(reader);
                    }
                    break;

                case 0x0C:     // Connection
                    // This byte (0x0C) indicates the current object needs to be connected to something in the generated Connect method.
                    // This can include event handlers, named objects (to be accessed via instance variables), etc.
                    // Event handlers aren't explicitly included as part of the XBF node stream since they're wired up in (generated) code.
                    // Each object that needs to be connected to something has a unique ID indicated in this section.

                    // Connection ID
                    _objectStack.Peek().ConnectionID = (int)GetPropertyValue(reader);
                    break;

                case 0x0D:     // x:Name
                    _objectStack.Peek().Name = GetPropertyValue(reader).ToString();
                    break;

                case 0x0E:     // x:Uid
                    _objectStack.Peek().Uid = GetPropertyValue(reader).ToString();
                    break;

                case 0x11:     // DataTemplate
                    ReadDataTemplate(reader);
                    break;

                case 0x1A:     // Property
                case 0x1B:     // Property (not sure what the difference from 0x1A is)
                {
                    string propertyName  = GetPropertyName(reader.ReadUInt16());
                    object propertyValue = GetPropertyValue(reader);
                    _objectStack.Peek().Properties.Add(new XbfObjectProperty(propertyName, propertyValue));
                }
                break;

                case 0x1D:                                                        // Style
                {
                    string propertyName   = GetPropertyName(reader.ReadUInt16()); // Always "TargetType"
                    string targetTypeName = GetTypeName(reader.ReadUInt16());
                    _objectStack.Peek().Properties.Add(new XbfObjectProperty(propertyName, targetTypeName));
                }
                break;

                case 0x1E:     // StaticResource
                {
                    string propertyName  = GetPropertyName(reader.ReadUInt16());
                    object propertyValue = GetPropertyValue(reader);
                    propertyValue = string.Format("{{StaticResource {0}}}", propertyValue);
                    _objectStack.Peek().Properties.Add(new XbfObjectProperty(propertyName, propertyValue));
                }
                break;

                case 0x1F:     // TemplateBinding
                {
                    string propertyName = GetPropertyName(reader.ReadUInt16());
                    string bindingPath  = GetPropertyName(reader.ReadUInt16());
                    bindingPath = string.Format("{{TemplateBinding {0}}}", bindingPath);
                    _objectStack.Peek().Properties.Add(new XbfObjectProperty(propertyName, bindingPath));
                }
                break;

                case 0x24:     // ThemeResource
                {
                    string propertyName  = GetPropertyName(reader.ReadUInt16());
                    object propertyValue = GetPropertyValue(reader);
                    propertyValue = string.Format("{{ThemeResource {0}}}", propertyValue);
                    _objectStack.Peek().Properties.Add(new XbfObjectProperty(propertyName, propertyValue));
                }
                break;

                case 0x22:     // StaticResource object
                {
                    object propertyValue = GetPropertyValue(reader);
                    var    obj           = new XbfObject()
                    {
                        TypeName = "StaticResource"
                    };
                    obj.Properties.Add(new XbfObjectProperty("ResourceKey", propertyValue));
                    _objectStack.Push(obj);

                    if (readSingleObject)
                    {
                        return;
                    }
                }
                break;

                case 0x23:     // ThemeResource object
                {
                    object propertyValue = GetPropertyValue(reader);
                    var    obj           = new XbfObject()
                    {
                        TypeName = "ThemeResource"
                    };
                    obj.Properties.Add(new XbfObjectProperty("ResourceKey", propertyValue));
                    _objectStack.Push(obj);

                    if (readSingleObject)
                    {
                        return;
                    }
                }
                break;

                case 0x13:     // Object collection begin
                {
                    string propertyName = GetPropertyName(reader.ReadUInt16());
                    var    collection   = new XbfObjectCollection();
                    _objectStack.Peek().Properties.Add(new XbfObjectProperty(propertyName, collection));
                    _objectCollectionStack.Push(collection);
                }
                break;

                case 0x02:     // End of collection
                    // The collection has already been added as a property, so we just need to pop it off the stack
                    _objectCollectionStack.Pop();
                    break;

                case 0x14:     // Object begin
                {
                    // We are starting a new object inside of the current object. It will be applied as a property of the current object.
                    var subObj = new XbfObject();
                    subObj.TypeName = GetTypeName(reader.ReadUInt16());
                    _objectStack.Push(subObj);

                    _objectCollectionStack.Push(subObj.Children);

                    if (readSingleObject && singleObject == null)
                    {
                        singleObject = subObj;
                    }
                }
                break;

                case 0x21:     // Object end
                    // Pop Children collection of ending object off the object collection stack if it had been pushed there earlier
                    if (_objectCollectionStack.Count > 0 && _objectCollectionStack.Peek() == _objectStack.Peek().Children)
                    {
                        _objectCollectionStack.Pop();
                    }

                    // Return if we are supposed to read only a single object and we just reached the end of it
                    if (readSingleObject && _objectStack.Peek() == singleObject)
                    {
                        return;
                    }

                    // Return if we reached the end of a nested root object
                    if (_objectStack.Peek() == _rootObjectStack.Peek())
                    {
                        return;
                    }
                    break;

                case 0x07:     // Add the new object as a property of the current object
                case 0x20:     // Same as 0x07, but this occurs when the object is a Binding, TemplateBinding, CustomResource, RelativeSource or NullExtension value
                {
                    string propertyName = GetPropertyName(reader.ReadUInt16());
                    var    subObj       = _objectStack.Pop();
                    _objectStack.Peek().Properties.Add(new XbfObjectProperty(propertyName, subObj));
                }
                break;

                case 0x08:     // Add the object to the list (simple)
                case 0x09:     // 0x09 seems to be used instead of 0x08 for Styles that don't have a Key
                {
                    var obj = _objectStack.Pop();
                    _objectCollectionStack.Peek().Add(obj);
                }
                break;

                case 0x0A:     // Add the object to the list with a key
                {
                    var obj = _objectStack.Pop();
                    // Note: technically the key is a property of the collection rather than the object itself, but for simplicity (and display purposes) we're just adding it to the object.
                    obj.Key = GetPropertyValue(reader).ToString();
                    _objectCollectionStack.Peek().Add(obj);
                }
                break;

                case 0x15:     // Literal value (x:Int32, x:String, etc. and types in Windows.UI.Xaml namespace)
                case 0x16:     // Literal value of type that is not in Windows.UI.Xaml namespace
                {
                    XbfObject obj = new XbfObject();
                    obj.TypeName = GetTypeName(reader.ReadUInt16());
                    object value = GetPropertyValue(reader);
                    obj.Properties.Add(new XbfObjectProperty("Value", value));         // TODO: This isn't really correct since the value for these types just appears in the object body
                    _objectStack.Push(obj);

                    if (readSingleObject && singleObject == null)
                    {
                        singleObject = obj;
                    }
                }
                break;

                case 0x0F:     // Reference to a different node section
                    ReadNodeSectionReference(reader);
                    break;

                case 0x12:     // Root objects can be nested
                case 0x0B:
                case 0x17:
                    // Rewind and handle the object in ReadRoot
                    reader.BaseStream.Seek(-1, SeekOrigin.Current);
                    ReadRoot(reader, endPosition);

                    if (readSingleObject && singleObject == null)
                    {
                        return;     // The root object we just read is the single object we are supposed to read
                    }
                    break;

                case 0x18:     // Looks to be equivalent to 0x17 but with an additional constructor argument
                case 0x19:
                {
                    string typeName = GetTypeName(reader.ReadUInt16());
                    object argument = GetPropertyValue(reader);
                    // I am not aware of any way to specify constructor arguments in UWP XAML but XAML 2009 had an x:Arguments attribute for this, so let's use that instead
                    _objectStack.Peek().Properties.Add(new XbfObjectProperty("x:Class", typeName));
                    _objectStack.Peek().Properties.Add(new XbfObjectProperty("x:Arguments", argument));
                }
                break;

                case 0x8B:     // Unknown purpose, only encountered in one file
                    _objectStack.Pop();
                    break;

                default:
                    throw new InvalidDataException(string.Format("Unrecognized character 0x{0:X2} while parsing object", nodeType));
                }

                if (readSingleNode)
                {
                    break;
                }
            }
        }
Esempio n. 9
0
        private void ReadRoot(BinaryReaderEx reader, int endPosition)
        {
            // The first node section contains the primary XAML data (and the root XAML object)
            XbfObject rootObject = new XbfObject();

            _rootObjectStack.Push(rootObject);
            _objectStack.Push(rootObject);

            _objectCollectionStack.Push(rootObject.Children);

            try
            {
                // Read the node bytes
                while (reader.BaseStream.Position < endPosition)
                {
                    byte nodeType = reader.ReadByte();
                    switch (nodeType)
                    {
                    case 0x12:     // This usually appears to be the first byte encountered. I'm not sure what the difference between 0x12 and 0x03 is.
                    case 0x03:     // Root node namespace declaration
                    {
                        string namespaceName = XmlNamespaceTable[reader.ReadUInt16()];
                        string prefix        = ReadString(reader);
                        _namespacePrefixes[namespaceName] = prefix;
                        if (!string.IsNullOrEmpty(prefix))
                        {
                            prefix = "xmlns:" + prefix;
                        }
                        else
                        {
                            prefix = "xmlns";
                        }
                        rootObject.Properties.Add(new XbfObjectProperty(prefix, namespaceName));
                    }
                    break;

                    case 0x0B:     // Indicates the class of the root object (i.e. x:Class)
                    {
                        string className = ReadString(reader);
                        rootObject.Properties.Add(new XbfObjectProperty("x:Class", className));
                    }
                    break;

                    case 0x17:     // Root object begin
                    {
                        rootObject.TypeName = GetTypeName(reader.ReadUInt16());
                        ReadNodes(reader, endPosition);
                        goto exitLoop;
                    }

                    default:
                        throw new InvalidDataException(string.Format("Unrecognized character 0x{0:X2} in node stream", nodeType));
                    }
                }
            }
            catch (Exception e)
            {
                throw new InvalidDataException(string.Format("Error parsing node stream at file position {0} (0x{0:X}) (node start position was: {1} (0x{1:X}))" + Environment.NewLine, reader.BaseStream.Position - 1, _firstNodeSectionPosition) + e.ToString(), e);
            }

exitLoop:

            if (_rootObjectStack.Pop() != rootObject)
            {
                throw new InvalidDataException("_rootObjectStack corrupted");
            }

            if (_objectStack.Peek() != rootObject)
            {
                throw new InvalidDataException("_objectStack corrupted");
            }
        }