/*
 * Fast486 386/486 CPU Emulation Library
 * opgroups.c
 *
 * Copyright (C) 2015 Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/* INCLUDES *******************************************************************/

#include <windef.h>

// #define NDEBUG
#include <debug.h>

#include <fast486.h>
#include "opcodes.h"
#include "common.h"

/* PRIVATE FUNCTIONS **********************************************************/

static
inline
ULONG
Fast486ArithmeticOperation(PFAST486_STATE State,
                           INT Operation,
                           ULONG FirstValue,
                           ULONG SecondValue,
                           UCHAR Bits)
{
    ULONG Result;
    ULONG SignFlag = 1 << (Bits - 1);
    ULONG MaxValue = (SignFlag - 1) | SignFlag;

    /* Make sure the values don't exceed the maximum for their size */
    FirstValue &= MaxValue;
    SecondValue &= MaxValue;

    /* Check which operation is this */
    switch (Operation)
    {
        /* ADD */
        case 0:
        {
            Result = (FirstValue + SecondValue) & MaxValue;

            /* Update CF, OF and AF */
            State->Flags.Cf = (Result < FirstValue) && (Result < SecondValue);
            State->Flags.Of = ((FirstValue & SignFlag) == (SecondValue & SignFlag))
                              && ((FirstValue & SignFlag) != (Result & SignFlag));
            State->Flags.Af = ((((FirstValue & 0x0F) + (SecondValue & 0x0F)) & 0x10) != 0);

            break;
        }

        /* OR */
        case 1:
        {
            Result = FirstValue | SecondValue;
            State->Flags.Cf = State->Flags.Of = FALSE;
            break;
        }

        /* ADC */
        case 2:
        {
            INT Carry = State->Flags.Cf ? 1 : 0;

            Result = (FirstValue + SecondValue + Carry) & MaxValue;

            /* Update CF, OF and AF */
            State->Flags.Cf = ((SecondValue == MaxValue) && (Carry == 1))
                              || ((Result < FirstValue) && (Result < (SecondValue + Carry)));
            State->Flags.Of = ((FirstValue & SignFlag) == (SecondValue & SignFlag))
                              && ((FirstValue & SignFlag) != (Result & SignFlag));
            State->Flags.Af = ((FirstValue ^ SecondValue ^ Result) & 0x10) != 0;

            break;
        }

        /* SBB */
        case 3:
        {
            INT Carry = State->Flags.Cf ? 1 : 0;

            Result = (FirstValue - SecondValue - Carry) & MaxValue;

            /* Update CF, OF and AF */
            State->Flags.Cf = Carry
                              ? (FirstValue <= SecondValue)
                              : (FirstValue < SecondValue);
            State->Flags.Of = ((FirstValue & SignFlag) != (SecondValue & SignFlag))
                              && ((FirstValue & SignFlag) != (Result & SignFlag));
            State->Flags.Af = ((FirstValue ^ SecondValue ^ Result) & 0x10) != 0;

            break;
        }

        /* AND */
        case 4:
        {
            Result = FirstValue & SecondValue;
            State->Flags.Cf = State->Flags.Of = FALSE;
            break;
        }

        /* SUB or CMP */
        case 5:
        case 7:
        {
            Result = (FirstValue - SecondValue) & MaxValue;

            /* Update CF, OF and AF */
            State->Flags.Cf = (FirstValue < SecondValue);
            State->Flags.Of = ((FirstValue & SignFlag) != (SecondValue & SignFlag))
                              && ((FirstValue & SignFlag) != (Result & SignFlag));
            State->Flags.Af = (FirstValue & 0x0F) < (SecondValue & 0x0F);

            break;
        }

        /* XOR */
        case 6:
        {
            Result = FirstValue ^ SecondValue;
            State->Flags.Cf = State->Flags.Of = FALSE;
            break;
        }

        default:
        {
            /* Shouldn't happen */
            ASSERT(FALSE);
        }
    }

    /* Update ZF, SF and PF */
    State->Flags.Zf = (Result == 0);
    State->Flags.Sf = ((Result & SignFlag) != 0);
    State->Flags.Pf = Fast486CalculateParity(LOBYTE(Result));

    /* Return the result */
    return Result;
}

static
inline
ULONG
Fast486RotateOperation(PFAST486_STATE State,
                       INT Operation,
                       ULONG Value,
                       UCHAR Bits,
                       UCHAR Count)
{
    ULONG HighestBit = 1 << (Bits - 1);
    ULONG MaxValue = HighestBit | (HighestBit - 1);
    ULONG Result;

    /* Normalize the count */
    Count &= 0x1F;

    if ((Operation == 2) || (Operation == 3)) Count %= Bits + 1;

    /* If the count is zero, do nothing */
    if (Count == 0) return Value;

    /* Check which operation is this */
    switch (Operation)
    {
        /* ROL */
        case 0:
        {
            Count %= Bits;
            Result = (Value << Count) | (Value >> (Bits - Count));

            /* Update CF and OF */
            State->Flags.Cf = Result & 1;
            if (Count == 1) State->Flags.Of = State->Flags.Cf
                                              ^ ((Result & HighestBit) != 0);

            break;
        }

        /* ROR */
        case 1:
        {
            Count %= Bits;
            Result = (Value >> Count) | (Value << (Bits - Count));

            /* Update CF and OF */
            State->Flags.Cf = ((Result & HighestBit) != 0);
            if (Count == 1) State->Flags.Of = State->Flags.Cf
                                              ^ ((Result & (HighestBit >> 1)) != 0);

            break;
        }

        /* RCL */
        case 2:
        {
            Result = (Value << Count) | (State->Flags.Cf << (Count - 1));

            /* Complete the calculation, but make sure we don't shift by too much */
            if ((Bits - Count) < 31) Result |= Value >> (Bits - Count + 1);

            /* Update CF and OF */
            State->Flags.Cf = ((Value & (1 << (Bits - Count))) != 0);
            if (Count == 1) State->Flags.Of = State->Flags.Cf ^ ((Result & HighestBit) != 0);

            break;
        }

        /* RCR */
        case 3:
        {
            /* Update OF */
            if (Count == 1) State->Flags.Of = State->Flags.Cf ^ ((Value & HighestBit) != 0);

            Result = (Value >> Count) | (State->Flags.Cf << (Bits - Count));

            /* Complete the calculation, but make sure we don't shift by too much */
            if ((Bits - Count) < 31) Result |= Value << (Bits - Count + 1);

            /* Update CF */
            State->Flags.Cf = ((Value & (1 << (Count - 1))) != 0);

            break;
        }

        /* SHL/SAL */
        case 4:
        case 6:
        {
            Result = Value << Count;

            /* Update CF and OF */
            State->Flags.Cf = ((Value & (1 << (Bits - Count))) != 0);
            if (Count == 1) State->Flags.Of = State->Flags.Cf
                                              ^ ((Result & HighestBit) != 0);

            break;
        }

        /* SHR */
        case 5:
        {
            Result = Value >> Count;

            /* Update CF and OF */
            State->Flags.Cf = ((Value & (1 << (Count - 1))) != 0);
            if (Count == 1) State->Flags.Of = ((Value & HighestBit) != 0);

            break;
        }

        /* SAR */
        case 7:
        {
            Result = Value >> Count;

            /* Fill the top Count bits with the sign bit */
            if (Value & HighestBit) Result |= ((1 << Count) - 1) << (Bits - Count);

            /* Update CF and OF */
            State->Flags.Cf = ((Value & (1 << (Count - 1))) != 0);
            if (Count == 1) State->Flags.Of = FALSE;

            break;
        }
    }

    if (Operation >= 4)
    {
        /* Update ZF, SF and PF */
        State->Flags.Zf = ((Result & MaxValue) == 0);
        State->Flags.Sf = ((Result & HighestBit) != 0);
        State->Flags.Pf = Fast486CalculateParity(Result);
    }

    /* Return the result */
    return Result;
}

