protected void ParseTextInputLog(BinaryReader br, IMovie movie, MiscHeaderInfo info)
        {
            Octoshock.SyncSettings settings    = new Octoshock.SyncSettings();
            SimpleController       controllers = new SimpleController();

            settings.FIOConfig.Devices8 = new[]
            {
                info.Player1Type,
                OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None,
                info.Player2Type,
                OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None
            };
            controllers.Definition = Octoshock.CreateControllerDefinition(settings);

            string[] buttons =
            {
                "Start", "Up", "Right", "Down", "Left",
                "L2",    "R2", "L1",    "R1",   "Triangle","Circle","Cross", "Square"
            };

            bool isCdTrayOpen = false;
            int  cdNumber     = 1;

            int player1Count = info.Player1Type == OctoshockDll.ePeripheralType.None ? 1 : info.Player1Type == OctoshockDll.ePeripheralType.Pad ? 15 : 33;
            int player2Count = info.Player2Type == OctoshockDll.ePeripheralType.None ? 1 : info.Player2Type == OctoshockDll.ePeripheralType.Pad ? 15 : 33;
            int strCount     = player1Count + player2Count + 4;         // 2 for control byte and pipe and line feed chars

            for (int frame = 0; frame < info.FrameCount; ++frame)
            {
                var mnemonicStr = new string(br.ReadChars(strCount));

                // Junk whitespace at the end of a file
                if (string.IsNullOrWhiteSpace(mnemonicStr))
                {
                    continue;
                }

                // Gross, if not CR LF, this will fail, but will the PSXjin?
                if (!mnemonicStr.EndsWith("|\r\n"))
                {
                    Result.Errors.Add("Unable to parse text input, unknown configuration");
                }

                var split      = mnemonicStr.Replace("\r\n", "").Split('|');
                var player1Str = split[0];
                var player2Str = split[1];
                var controlStr = split[2];
                if (info.Player1Type != OctoshockDll.ePeripheralType.None)
                {
                    // As L3 and R3 don't exist on a standard gamepad, handle them separately later.  Unfortunately
                    // due to the layout, we handle select separately too first.
                    controllers["P1 Select"] = player1Str[0] != '.';

                    if (info.Player1Type == OctoshockDll.ePeripheralType.DualShock)
                    {
                        controllers["P1 L3"] = player1Str[1] != '.';
                        controllers["P1 R3"] = player1Str[2] != '.';
                    }

                    int offSet = info.Player1Type == OctoshockDll.ePeripheralType.Pad ? 0 : 2;
                    for (int button = 1; button < buttons.Length; button++)
                    {
                        controllers[$"P1 {buttons[button]}"] = player1Str[button + offSet] != '.';
                    }

                    if (info.Player1Type == OctoshockDll.ePeripheralType.DualShock)
                    {
                        // The analog controls are encoded as four space-separated numbers with a leading space
                        string leftXRaw  = player1Str.Substring(16, 4);
                        string leftYRaw  = player1Str.Substring(20, 4);
                        string rightXRaw = player1Str.Substring(24, 4);
                        string rightYRaw = player1Str.Substring(28, 4);

                        var leftX  = ("P1 LStick X", (int)float.Parse(leftXRaw));
                        var leftY  = ("P1 LStick Y", (int)float.Parse(leftYRaw));
                        var rightX = ("P1 RStick X", (int)float.Parse(rightXRaw));
                        var rightY = ("P1 RStick Y", (int)float.Parse(rightYRaw));

                        controllers.AcceptNewAxes(new[] { leftX, leftY, rightX, rightY });
                    }
                }

                if (info.Player2Type != OctoshockDll.ePeripheralType.None)
                {
                    // As L3 and R3 don't exist on a standard gamepad, handle them separately later.  Unfortunately
                    // due to the layout, we handle select separately too first.
                    controllers["P2 Select"] = player2Str[0] != '.';

                    if (info.Player2Type == OctoshockDll.ePeripheralType.DualShock)
                    {
                        controllers["P2 L3"] = player2Str[1] != '.';
                        controllers["P2 R3"] = player2Str[2] != '.';
                    }

                    int offSet = info.Player2Type == OctoshockDll.ePeripheralType.Pad ? 0 : 2;
                    for (int button = 1; button < buttons.Length; button++)
                    {
                        controllers[$"P2 {buttons[button]}"] = player2Str[button + offSet] != '.';
                    }

                    if (info.Player2Type == OctoshockDll.ePeripheralType.DualShock)
                    {
                        // The analog controls are encoded as four space-separated numbers with a leading space
                        string leftXRaw  = player2Str.Substring(16, 4);
                        string leftYRaw  = player2Str.Substring(20, 4);
                        string rightXRaw = player2Str.Substring(24, 4);
                        string rightYRaw = player2Str.Substring(28, 4);

                        var leftX  = ("P2 LStick X", (int)float.Parse(leftXRaw));
                        var leftY  = ("P2 LStick Y", (int)float.Parse(leftYRaw));
                        var rightX = ("P2 RStick X", (int)float.Parse(rightXRaw));
                        var rightY = ("P2 RStick Y", (int)float.Parse(rightYRaw));

                        controllers.AcceptNewAxes(new[] { leftX, leftY, rightX, rightY });
                    }
                }

                byte controlState = (byte)controlStr[0];
                controllers["Reset"] = (controlState & 0x02) != 0;
                if ((controlState & 0x04) != 0)
                {
                    if (isCdTrayOpen)
                    {
                        controllers["Close"] = true;
                        cdNumber++;
                    }
                    else
                    {
                        controllers["Open"] = true;
                    }

                    isCdTrayOpen = !isCdTrayOpen;
                }
                else
                {
                    controllers["Close"] = false;
                    controllers["Open"]  = false;
                }

                var discSelect = ("Disc Select", cdNumber);
                controllers.AcceptNewAxes(new[] { discSelect });

                if ((controlState & 0xFC) != 0)
                {
                    Result.Warnings.Add($"Ignored toggle hack flag on frame {frame}");
                }

                movie.AppendFrame(controllers);
            }
        }
        protected void ParseBinaryInputLog(BinaryReader br, IMovie movie, MiscHeaderInfo info)
        {
            var settings    = new Octoshock.SyncSettings();
            var controllers = new SimpleController();

            settings.FIOConfig.Devices8 = new[]
            {
                info.Player1Type,
                OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None,
                info.Player2Type,
                OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None
            };
            controllers.Definition = Octoshock.CreateControllerDefinition(settings);

            string[] buttons =
            {
                "Select", "L3", "R3", "Start", "Up",       "Right",  "Down",  "Left",
                "L2",     "R2", "L1", "R1",    "Triangle", "Circle", "Cross", "Square"
            };

            bool isCdTrayOpen = false;
            int  cdNumber     = 1;

            for (int frame = 0; frame < info.FrameCount; ++frame)
            {
                if (info.Player1Type != OctoshockDll.ePeripheralType.None)
                {
                    ushort controllerState = br.ReadUInt16();

                    // As L3 and R3 don't exist on a standard gamepad, handle them separately later.  Unfortunately
                    // due to the layout, we handle select separately too first.
                    controllers["P1 Select"] = (controllerState & 0x1) != 0;

                    for (int button = 3; button < buttons.Length; button++)
                    {
                        controllers[$"P1 {buttons[button]}"] = ((controllerState >> button) & 0x1) != 0;
                        if (((controllerState >> button) & 0x1) != 0 && button > 15)
                        {
                            continue;
                        }
                    }

                    if (info.Player1Type == OctoshockDll.ePeripheralType.DualShock)
                    {
                        controllers["P1 L3"] = (controllerState & 0x2) != 0;
                        controllers["P1 R3"] = (controllerState & 0x4) != 0;
                        var leftX  = ("P1 LStick X", (int)br.ReadByte());
                        var leftY  = ("P1 LStick Y", (int)br.ReadByte());
                        var rightX = ("P1 RStick X", (int)br.ReadByte());
                        var rightY = ("P1 RStick Y", (int)br.ReadByte());

                        controllers.AcceptNewAxes(new[] { leftX, leftY, rightX, rightY });
                    }
                }

                if (info.Player2Type != OctoshockDll.ePeripheralType.None)
                {
                    ushort controllerState = br.ReadUInt16();
                    for (int button = 0; button < buttons.Length; button++)
                    {
                        controllers[$"P2 {buttons[button]}"] = ((controllerState >> button) & 0x1) != 0;
                        if (((controllerState >> button) & 0x1) != 0 && button > 15)
                        {
                            continue;
                        }
                    }

                    if (info.Player2Type == OctoshockDll.ePeripheralType.DualShock)
                    {
                        var leftX  = ("P2 LStick X", (int)br.ReadByte());
                        var leftY  = ("P2 LStick Y", (int)br.ReadByte());
                        var rightX = ("P2 RStick X", (int)br.ReadByte());
                        var rightY = ("P2 RStick Y", (int)br.ReadByte());

                        controllers.AcceptNewAxes(new[] { leftX, leftY, rightX, rightY });
                    }
                }

                byte controlState = br.ReadByte();
                controllers["Reset"] = (controlState & 0x02) != 0;
                if ((controlState & 0x04) != 0)
                {
                    if (isCdTrayOpen)
                    {
                        controllers["Close"] = true;
                        cdNumber++;
                    }
                    else
                    {
                        controllers["Open"] = true;
                    }

                    isCdTrayOpen = !isCdTrayOpen;
                }
                else
                {
                    controllers["Close"] = false;
                    controllers["Open"]  = false;
                }

                var discSelect = ("Disc Select", cdNumber);
                controllers.AcceptNewAxes(new[] { discSelect });

                if ((controlState & 0xFC) != 0)
                {
                    Result.Warnings.Add($"Ignored toggle hack flag on frame {frame}");
                }

                movie.AppendFrame(controllers);
            }
        }