public void ParseGameDataMessages(Byte[] frameData, Function <Byte, Byte> userMessageCallback)
        {
            Int64 gameDataStartOffset = fileStream.Position - frameData.Length;

            // read game data frame into memory
            bitBuffer       = new BitBuffer(frameData);
            readingGameData = true;

            try
            {
                BeginMessageLog(gameDataStartOffset, frameData);

                // start parsing messages
                while (true)
                {
                    Int32  messageFrameOffset = bitBuffer.CurrentByte;
                    Byte   messageId          = bitBuffer.ReadByte();
                    String messageName        = Enum.GetName(typeof(MessageId), messageId);

                    if (messageName == null) // a user message, presumably
                    {
                        messageName = FindMessageIdString(messageId);
                    }

                    LogMessage(messageId, messageName, messageFrameOffset);

                    MessageHandler messageHandler = FindMessageHandler(messageId);

                    // Handle the conversion of user message id's.
                    // Used by demo writing to convert to the current network protocol.
                    if (messageId > 64 && userMessageCallback != null)
                    {
                        Byte newMessageId = userMessageCallback(messageId);

                        if (newMessageId != messageId)
                        {
                            // write the new id to the bitbuffer
                            bitBuffer.SeekBytes(-1);
                            bitBuffer.RemoveBytes(1);
                            bitBuffer.InsertBytes(new Byte[] { newMessageId });
                        }
                    }

                    // unknown message
                    if (messageHandler == null)
                    {
                        throw new ApplicationException(String.Format("Cannot find message handler for message id \"[{0}] {1}\"", messageId, messageName));
                    }

                    // callback takes priority over length
                    if (messageHandler.Callback != null)
                    {
                        messageHandler.Callback();
                    }
                    else if (messageHandler.Length != -1)
                    {
                        Seek(messageHandler.Length);
                    }
                    else
                    {
                        // user messages
                        if (messageId >= 64)
                        {
                            // All non-engine user messages start with a byte that is the number of bytes in the message remaining.
                            Byte length = bitBuffer.ReadByte();
                            Seek(length);
                        }
                        else
                        {
                            throw new ApplicationException(String.Format("Unknown message id \"{0}\"", messageId));
                        }
                    }

                    // Check if we've reached the end of the frame, or if any of the messages have called SkipGameDataFrame (readingGameData will be false).
                    if (bitBuffer.CurrentByte == bitBuffer.Length || !readingGameData)
                    {
                        break;
                    }
                }
            }
            finally
            {
                readingGameData = false;
            }
        }