/* PUBLIC FUNCTIONS ***********************************************************/

FAST486_OPCODE_HANDLER(Fast486OpcodeGroup8082)
{
    UCHAR Immediate, Value;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Fetch the immediate operand */
    if (!Fast486FetchByte(State, &Immediate))
    {
        /* Exception occurred */
        return;
    }

    /* Read the operands */
    if (!Fast486ReadModrmByteOperands(State, &ModRegRm, NULL, &Value))
    {
        /* Exception occurred */
        return;
    }

    /* Calculate the result */
    Value = Fast486ArithmeticOperation(State, ModRegRm.Register, Value, Immediate, 8);

    /* Unless this is CMP, write back the result */
    if (ModRegRm.Register != 7)
    {
        Fast486WriteModrmByteOperands(State, &ModRegRm, FALSE, Value);
    }
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroup81)
{
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    if (OperandSize)
    {
        ULONG Immediate, Value;

        /* Fetch the immediate operand */
        if (!Fast486FetchDword(State, &Immediate))
        {
            /* Exception occurred */
            return;
        }

        /* Read the operands */
        if (!Fast486ReadModrmDwordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = Fast486ArithmeticOperation(State, ModRegRm.Register, Value, Immediate, 32);

        /* Unless this is CMP, write back the result */
        if (ModRegRm.Register != 7)
        {
            Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Value);
        }
    }
    else
    {
        USHORT Immediate, Value;

        /* Fetch the immediate operand */
        if (!Fast486FetchWord(State, &Immediate))
        {
            /* Exception occurred */
            return;
        }

        /* Read the operands */
        if (!Fast486ReadModrmWordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = Fast486ArithmeticOperation(State, ModRegRm.Register, Value, Immediate, 16);

        /* Unless this is CMP, write back the result */
        if (ModRegRm.Register != 7)
        {
            Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, Value);
        }
    }
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroup83)
{
    CHAR ImmByte;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Fetch the immediate operand */
    if (!Fast486FetchByte(State, (PUCHAR)&ImmByte))
    {
        /* Exception occurred */
        return;
    }

    if (OperandSize)
    {
        ULONG Immediate = (ULONG)((LONG)ImmByte); // Sign extend
        ULONG Value;

        /* Read the operands */
        if (!Fast486ReadModrmDwordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = Fast486ArithmeticOperation(State, ModRegRm.Register, Value, Immediate, 32);

        /* Unless this is CMP, write back the result */
        if (ModRegRm.Register != 7)
        {
            Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Value);
        }
    }
    else
    {
        USHORT Immediate = (USHORT)((SHORT)ImmByte); // Sign extend
        USHORT Value;

        /* Read the operands */
        if (!Fast486ReadModrmWordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = Fast486ArithmeticOperation(State, ModRegRm.Register, Value, Immediate, 16);

        /* Unless this is CMP, write back the result */
        if (ModRegRm.Register != 7)
        {
            Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, Value);
        }
    }
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroup8F)
{
    ULONG Value;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    /* Pop a value from the stack - this must be done first */
    if (!Fast486StackPop(State, &Value))
    {
        /* Exception occurred */
        return;
    }

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    if (ModRegRm.Register != 0)
    {
        /* Invalid */
        Fast486Exception(State, FAST486_EXCEPTION_UD);
        return;
    }

    if (OperandSize) Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Value);
    else Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, LOWORD(Value));
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupC0)
{
    UCHAR Value, Count;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Fetch the count */
    if (!Fast486FetchByte(State, &Count))
    {
        /* Exception occurred */
        return;
    }

    /* Read the operands */
    if (!Fast486ReadModrmByteOperands(State, &ModRegRm, NULL, &Value))
    {
        /* Exception occurred */
        return;
    }

    /* Calculate the result */
    Value = LOBYTE(Fast486RotateOperation(State,
                                          ModRegRm.Register,
                                          Value,
                                          8,
                                          Count));

    /* Write back the result */
    Fast486WriteModrmByteOperands(State, &ModRegRm, FALSE, Value);
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupC1)
{
    UCHAR Count;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Fetch the count */
    if (!Fast486FetchByte(State, &Count))
    {
        /* Exception occurred */
        return;
    }

    if (OperandSize)
    {
        ULONG Value;

        /* Read the operands */
        if (!Fast486ReadModrmDwordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = Fast486RotateOperation(State,
                                       ModRegRm.Register,
                                       Value,
                                       32,
                                       Count);

        /* Write back the result */
        Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Value);
    }
    else
    {
        USHORT Value;

        /* Read the operands */
        if (!Fast486ReadModrmWordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = LOWORD(Fast486RotateOperation(State,
                                              ModRegRm.Register,
                                              Value,
                                              16,
                                              Count));

        /* Write back the result */
        Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, Value);
    }
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupC6)
{
    UCHAR Immediate;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    if (ModRegRm.Register != 0)
    {
        /* Invalid */
        Fast486Exception(State, FAST486_EXCEPTION_UD);
        return;
    }

    /* Get the immediate operand */
    if (!Fast486FetchByte(State, &Immediate))
    {
        /* Exception occurred */
        return;
    }

    Fast486WriteModrmByteOperands(State, &ModRegRm, FALSE, Immediate);
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupC7)
{
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    if (ModRegRm.Register != 0)
    {
        /* Invalid */
        Fast486Exception(State, FAST486_EXCEPTION_UD);
        return;
    }

    if (OperandSize)
    {
        ULONG Immediate;

        /* Get the immediate operand */
        if (!Fast486FetchDword(State, &Immediate))
        {
            /* Exception occurred */
            return;
        }

        Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Immediate);
    }
    else
    {
        USHORT Immediate;

        /* Get the immediate operand */
        if (!Fast486FetchWord(State, &Immediate))
        {
            /* Exception occurred */
            return;
        }

        Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, Immediate);
    }
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupD0)
{
    UCHAR Value;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Read the operands */
    if (!Fast486ReadModrmByteOperands(State, &ModRegRm, NULL, &Value))
    {
        /* Exception occurred */
        return;
    }

    /* Calculate the result */
    Value = LOBYTE(Fast486RotateOperation(State, ModRegRm.Register, Value, 8, 1));

    /* Write back the result */
    Fast486WriteModrmByteOperands(State, &ModRegRm, FALSE, Value);

}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupD1)
{
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    if (OperandSize)
    {
        ULONG Value;

        /* Read the operands */
        if (!Fast486ReadModrmDwordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = Fast486RotateOperation(State, ModRegRm.Register, Value, 32, 1);

        /* Write back the result */
        Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Value);
    }
    else
    {
        USHORT Value;

        /* Read the operands */
        if (!Fast486ReadModrmWordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = LOWORD(Fast486RotateOperation(State, ModRegRm.Register, Value, 16, 1));

        /* Write back the result */
        Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, Value);
    }
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupD2)
{
    UCHAR Value;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Read the operands */
    if (!Fast486ReadModrmByteOperands(State, &ModRegRm, NULL, &Value))
    {
        /* Exception occurred */
        return;
    }

    /* Calculate the result */
    Value = LOBYTE(Fast486RotateOperation(State,
                                          ModRegRm.Register,
                                          Value,
                                          8,
                                          State->GeneralRegs[FAST486_REG_ECX].LowByte));

    /* Write back the result */
    Fast486WriteModrmByteOperands(State, &ModRegRm, FALSE, Value);
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupD3)
{
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    if (OperandSize)
    {
        ULONG Value;

        /* Read the operands */
        if (!Fast486ReadModrmDwordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = Fast486RotateOperation(State,
                                       ModRegRm.Register,
                                       Value,
                                       32,
                                       State->GeneralRegs[FAST486_REG_ECX].LowByte);

        /* Write back the result */
        Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Value);
    }
    else
    {
        USHORT Value;

        /* Read the operands */
        if (!Fast486ReadModrmWordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Calculate the result */
        Value = LOWORD(Fast486RotateOperation(State,
                                              ModRegRm.Register,
                                              Value,
                                              16,
                                              State->GeneralRegs[FAST486_REG_ECX].LowByte));

        /* Write back the result */
        Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, Value);
    }
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupF6)
{
    UCHAR Value = 0;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Read the operands */
    if (!Fast486ReadModrmByteOperands(State, &ModRegRm, NULL, &Value))
    {
        /* Exception occurred */
        return;
    }

    switch (ModRegRm.Register)
    {
        /* TEST */
        case 0:
        case 1:
        {
            UCHAR Immediate, Result;

            /* Fetch the immediate byte */
            if (!Fast486FetchByte(State, &Immediate))
            {
                /* Exception occurred */
                return;
            }

            /* Calculate the result */
            Result = Value & Immediate;

            /* Update the flags */
            State->Flags.Cf = FALSE;
            State->Flags.Of = FALSE;
            State->Flags.Zf = (Result == 0);
            State->Flags.Sf = ((Result & SIGN_FLAG_BYTE) != 0);
            State->Flags.Pf = Fast486CalculateParity(Result);

            break;
        }

        /* NOT */
        case 2:
        {
            /* Write back the result */
            Fast486WriteModrmByteOperands(State, &ModRegRm, FALSE, ~Value);

            break;
        }

        /* NEG */
        case 3:
        {
            /* Calculate the result */
            UCHAR Result = -Value;

            /* Update the flags */
            State->Flags.Cf = (Value != 0);
            State->Flags.Of = (Value & SIGN_FLAG_BYTE) && (Result & SIGN_FLAG_BYTE);
            State->Flags.Af = ((Value & 0x0F) != 0);
            State->Flags.Zf = (Result == 0);
            State->Flags.Sf = ((Result & SIGN_FLAG_BYTE) != 0);
            State->Flags.Pf = Fast486CalculateParity(Result);

            /* Write back the result */
            Fast486WriteModrmByteOperands(State, &ModRegRm, FALSE, Result);

            break;
        }

        /* MUL */
        case 4:
        {
            USHORT Result = (USHORT)Value * (USHORT)State->GeneralRegs[FAST486_REG_EAX].LowByte;

            /* Update the flags */
            State->Flags.Cf = State->Flags.Of = (HIBYTE(Result) != 0);

            /* Write back the result */
            State->GeneralRegs[FAST486_REG_EAX].LowWord = Result;

            break;
        }

        /* IMUL */
        case 5:
        {
            SHORT Result = (SHORT)((CHAR)Value) * (SHORT)((CHAR)State->GeneralRegs[FAST486_REG_EAX].LowByte);

            /* Update the flags */
            State->Flags.Cf = State->Flags.Of = ((Result < FAST486_CHAR_MIN) || (Result > FAST486_CHAR_MAX));

            /* Write back the result */
            State->GeneralRegs[FAST486_REG_EAX].LowWord = (USHORT)Result;

            break;
        }

        /* DIV */
        case 6:
        {
            USHORT Quotient;
            UCHAR Remainder;

            if (Value == 0)
            {
                /* Divide error */
                Fast486Exception(State, FAST486_EXCEPTION_DE);
                return;
            }

            Quotient = State->GeneralRegs[FAST486_REG_EAX].LowWord / Value;
            Remainder = State->GeneralRegs[FAST486_REG_EAX].LowWord % Value;

            if (Quotient > 0xFF)
            {
                /* Divide error */
                Fast486Exception(State, FAST486_EXCEPTION_DE);
                return;
            }

            /* Write back the results */
            State->GeneralRegs[FAST486_REG_EAX].LowByte = (UCHAR)Quotient;
            State->GeneralRegs[FAST486_REG_EAX].HighByte = Remainder;

            break;
        }

        /* IDIV */
        case 7:
        {
            SHORT Quotient;
            CHAR Remainder;

            if (Value == 0)
            {
                /* Divide error */
                Fast486Exception(State, FAST486_EXCEPTION_DE);
                return;
            }

            Quotient = (SHORT)State->GeneralRegs[FAST486_REG_EAX].LowWord / (CHAR)Value;
            Remainder = (SHORT)State->GeneralRegs[FAST486_REG_EAX].LowWord % (CHAR)Value;

            if (Quotient > FAST486_CHAR_MAX || Quotient < FAST486_CHAR_MIN)
            {
                /* Divide error */
                Fast486Exception(State, FAST486_EXCEPTION_DE);
                return;
            }

            /* Write back the results */
            State->GeneralRegs[FAST486_REG_EAX].LowByte = (UCHAR)((CHAR)Quotient);
            State->GeneralRegs[FAST486_REG_EAX].HighByte = (UCHAR)Remainder;

            break;
        }
    }
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupF7)
{
    ULONG Value = 0, SignFlag;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Set the sign flag */
    if (OperandSize) SignFlag = SIGN_FLAG_LONG;
    else SignFlag = SIGN_FLAG_WORD;

    /* Read the operand */
    if (OperandSize)
    {
        /* 32-bit */
        if (!Fast486ReadModrmDwordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }
    }
    else
    {
        /* 16-bit */
        if (!Fast486ReadModrmWordOperands(State, &ModRegRm, NULL, (PUSHORT)&Value))
        {
            /* Exception occurred */
            return;
        }
    }

    switch (ModRegRm.Register)
    {
        /* TEST */
        case 0:
        case 1:
        {
            ULONG Immediate = 0, Result = 0;

            if (OperandSize)
            {
                /* Fetch the immediate dword */
                if (!Fast486FetchDword(State, &Immediate))
                {
                    /* Exception occurred */
                    return;
                }
            }
            else
            {
                /* Fetch the immediate word */
                if (!Fast486FetchWord(State, (PUSHORT)&Immediate))
                {
                    /* Exception occurred */
                    return;
                }
            }

            /* Calculate the result */
            Result = Value & Immediate;

            /* Update the flags */
            State->Flags.Cf = FALSE;
            State->Flags.Of = FALSE;
            State->Flags.Zf = (Result == 0);
            State->Flags.Sf = ((Result & SignFlag) != 0);
            State->Flags.Pf = Fast486CalculateParity(Result);

            break;
        }

        /* NOT */
        case 2:
        {
            /* Write back the result */
            if (OperandSize)
            {
                /* 32-bit */
                Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, ~Value);
            }
            else
            {
                /* 16-bit */
                Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, LOWORD(~Value));
            }

            break;
        }

        /* NEG */
        case 3:
        {
            /* Calculate the result */
            ULONG Result = -(LONG)Value;
            if (!OperandSize) Result &= 0xFFFF;

            /* Update the flags */
            State->Flags.Cf = (Value != 0);
            State->Flags.Of = (Value & SignFlag) && (Result & SignFlag);
            State->Flags.Af = ((Value & 0x0F) != 0);
            State->Flags.Zf = (Result == 0);
            State->Flags.Sf = ((Result & SignFlag) != 0);
            State->Flags.Pf = Fast486CalculateParity(Result);

            /* Write back the result */
            if (OperandSize)
            {
                /* 32-bit */
                Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Result);
            }
            else
            {
                /* 16-bit */
                Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, LOWORD(Result));
            }

            break;
        }

        /* MUL */
        case 4:
        {
            if (OperandSize)
            {
                ULONGLONG Result = (ULONGLONG)Value * (ULONGLONG)State->GeneralRegs[FAST486_REG_EAX].Long;

                /* Update the flags */
                State->Flags.Cf = State->Flags.Of = ((Result & 0xFFFFFFFF00000000ULL) != 0);

                /* Write back the result */
                State->GeneralRegs[FAST486_REG_EAX].Long = Result & 0xFFFFFFFFULL;
                State->GeneralRegs[FAST486_REG_EDX].Long = Result >> 32;
            }
            else
            {
                ULONG Result = (ULONG)Value * (ULONG)State->GeneralRegs[FAST486_REG_EAX].LowWord;

                /* Update the flags */
                State->Flags.Cf = State->Flags.Of = (HIWORD(Result) != 0);

                /* Write back the result */
                State->GeneralRegs[FAST486_REG_EAX].LowWord = LOWORD(Result);
                State->GeneralRegs[FAST486_REG_EDX].LowWord = HIWORD(Result);
            }

            break;
        }

        /* IMUL */
        case 5:
        {
            if (OperandSize)
            {
                LONGLONG Result = (LONGLONG)((LONG)Value) * (LONGLONG)((LONG)State->GeneralRegs[FAST486_REG_EAX].Long);

                /* Update the flags */
                State->Flags.Cf = State->Flags.Of = ((Result < FAST486_LONG_MIN) || (Result > FAST486_LONG_MAX));

                /* Write back the result */
                State->GeneralRegs[FAST486_REG_EAX].Long = Result & 0xFFFFFFFFULL;
                State->GeneralRegs[FAST486_REG_EDX].Long = Result >> 32;
            }
            else
            {
                LONG Result = (LONG)((SHORT)Value) * (LONG)((SHORT)State->GeneralRegs[FAST486_REG_EAX].LowWord);

                /* Update the flags */
                State->Flags.Cf = State->Flags.Of = ((Result < FAST486_SHORT_MIN) || (Result > FAST486_SHORT_MAX));

                /* Write back the result */
                State->GeneralRegs[FAST486_REG_EAX].LowWord = LOWORD(Result);
                State->GeneralRegs[FAST486_REG_EDX].LowWord = HIWORD(Result);
            }

            break;
        }

        /* DIV */
        case 6:
        {
            if (Value == 0)
            {
                /* Divide error */
                Fast486Exception(State, FAST486_EXCEPTION_DE);
                return;
            }

            if (OperandSize)
            {
                ULONGLONG Dividend = (ULONGLONG)State->GeneralRegs[FAST486_REG_EAX].Long
                                     | ((ULONGLONG)State->GeneralRegs[FAST486_REG_EDX].Long << 32);
                ULONGLONG Quotient = Dividend / Value;
                ULONG Remainder = Dividend % Value;

                if (Quotient > 0xFFFFFFFFULL)
                {
                    /* Divide error */
                    Fast486Exception(State, FAST486_EXCEPTION_DE);
                    return;
                }

                /* Write back the results */
                State->GeneralRegs[FAST486_REG_EAX].Long = (ULONG)Quotient;
                State->GeneralRegs[FAST486_REG_EDX].Long = Remainder;
            }
            else
            {
                ULONG Dividend = (ULONG)State->GeneralRegs[FAST486_REG_EAX].LowWord
                                 | ((ULONG)State->GeneralRegs[FAST486_REG_EDX].LowWord << 16);
                ULONG Quotient = Dividend / Value;
                USHORT Remainder = Dividend % Value;

                if (Quotient > 0xFFFF)
                {
                    /* Divide error */
                    Fast486Exception(State, FAST486_EXCEPTION_DE);
                    return;
                }

                /* Write back the results */
                State->GeneralRegs[FAST486_REG_EAX].LowWord = (USHORT)Quotient;
                State->GeneralRegs[FAST486_REG_EDX].LowWord = Remainder;
            }

            break;
        }

        /* IDIV */
        case 7:
        {
            if (Value == 0)
            {
                /* Divide error */
                Fast486Exception(State, FAST486_EXCEPTION_DE);
                return;
            }

            if (OperandSize)
            {
                LONGLONG Dividend = (LONGLONG)State->GeneralRegs[FAST486_REG_EAX].Long
                                     | ((LONGLONG)State->GeneralRegs[FAST486_REG_EDX].Long << 32);
                LONGLONG Quotient = Dividend / (LONG)Value;
                LONG Remainder = Dividend % (LONG)Value;

                if (Quotient > FAST486_LONG_MAX || Quotient < FAST486_LONG_MIN)
                {
                    /* Divide error */
                    Fast486Exception(State, FAST486_EXCEPTION_DE);
                    return;
                }

                /* Write back the results */
                State->GeneralRegs[FAST486_REG_EAX].Long = (ULONG)((LONG)Quotient);
                State->GeneralRegs[FAST486_REG_EDX].Long = (ULONG)Remainder;
            }
            else
            {
                LONG Dividend = (LONG)State->GeneralRegs[FAST486_REG_EAX].LowWord
                                 | ((LONG)State->GeneralRegs[FAST486_REG_EDX].LowWord << 16);
                LONG Quotient = Dividend / (SHORT)LOWORD(Value);
                SHORT Remainder = Dividend % (SHORT)LOWORD(Value);

                if (Quotient > FAST486_SHORT_MAX || Quotient < FAST486_SHORT_MIN)
                {
                    /* Divide error */
                    Fast486Exception(State, FAST486_EXCEPTION_DE);
                    return;
                }

                /* Write back the results */
                State->GeneralRegs[FAST486_REG_EAX].LowWord = (USHORT)((SHORT)Quotient);
                State->GeneralRegs[FAST486_REG_EDX].LowWord = (USHORT)Remainder;
            }

            break;
        }
    }
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupFE)
{
    UCHAR Value;
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    if (ModRegRm.Register > 1)
    {
        /* Invalid */
        Fast486Exception(State, FAST486_EXCEPTION_UD);
        return;
    }

    /* Read the operands */
    if (!Fast486ReadModrmByteOperands(State, &ModRegRm, NULL, &Value))
    {
        /* Exception occurred */
        return;
    }

    if (ModRegRm.Register == 0)
    {
        /* Increment and update OF and AF */
        Value++;
        State->Flags.Of = (Value == SIGN_FLAG_BYTE);
        State->Flags.Af = ((Value & 0x0F) == 0);
    }
    else
    {
        /* Decrement and update OF and AF */
        State->Flags.Of = (Value == SIGN_FLAG_BYTE);
        Value--;
        State->Flags.Af = ((Value & 0x0F) == 0x0F);
    }

    /* Update flags */
    State->Flags.Zf = (Value == 0);
    State->Flags.Sf = ((Value & SIGN_FLAG_BYTE) != 0);
    State->Flags.Pf = Fast486CalculateParity(Value);

    /* Write back the result */
    Fast486WriteModrmByteOperands(State, &ModRegRm, FALSE, Value);
}

FAST486_OPCODE_HANDLER(Fast486OpcodeGroupFF)
{
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    if (ModRegRm.Register == 7)
    {
        /* Invalid */
        Fast486Exception(State, FAST486_EXCEPTION_UD);
        return;
    }

    /* Read the operands */
    if (OperandSize)
    {
        ULONG Value;

        if (!Fast486ReadModrmDwordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        if (ModRegRm.Register == 0)
        {
            /* Increment and update OF and AF */
            Value++;
            State->Flags.Of = (Value == SIGN_FLAG_LONG);
            State->Flags.Af = ((Value & 0x0F) == 0);
        }
        else if (ModRegRm.Register == 1)
        {
            /* Decrement and update OF and AF */
            State->Flags.Of = (Value == SIGN_FLAG_LONG);
            Value--;
            State->Flags.Af = ((Value & 0x0F) == 0x0F);
        }
        else if (ModRegRm.Register == 2)
        {
            /* Push the current value of EIP */
            if (!Fast486StackPush(State, State->InstPtr.Long))
            {
                /* Exception occurred */
                return;
            }

            /* Set the EIP to the address */
            State->InstPtr.Long = Value;
        }
        else if (ModRegRm.Register == 3)
        {
            USHORT Selector;
            FAST486_SEG_REGS Segment = FAST486_REG_DS;

            /* Check for the segment override */
            if (State->PrefixFlags & FAST486_PREFIX_SEG)
            {
                /* Use the override segment instead */
                Segment = State->SegmentOverride;
            }

            /* Read the selector */
            if (!Fast486ReadMemory(State,
                                   Segment,
                                   ModRegRm.MemoryAddress + sizeof(ULONG),
                                   FALSE,
                                   &Selector,
                                   sizeof(USHORT)))
            {
                /* Exception occurred */
                return;
            }

            if ((State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
                && !State->Flags.Vm)
            {
                if (!Fast486ProcessGate(State, Selector, Value, TRUE))
                {
                    /* Gate processed or exception occurred */
                    return;
                }
            }

            /* Push the current value of CS */
            if (!Fast486StackPush(State, State->SegmentRegs[FAST486_REG_CS].Selector))
            {
                /* Exception occurred */
                return;
            }

            /* Push the current value of EIP */
            if (!Fast486StackPush(State, State->InstPtr.Long))
            {
                /* Exception occurred */
                return;
            }

            /* Load the new code segment */
            if (!Fast486LoadSegment(State, FAST486_REG_CS, Selector))
            {
                /* Exception occurred */
                return;
            }

            /* Set the EIP to the address */
            State->InstPtr.Long = Value;
        }
        else if (ModRegRm.Register == 4)
        {
            /* Set the EIP to the address */
            State->InstPtr.Long = Value;
        }
        else if (ModRegRm.Register == 5)
        {
            USHORT Selector;
            FAST486_SEG_REGS Segment = FAST486_REG_DS;

            /* Check for the segment override */
            if (State->PrefixFlags & FAST486_PREFIX_SEG)
            {
                /* Use the override segment instead */
                Segment = State->SegmentOverride;
            }

            /* Read the selector */
            if (!Fast486ReadMemory(State,
                                   Segment,
                                   ModRegRm.MemoryAddress + sizeof(ULONG),
                                   FALSE,
                                   &Selector,
                                   sizeof(USHORT)))
            {
                /* Exception occurred */
                return;
            }

            if ((State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
                && !State->Flags.Vm)
            {
                if (!Fast486ProcessGate(State, Selector, Value, FALSE))
                {
                    /* Gate processed or exception occurred */
                    return;
                }
            }

            /* Load the new code segment */
            if (!Fast486LoadSegment(State, FAST486_REG_CS, Selector))
            {
                /* Exception occurred */
                return;
            }

            /* Set the EIP to the address */
            State->InstPtr.Long = Value;
        }
        else if (ModRegRm.Register == 6)
        {
            /* Push the value on to the stack */
            Fast486StackPush(State, Value);
            return;
        }

        if (ModRegRm.Register <= 1)
        {
            /* Update flags */
            State->Flags.Sf = ((Value & SIGN_FLAG_LONG) != 0);
            State->Flags.Zf = (Value == 0);
            State->Flags.Pf = Fast486CalculateParity(Value);

            /* Write back the result */
            Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Value);
        }
    }
    else
    {
        USHORT Value;

        if (!Fast486ReadModrmWordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        if (ModRegRm.Register == 0)
        {
            /* Increment and update OF */
            Value++;
            State->Flags.Of = (Value == SIGN_FLAG_WORD);
            State->Flags.Af = ((Value & 0x0F) == 0);
        }
        else if (ModRegRm.Register == 1)
        {
            /* Decrement and update OF */
            State->Flags.Of = (Value == SIGN_FLAG_WORD);
            Value--;
            State->Flags.Af = ((Value & 0x0F) == 0x0F);
        }
        else if (ModRegRm.Register == 2)
        {
            /* Push the current value of IP */
            if (!Fast486StackPush(State, State->InstPtr.LowWord))
            {
                /* Exception occurred */
                return;
            }

            /* Set the IP to the address */
            State->InstPtr.LowWord = Value;

            /* Clear the top half of EIP */
            State->InstPtr.Long &= 0xFFFF;
        }
        else if (ModRegRm.Register == 3)
        {
            USHORT Selector;
            FAST486_SEG_REGS Segment = FAST486_REG_DS;

            /* Check for the segment override */
            if (State->PrefixFlags & FAST486_PREFIX_SEG)
            {
                /* Use the override segment instead */
                Segment = State->SegmentOverride;
            }

            /* Read the selector */
            if (!Fast486ReadMemory(State,
                                   Segment,
                                   ModRegRm.MemoryAddress + sizeof(USHORT),
                                   FALSE,
                                   &Selector,
                                   sizeof(USHORT)))
            {
                /* Exception occurred */
                return;
            }

            if ((State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
                && !State->Flags.Vm)
            {
                if (!Fast486ProcessGate(State, Selector, Value, TRUE))
                {
                    /* Gate processed or exception occurred */
                    return;
                }
            }

            /* Push the current value of CS */
            if (!Fast486StackPush(State, State->SegmentRegs[FAST486_REG_CS].Selector))
            {
                /* Exception occurred */
                return;
            }

            /* Push the current value of IP */
            if (!Fast486StackPush(State, State->InstPtr.LowWord))
            {
                /* Exception occurred */
                return;
            }

            /* Load the new code segment */
            if (!Fast486LoadSegment(State, FAST486_REG_CS, Selector))
            {
                /* Exception occurred */
                return;
            }

            /* Set the IP to the address */
            State->InstPtr.LowWord = Value;

            /* Clear the top half of EIP */
            State->InstPtr.Long &= 0xFFFF;
        }
        else if (ModRegRm.Register == 4)
        {
            /* Set the IP to the address */
            State->InstPtr.LowWord = Value;

            /* Clear the top half of EIP */
            State->InstPtr.Long &= 0xFFFF;
        }
        else if (ModRegRm.Register == 5)
        {
            USHORT Selector;
            FAST486_SEG_REGS Segment = FAST486_REG_DS;

            /* Check for the segment override */
            if (State->PrefixFlags & FAST486_PREFIX_SEG)
            {
                /* Use the override segment instead */
                Segment = State->SegmentOverride;
            }

            /* Read the selector */
            if (!Fast486ReadMemory(State,
                                   Segment,
                                   ModRegRm.MemoryAddress + sizeof(USHORT),
                                   FALSE,
                                   &Selector,
                                   sizeof(USHORT)))
            {
                /* Exception occurred */
                return;
            }

            if ((State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
                && !State->Flags.Vm)
            {
                if (!Fast486ProcessGate(State, Selector, Value, FALSE))
                {
                    /* Gate processed or exception occurred */
                    return;
                }
            }

            /* Load the new code segment */
            if (!Fast486LoadSegment(State, FAST486_REG_CS, Selector))
            {
                /* Exception occurred */
                return;
            }

            /* Set the IP to the address */
            State->InstPtr.LowWord = Value;

            /* Clear the top half of EIP */
            State->InstPtr.Long &= 0xFFFF;
        }
        else if (ModRegRm.Register == 6)
        {
            /* Push the value on to the stack */
            Fast486StackPush(State, Value);
            return;
        }
        else
        {
            /* Invalid */
            Fast486Exception(State, FAST486_EXCEPTION_UD);
            return;
        }

        if (ModRegRm.Register <= 1)
        {
            /* Update flags */
            State->Flags.Sf = ((Value & SIGN_FLAG_WORD) != 0);
            State->Flags.Zf = (Value == 0);
            State->Flags.Pf = Fast486CalculateParity(Value);

            /* Write back the result */
            Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, Value);
        }
    }
}

FAST486_OPCODE_HANDLER(Fast486ExtOpcodeGroup0F00)
{
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    NO_LOCK_PREFIX();
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Check which operation this is */
    switch (ModRegRm.Register)
    {
        /* SLDT */
        case 0:
        {
            /* Not recognized in real mode or virtual 8086 mode */
            if (!(State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
                || State->Flags.Vm)
            {
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, State->Ldtr.Selector);
            break;
        }

        /* STR */
        case 1:
        {
            /* Not recognized in real mode or virtual 8086 mode */
            if (!(State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
                || State->Flags.Vm)
            {
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, State->TaskReg.Selector);
            break;
        }

        /* LLDT */
        case 2:
        {
            BOOLEAN Valid;
            USHORT Selector;
            FAST486_SYSTEM_DESCRIPTOR GdtEntry;

            /* Not recognized in real mode or virtual 8086 mode */
            if (!(State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
                || State->Flags.Vm)
            {
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            /* This is a privileged instruction */
            if (Fast486GetCurrentPrivLevel(State) != 0)
            {
                Fast486Exception(State, FAST486_EXCEPTION_GP);
                return;
            }

            if (!Fast486ReadModrmWordOperands(State,
                                              &ModRegRm,
                                              NULL,
                                              &Selector))
            {
                /* Exception occurred */
                return;
            }

            if (Selector & SEGMENT_TABLE_INDICATOR)
            {
                /* This selector doesn't point to the GDT */
                Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_GP, Selector);
                return;
            }

            if (!Fast486ReadDescriptorEntry(State,
                                            Selector,
                                            &Valid,
                                            (PFAST486_GDT_ENTRY)&GdtEntry))
            {
                /* Exception occurred */
                return;
            }

            if (!Valid)
            {
                /* Invalid selector */
                Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_GP, Selector);
                return;
            }

            if (GET_SEGMENT_INDEX(Selector) == 0)
            {
                RtlZeroMemory(&State->Ldtr, sizeof(State->Ldtr));
                return;
            }

            if (!GdtEntry.Present)
            {
                Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_NP, Selector);
                return;
            }

            if (GdtEntry.Signature != FAST486_LDT_SIGNATURE)
            {
                /* This is not a LDT descriptor */
                Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_GP, Selector);
                return;
            }

            /* Update the LDTR */
            State->Ldtr.Selector = Selector;
            State->Ldtr.Base = GdtEntry.Base | (GdtEntry.BaseMid << 16) | (GdtEntry.BaseHigh << 24);
            State->Ldtr.Limit = GdtEntry.Limit | (GdtEntry.LimitHigh << 16);

            if (GdtEntry.Granularity)
            {
                State->Ldtr.Limit <<= 12;
                State->Ldtr.Limit |= 0x00000FFF;
            }

            break;
        }

        /* LTR */
        case 3:
        {
            BOOLEAN Valid;
            USHORT Selector;
            FAST486_SYSTEM_DESCRIPTOR GdtEntry;

            /* Not recognized in real mode or virtual 8086 mode */
            if (!(State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
                || State->Flags.Vm)
            {
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            /* This is a privileged instruction */
            if (Fast486GetCurrentPrivLevel(State) != 0)
            {
                Fast486Exception(State, FAST486_EXCEPTION_GP);
                return;
            }

            if (!Fast486ReadModrmWordOperands(State,
                                              &ModRegRm,
                                              NULL,
                                              &Selector))
            {
                /* Exception occurred */
                return;
            }

            if (Selector & SEGMENT_TABLE_INDICATOR)
            {
                /* This selector doesn't point to the GDT */
                Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_GP, Selector);
                return;
            }

            if (!Fast486ReadDescriptorEntry(State,
                                            Selector,
                                            &Valid,
                                            (PFAST486_GDT_ENTRY)&GdtEntry))
            {
                /* Exception occurred */
                return;
            }

            if (!Valid)
            {
                /* Invalid selector */
                Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_GP, Selector);
                return;
            }

            if (GET_SEGMENT_INDEX(Selector) == 0)
            {
                Fast486Exception(State, FAST486_EXCEPTION_GP);
                return;
            }

            if (!GdtEntry.Present)
            {
                Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_NP, Selector);
                return;
            }

            if (GdtEntry.Signature != FAST486_TSS_SIGNATURE
                && GdtEntry.Signature != FAST486_BUSY_TSS_SIGNATURE
                && GdtEntry.Signature != FAST486_TSS_16_SIGNATURE
                && GdtEntry.Signature != FAST486_BUSY_TSS_16_SIGNATURE)
            {
                /* This is not a TSS descriptor */
                Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_GP, Selector);
                return;
            }

            /* Update the TR */
            State->TaskReg.Selector = Selector;
            State->TaskReg.Base = GdtEntry.Base | (GdtEntry.BaseMid << 16) | (GdtEntry.BaseHigh << 24);
            State->TaskReg.Limit = GdtEntry.Limit | (GdtEntry.LimitHigh << 16);
            State->TaskReg.Modern = GdtEntry.Signature == FAST486_TSS_SIGNATURE
                                    || GdtEntry.Signature == FAST486_BUSY_TSS_SIGNATURE;

            if (GdtEntry.Granularity)
            {
                State->TaskReg.Limit <<= 12;
                State->TaskReg.Limit |= 0x00000FFF;
            }

            if (GdtEntry.Signature != FAST486_BUSY_TSS_SIGNATURE
                && GdtEntry.Signature != FAST486_BUSY_TSS_16_SIGNATURE)
            {
                /* Set the busy bit of this TSS descriptor and write it back */
                GdtEntry.Signature |= 2;

                Fast486WriteLinearMemory(State,
                                         State->Gdtr.Address + GET_SEGMENT_INDEX(Selector),
                                         &GdtEntry,
                                         sizeof(GdtEntry),
                                         FALSE /* We already made sure CPL is 0 */);
            }

            break;
        }

        /* VERR/VERW */
        case 4:
        case 5:
        {
            USHORT Selector;
            BOOLEAN Valid;
            FAST486_GDT_ENTRY GdtEntry;

            /* Not recognized in real mode or virtual 8086 mode */
            if (!(State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
                || State->Flags.Vm)
            {
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            if (!Fast486ReadModrmWordOperands(State,
                                              &ModRegRm,
                                              NULL,
                                              &Selector))
            {
                /* Exception occurred */
                return;
            }

            if (!Fast486ReadDescriptorEntry(State, Selector, &Valid, &GdtEntry))
            {
                /* Exception occurred */
                return;
            }

            if (!Valid)
            {
                /* Clear ZF */
                State->Flags.Zf = FALSE;
                return;
            }

            /* Set ZF if it is valid and accessible */
            State->Flags.Zf = GdtEntry.Present // must be present
                              && GdtEntry.SystemType // must be a segment
                              && (((ModRegRm.Register == 4)
                              /* code segments are only readable if the RW bit is set */
                              && (!GdtEntry.Executable || GdtEntry.ReadWrite))
                              || ((ModRegRm.Register == 5)
                              /* code segments are never writable, data segments are writable when RW is set */
                              && (!GdtEntry.Executable && GdtEntry.ReadWrite)))
                              /*
                               * for segments other than conforming code segments,
                               * both RPL and CPL must be less than or equal to DPL
                               */
                              && (((!GdtEntry.Executable || !GdtEntry.DirConf)
                              && (GET_SEGMENT_RPL(Selector) <= GdtEntry.Dpl)
                              && (Fast486GetCurrentPrivLevel(State) <= GdtEntry.Dpl))
                              /* for conforming code segments, DPL must be less than or equal to CPL */
                              || ((GdtEntry.Executable && GdtEntry.DirConf)
                              && (GdtEntry.Dpl <= Fast486GetCurrentPrivLevel(State))));


            break;
        }

        /* Invalid */
        default:
        {
            Fast486Exception(State, FAST486_EXCEPTION_UD);
        }
    }
}

FAST486_OPCODE_HANDLER(Fast486ExtOpcodeGroup0F01)
{
    // FAST486_TABLE_REG TableReg;
    UCHAR TableReg[6];
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;
    FAST486_SEG_REGS Segment = FAST486_REG_DS;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    NO_LOCK_PREFIX();
    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* Check for the segment override */
    if (State->PrefixFlags & FAST486_PREFIX_SEG)
    {
        /* Use the override segment instead */
        Segment = State->SegmentOverride;
    }

    /* Check which operation this is */
    switch (ModRegRm.Register)
    {
        /* SGDT */
        case 0:
        {
            if (!ModRegRm.Memory)
            {
                /* The second operand must be a memory location */
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            /* Fill the 6-byte table register */
            // TableReg = State->Gdtr;
            *((PUSHORT)&TableReg) = State->Gdtr.Size;
            *((PULONG)&TableReg[sizeof(USHORT)]) = State->Gdtr.Address;

            /* Store the GDTR */
            Fast486WriteMemory(State,
                               Segment,
                               ModRegRm.MemoryAddress,
                               TableReg,
                               sizeof(TableReg));

            break;
        }

        /* SIDT */
        case 1:
        {
            if (!ModRegRm.Memory)
            {
                /* The second operand must be a memory location */
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            /* Fill the 6-byte table register */
            // TableReg = State->Idtr;
            *((PUSHORT)&TableReg) = State->Idtr.Size;
            *((PULONG)&TableReg[sizeof(USHORT)]) = State->Idtr.Address;

            /* Store the IDTR */
            Fast486WriteMemory(State,
                               Segment,
                               ModRegRm.MemoryAddress,
                               TableReg,
                               sizeof(TableReg));

            break;
        }

        /* LGDT */
        case 2:
        {
            /* This is a privileged instruction */
            if (Fast486GetCurrentPrivLevel(State) != 0)
            {
                Fast486Exception(State, FAST486_EXCEPTION_GP);
                return;
            }

            if (!ModRegRm.Memory)
            {
                /* The second operand must be a memory location */
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            /* Read the new GDTR */
            if (!Fast486ReadMemory(State,
                                   Segment,
                                   ModRegRm.MemoryAddress,
                                   FALSE,
                                   TableReg,
                                   sizeof(TableReg)))
            {
                /* Exception occurred */
                return;
            }

            /* Load the new GDT */
            // State->Gdtr = TableReg;
            State->Gdtr.Size = *((PUSHORT)&TableReg);
            State->Gdtr.Address = *((PULONG)&TableReg[sizeof(USHORT)]);

            /* In 16-bit mode the highest byte is masked out */
            if (!OperandSize) State->Gdtr.Address &= 0x00FFFFFF;

            break;
        }

        /* LIDT */
        case 3:
        {
            /* This is a privileged instruction */
            if (Fast486GetCurrentPrivLevel(State) != 0)
            {
                Fast486Exception(State, FAST486_EXCEPTION_GP);
                return;
            }

            if (!ModRegRm.Memory)
            {
                /* The second operand must be a memory location */
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            /* Read the new IDTR */
            if (!Fast486ReadMemory(State,
                                   Segment,
                                   ModRegRm.MemoryAddress,
                                   FALSE,
                                   TableReg,
                                   sizeof(TableReg)))
            {
                /* Exception occurred */
                return;
            }

            /* Load the new IDT */
            // State->Idtr = TableReg;
            State->Idtr.Size = *((PUSHORT)&TableReg);
            State->Idtr.Address = *((PULONG)&TableReg[sizeof(USHORT)]);

            /* In 16-bit mode the highest byte is masked out */
            if (!OperandSize) State->Idtr.Address &= 0x00FFFFFF;

            break;
        }

        /* SMSW */
        case 4:
        {
            /* Store the lower 16 bits (Machine Status Word) of CR0 */
            Fast486WriteModrmWordOperands(State,
                                          &ModRegRm,
                                          FALSE,
                                          LOWORD(State->ControlRegisters[FAST486_REG_CR0]));

            break;
        }

        /* LMSW */
        case 6:
        {
            USHORT MachineStatusWord;

            if (State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE)
            {
                /* This is a privileged instruction */
                if (Fast486GetCurrentPrivLevel(State) != 0)
                {
                    Fast486Exception(State, FAST486_EXCEPTION_GP);
                    return;
                }
            }

            /* Read the new Machine Status Word */
            if (!Fast486ReadModrmWordOperands(State, &ModRegRm, NULL, &MachineStatusWord))
            {
                /* Exception occurred */
                return;
            }

            /* Set the lowest 4 bits, but never clear bit 0 */
            State->ControlRegisters[FAST486_REG_CR0] &= 0xFFFFFFF1;
            State->ControlRegisters[FAST486_REG_CR0] |= MachineStatusWord & 0x0F;

            break;
        }

        /* INVLPG */
        case 7:
        {
#ifndef FAST486_NO_PREFETCH
            /* Invalidate the prefetch */
            State->PrefetchValid = FALSE;
#endif

            /* This is a privileged instruction */
            if (Fast486GetCurrentPrivLevel(State) != 0)
            {
                Fast486Exception(State, FAST486_EXCEPTION_GP);
                return;
            }

            if (!ModRegRm.Memory)
            {
                /* The second operand must be a memory location */
                Fast486Exception(State, FAST486_EXCEPTION_UD);
                return;
            }

            if (State->Tlb != NULL)
            {
                /* Clear the TLB entry */
                State->Tlb[ModRegRm.MemoryAddress >> 12] = INVALID_TLB_FIELD;
            }

            break;
        }

        /* Invalid */
        default:
        {
            Fast486Exception(State, FAST486_EXCEPTION_UD);
        }
    }
}

FAST486_OPCODE_HANDLER(Fast486ExtOpcodeGroup0FB9)
{
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_ADSIZE(AddressSize);

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    /* All of them are reserved (UD2) */
    Fast486Exception(State, FAST486_EXCEPTION_UD);
    return;
}

FAST486_OPCODE_HANDLER(Fast486ExtOpcodeGroup0FBA)
{
    FAST486_MOD_REG_RM ModRegRm;
    BOOLEAN OperandSize, AddressSize;
    UINT DataSize;
    UCHAR BitNumber;

    OperandSize = AddressSize = State->SegmentRegs[FAST486_REG_CS].Size;

    TOGGLE_OPSIZE(OperandSize);
    TOGGLE_ADSIZE(AddressSize);

    /* Get the number of bits */
    if (OperandSize) DataSize = 32;
    else DataSize = 16;

    if (!Fast486ParseModRegRm(State, AddressSize, &ModRegRm))
    {
        /* Exception occurred */
        return;
    }

    if (ModRegRm.Register < 4)
    {
        /* Invalid */
        Fast486Exception(State, FAST486_EXCEPTION_UD);
        return;
    }

    /* Get the bit number */
    if (!Fast486FetchByte(State, &BitNumber))
    {
        /* Exception occurred */
        return;
    }

    if (ModRegRm.Memory)
    {
        /*
         * For memory operands, add the bit offset divided by
         * the data size to the address
         */
        ModRegRm.MemoryAddress += BitNumber / DataSize;
    }

    /* Normalize the bit number */
    BitNumber %= DataSize;

    if (OperandSize)
    {
        ULONG Value;

        /* Read the value */
        if (!Fast486ReadModrmDwordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Set CF to the bit value */
        State->Flags.Cf = (Value >> BitNumber) & 1;

        if (ModRegRm.Register == 5)
        {
            /* BTS */
            Value |= 1 << BitNumber;
        }
        else if (ModRegRm.Register == 6)
        {
            /* BTR */
            Value &= ~(1 << BitNumber);
        }
        else if (ModRegRm.Register == 7)
        {
            /* BTC */
            Value ^= 1 << BitNumber;
        }

        if (ModRegRm.Register >= 5)
        {
            /* Write back the result */
            if (!Fast486WriteModrmDwordOperands(State, &ModRegRm, FALSE, Value))
            {
                /* Exception occurred */
                return;
            }
        }
    }
    else
    {
        USHORT Value;

        /* Read the value */
        if (!Fast486ReadModrmWordOperands(State, &ModRegRm, NULL, &Value))
        {
            /* Exception occurred */
            return;
        }

        /* Set CF to the bit value */
        State->Flags.Cf = (Value >> BitNumber) & 1;

        if (ModRegRm.Register == 5)
        {
            /* BTS */
            Value |= 1 << BitNumber;
        }
        else if (ModRegRm.Register == 6)
        {
            /* BTR */
            Value &= ~(1 << BitNumber);
        }
        else if (ModRegRm.Register == 7)
        {
            /* BTC */
            Value ^= 1 << BitNumber;
        }

        if (ModRegRm.Register >= 5)
        {
            /* Write back the result */
            if (!Fast486WriteModrmWordOperands(State, &ModRegRm, FALSE, Value))
            {
                /* Exception occurred */
                return;
            }
        }
    }
}

/* EOF */