Unit AtariControls;

Interface

Uses INIFiles,HexWrite,Classes;

Type
  GRIP_SLOT     = Byte;
  GRIP_CLASS    = Byte;
  GRIP_INDEX    = Byte;
  GRIP_VALUE    = Word;
  GRIP_BITFIELD = Cardinal;

Const
  { Standard Classes }

  GRIP_CLASS_BUTTON   = 1;
  GRIP_CLASS_AXIS     = 2;
  GRIP_CLASS_POV_HAT  = 3;
  GRIP_CLASS_VELOCITY = 4;
  GRIP_CLASS_THROTTLE = 5;
  GRIP_CLASS_ANALOG   = 6;

  JoyPort          = $201;
  MaxChannelLength = 256;

  { Define interface functions }

  ifPaddle1Left    = $00000001;  { LongInt 1 }
  ifPaddle1Right   = $00000002;
  ifPaddle2Left    = $00000004;
  ifPaddle2Right   = $00000008;
  ifPaddle3Left    = $00000010;
  ifPaddle3Right   = $00000020;
  ifPaddle4Left    = $00000040;
  ifPaddle4Right   = $00000080;
  ifPaddle1Btn     = $00000100;
  ifPaddle2Btn     = $00000200;
  ifPaddle3Btn     = $00000400;
  ifPaddle4Btn     = $00000800;

  ifJoy1Left       = $00001000;
  ifJoy1Right      = $00002000;
  ifJoy1Up         = $00004000;
  ifJoy1Down       = $00008000;
  ifJoy1Btn        = $00010000;

  ifJoy2Left       = $00020000;
  ifJoy2Right      = $00040000;
  ifJoy2Up         = $00080000;
  ifJoy2Down       = $00100000;
  ifJoy2Btn        = $00200000;

  ifCBSBtn1        = $00400000;
  ifCBSBtn2        = $00800000;

  ifDriving1Left   = $01000000;
  ifDriving1Right  = $02000000;
  ifDriving2Left   = $04000000;
  ifDriving2Right  = $08000000;

  ifDriving1Btn    = $10000000;
  ifDriving2Btn    = $20000000;

  ifKB1_1          = $00000001;  { LongInt 2 }
  ifKB1_2          = $00000002;
  ifKB1_3          = $00000004;
  ifKB1_4          = $00000008;
  ifKB1_5          = $00000010;
  ifKB1_6          = $00000020;
  ifKB1_7          = $00000040;
  ifKB1_8          = $00000080;
  ifKB1_9          = $00000100;
  ifKB1_Star       = $00000200;
  ifKB1_0          = $00000400;
  ifKB1_Pound      = $00000800;

  ifKB2_1          = $00001000;
  ifKB2_2          = $00002000;
  ifKB2_3          = $00004000;
  ifKB2_4          = $00008000;
  ifKB2_5          = $00010000;
  ifKB2_6          = $00020000;
  ifKB2_7          = $00040000;
  ifKB2_8          = $00080000;
  ifKB2_9          = $00100000;
  ifKB2_Star       = $00200000;
  ifKB2_0          = $00400000;
  ifKB2_Pound      = $00800000;

  ifReset          = $01000000;
  ifSelect         = $02000000;
  ifDiff1          = $04000000;
  ifDiff2          = $08000000;
  ifColorBW        = $10000000;
  ifPower          = $20000000;

  { Define interface masks }

  imPaddle1Axis    = ifPaddle1Left + ifPaddle1Right;
  imPaddle2Axis    = ifPaddle2Left + ifPaddle2Right;
  imPaddle3Axis    = ifPaddle3Left + ifPaddle3Right;
  imPaddle4Axis    = ifPaddle4Left + ifPaddle4Right;
  imPaddle1        = imPaddle1Axis + ifPaddle1Btn;
  imPaddle2        = imPaddle2Axis + ifPaddle2Btn;
  imPaddle3        = imPaddle3Axis + ifPaddle3Btn;
  imPaddle4        = imPaddle4Axis + ifPaddle4Btn;
  imPaddleAxis     = imPaddle1Axis + imPaddle2Axis + imPaddle3Axis + imPaddle4Axis;
  imPaddleBtn      = ifPaddle1Btn + ifPaddle2Btn + ifPaddle3Btn + ifPaddle4Btn;
  imPaddles        = imPaddleAxis + imPaddleBtn;
  imJoy1X          = ifJoy1Left + ifJoy1Right;
  imJoy1Y          = ifJoy1Up + ifJoy1Down;
  imJoy2X          = ifJoy2Left + ifJoy2Right;
  imJoy2Y          = ifJoy2Up + ifJoy2Down;
  imJoy1Axis       = imJoy1X + imJoy1Y;
  imJoy2Axis       = imJoy2X + imJoy2Y;
  imJoyAxis        = imJoy1Axis + imJoy2Axis;
  imJoy1           = imJoy1X + imJoy1Y + ifJoy1Btn;
  imJoy2           = imJoy2X + imJoy2Y + ifJoy2Btn;
  imJoyBtn         = ifJoy1Btn + ifJoy2Btn;
  imJoysticks      = imJoy1 + imJoy2;
  imCBS            = ifCBSBtn1 + ifCBSBtn2;
  imDriving1Axis   = ifDriving1Left + ifDriving1Right;
  imDriving2Axis   = ifDriving2Left + ifDriving2Right;
  imDriving1       = imDriving1Axis + ifDriving1Btn;
  imDriving2       = imDriving2Axis + ifDriving2Btn;
  imDrivingAxis    = imDriving1Axis + imDriving2Axis;
  imDrivingBtn     = ifDriving1Btn + ifDriving2Btn;
  imDriving        = imDriving1 + imDriving2;
  imKB1            = ifKB1_1 + ifKB1_2 + ifKB1_3 + ifKB1_4 + ifKB1_5    + ifKB1_6 +
                     ifKB1_7 + ifKB1_8 + ifKB1_9 + ifKB1_0 + ifKB1_Star + ifKB1_Pound;
  imKB2            = ifKB2_1 + ifKB2_2 + ifKB2_3 + ifKB2_4 + ifKB2_5    + ifKB2_6 +
                     ifKB2_7 + ifKB2_8 + ifKB2_9 + ifKB2_0 + ifKB2_Star + ifKB2_Pound;
  imKeyboard       = imKB1 + imKB2;
  imConsole        = ifReset + ifSelect + ifDiff1 + ifDiff2 + ifColorBW + ifPower;
  imAllACaps       = imPaddles + imJoysticks + imCBS + imDriving;
  imAllBCaps       = imKeyboard + imConsole;

  imNoPaddle1Axis  = Not imPaddle1Axis;
  imNoPaddle2Axis  = Not imPaddle2Axis;
  imNoPaddle3Axis  = Not imPaddle3Axis;
  imNoPaddle4Axis  = Not imPaddle4Axis;
  imNoPaddle1      = Not imPaddle1;
  imNoPaddle2      = Not imPaddle2;
  imNoPaddle3      = Not imPaddle3;
  imNoPaddle4      = Not imPaddle4;
  imNoPaddleAxis   = Not imPaddleAxis;
  imNoPaddleBtn    = Not imPaddleBtn;
  imNoPaddles      = Not imPaddles;
  imNoJoy1X        = Not imJoy1X;
  imNoJoy1Y        = Not imJoy1Y;
  imNoJoy2X        = Not imJoy2X;
  imNoJoy2Y        = Not imJoy2Y;
  imNoJoy1         = Not imJoy1;
  imNoJoy2         = Not imJoy2;
  imNoJoyBtn       = Not imJoyBtn;
  imNoJoysticks    = Not imJoysticks;
  imNoCBS          = Not imCBS;
  imNoDriving1Axis = Not imDriving1Axis;
  imNoDriving2Axis = Not imDriving2Axis;
  imNoDriving1     = Not imDriving1;
  imNoDriving2     = Not imDriving2;
  imNoDrivingAxis  = Not imDrivingAxis;
  imNoDrivingBtn   = Not imDrivingBtn;
  imNoDriving      = Not imDriving;
  imNoKB1          = Not imKB1;
  imNoKB2          = Not imKB2;
  imNoKeyboard     = Not imKeyboard;
  imNoConsole      = Not imConsole;

  { Define controller masks }

  cmPaddle1       = imPaddle1Axis + ifPaddle1Btn;
  cmPaddle2       = imPaddle2Axis + ifPaddle2Btn;
  cmPaddle3       = imPaddle3Axis + ifPaddle3Btn;
  cmPaddle4       = imPaddle4Axis + ifPaddle4Btn;
  cmJoy1          = imJoy1;
  cmJoy2          = imJoy2;
  cmBoosterGrip   = imCBS + imJoy1;
  cmDriving1      = imDriving1Axis + ifDriving1Btn;
  cmDriving2      = imDriving2Axis + ifDriving2Btn;
  cmKB1           = imKB1;
  cmKB2           = imKB2;

  { Console flags }

  cfPower   = $01;
  cfReset   = $02;
  cfSelect  = $04;
  cfColorBW = $08;
  cfDiff1   = $10;
  cfDiff2   = $20;

  { Other constants }

  Max_Digital_Con  = 32;
  Max_Digital_Btn  = 32;
  Max_Digital_Axis = 32;
  Max_Digital_Hat  = 32;
  Max_Digital_Thr  = 32;
  Max_Digital_Ana  = 32;

  Max_Joy_Btn   = 8;
  Max_Joy_Axis  = 6;

  Indy500Trans1 : Packed Array[0..3] Of Byte = ($EF,$FF,$DF,$CF);
  Indy500Trans2 : Packed Array[0..3] Of Byte = ($FE,$FF,$FD,$FC);

  { Define analog joystick extensions (Thanks to Vojtech Pavlik, author }
  { of the Linux joystick driver)                                       }

  JS_AN_BUTTONS_CHF    = $01;
  JS_AN_HAT1_CHF       = $02;
  JS_AN_HAT2_CHF       = $04;
  JS_AN_ANY_CHF        = $07;
  JS_AN_HAT_FCS        = $08;
  JS_AN_HATS_ALL       = $0e;
  JS_AN_BUTTON_PXY_X   = $10;
  JS_AN_BUTTON_PXY_Y   = $20;
  JS_AN_BUTTON_PXY_U   = $40;
  JS_AN_BUTTON_PXY_V   = $80;
  JS_AN_BUTTONS_PXY_XY = $30;
  JS_AN_BUTTONS_PXY_UV = $c0;
  JS_AN_BUTTONS_PXY    = $f0;

Type
  TInterfaceName  = String[20];

  TAtariControls  = Packed Record
    PortA   : Byte;
    Console : Byte;
    Pad     : Array[1..4] Of SmallInt;//LongInt;
    Input4  : Boolean;
    Input5  : Boolean;
    Driv    : Array[1..2] Of Word;
    CBS1    : Boolean;
    CBS2    : Boolean;
    KB      : LongInt;
  End;

  TCapability = Object
    A: LongInt;
    B: LongInt;
  End; { TCapability }

  { Analog axis record }

  TAxis = Record
    Cap : TCapability;
    Min : LongInt;
    Mid : LongInt;
    Max : LongInt;
  End;

  { Record defining capabilities of standard PC joystick, including     }
  { extended versions such as the Thrustmaster FCS, CH FlightStick Pro, }
  { and other assorted gamepads.                                        }

  TJoystickRec    = Record
    Axis : Array[1..Max_Joy_Axis] Of TAxis;      { Allow for 2 hats      }
    Btn  : Array[1..Max_Joy_Btn] Of TCapability; { Allow up to 8 buttons }
  End;

  { Record defining digital controller capabilities }

  TDigitalRec = Record
    Loaded   : Boolean;
    ClassMap : LongInt;
    NumBtns  : Integer;
    NumAxes  : Integer;
    NumHats  : Integer;
    NumThrs  : Integer;
    NumAnas  : Integer;
    Btn  : Array[1..Max_Digital_Btn]  Of TCapability;
    Axis : Array[1..Max_Digital_Axis] Of TCapability;
    Hat  : Array[1..Max_Digital_Hat]  Of TCapability;
    Thr  : Array[1..Max_Digital_Thr]  Of TAxis;
    Ana  : Array[1..Max_Digital_Ana]  Of TAxis;
  End;

  TDigitalInfoRec = Record
    Buttons  : GRIP_BITFIELD;
    Axis     : Array[1..Max_Digital_Axis] Of Word;
    Hat      : Array[1..Max_Digital_Hat]  Of Word;
    Throttle : Array[1..Max_Digital_Thr]  Of Word;
    Analog   : Array[1..Max_Digital_Ana]  Of Word;
  End;

  { TInterface abstract class: VERY emulator-specific }

  TInterface = Class
  Public
    Name     : TInterfaceName;
    Enabled  : Boolean;
    Active   : Boolean;
    Pad      : Array[1..4] Of LongInt;
    ReadMask : Byte;
    Constructor Create(St: String);
    Destructor  Destroy;                                          Override;
    Procedure   LoadConfiguration(F: TINIFile);                   Virtual;
    Procedure   SaveConfiguration(F: TINIFile);                   Virtual;
    Procedure   GetControllerInfo(Var Controllers: TCapability;
                                  Var Info: TAtariControls);      Virtual; Abstract;
    Procedure   Activate;                                         Virtual;
    Procedure   Deactivate;                                       Virtual;
    Procedure   GetDigitalAny(On: Boolean;
                              Var Cap: TCapability;
                              Var CapMask: TCapability;
                              Var Controllers: TCapability;
                              Var Info: TAtariControls);          Virtual;
    Procedure   GetAnalog(Min,Mid,Max,Value,XM: LongInt;
                          Var Cap: TCapability;
                          Var CapMask: TCapability;
                          Var Controllers: TCapability;
                          Var Info: TAtariControls);              Virtual;
    Procedure   WriteToPort(B: Byte);                             Virtual;

    { Capability routines }

    Function    UsedCapability(Var Cap: TCapability): Boolean;
    Procedure   ReadCapability(F: TINIFile; Const Section,Ident: String; Var Cap: TCapability);
    Procedure   WriteCapability(F: TINIFile; Const Section,Ident: String; Var Cap: TCapability);
    Procedure   SetAllCapability(Var Cap: TCapability);
    Procedure   AndCapabilities(Var Src,Dest: TCapability);
    Procedure   CopyTo(Intf: TInterface);                         Virtual;
  End; { TInterface }

  { Interface handler class: emulator non-specific }

  TInterfaceHandler = Class
  Public
    Stack : TStringList;
    Constructor Create;
    Destructor  Destroy;                                          Override;
    Procedure   LoadConfiguration(F: TINIFile);
    Procedure   SaveConfiguration(F: TINIFile);
    Procedure   GetControllerInfo(Controllers: TCapability;
                                  Var Info: TAtariControls);
    Procedure   AddInterface(Int: TInterface);
    Procedure   ActivateInterfaces;
    Procedure   DeactivateInterfaces;
    Procedure   SetReadMask(Mask: Byte);
    Procedure   WriteToPort(B: Byte);
  End; { TInterfaceHandler }

  { Subclasses of TInterface: partially emulator-specific }

  TMouseInterface = Class(TInterface)
  Private
    OldX      : Integer;
    OldY      : Integer;
  Public
    MouseX    : TAxis;
    MouseY    : TAxis;
    MouseBtn  : Array[1..3] Of TCapability;
    DrivX     : Integer;
    DrivCount : Word;
    DrivAdd   : Integer;
    Constructor Create;
    Destructor  Destroy;                                          Override;
    Procedure   LoadConfiguration(F: TINIFile);                   Override;
    Procedure   SaveConfiguration(F: TINIFile);                   Override;
    Procedure   GetControllerInfo(Var Controllers: TCapability;
                                  Var Info: TAtariControls);      Override;
    Procedure   GetAnalog(Min,Mid,Max,Value,XM: LongInt;
                          Var Cap: TCapability;
                          Var CapMask: TCapability;
                          Var Controllers: TCapability;
                          Var Info: TAtariControls);              Override;
    Procedure   CopyTo(Intf: TInterface);                         Override;
  End; { TMouseInterface }

  TKeyboardInterface = Class(TInterface)
  Public
    KeyCap : Array[0..$FF] Of TCapability;
//    Keys   : Array[0..$FF] Of Boolean;
    Constructor Create;
    Destructor  Destroy;                                          Override;
    Procedure   LoadConfiguration(F: TINIFile);                   Override;
    Procedure   SaveConfiguration(F: TINIFile);                   Override;
    Procedure   GetControllerInfo(Var Controllers: TCapability;
                                  Var Info: TAtariControls);      Override;
    Procedure   CopyTo(Intf: TInterface);                         Override;
  End; { TKeyboardInterface }
(*
  TExternalInterface = Class(TInterface)
    LPTCap : Array[0..2] Of TCapability;
    JoyCap : TAxis;
    Wrote  : Byte;
    Constructor Create;
    Destructor  Destroy;                                          Virtual;
    Procedure   LoadConfiguration(F: TINIFile);                   Virtual;
    Procedure   SaveConfiguration(F: TINIFile);                   Virtual;
    Procedure   Configure;                                        Virtual;
    Procedure   GetControllerInfo(Var Controllers: TCapability;
                                  Var Info: TAtariControls);      Virtual;
    Procedure   WriteToPort(B: Byte);                             Virtual;
    Procedure   Deactivate;                                       Virtual;
  End; { TExternalInterface }
*)
  { TDigitalInterface abstract class }

  TDigitalInterface = Class(TInterface)
  Public
    Cap       : Array[1..Max_Digital_Con] Of TDigitalRec;
    ContInfo  : Array[1..Max_Digital_Con] Of TDigitalInfoRec;
    ConInfo   : Array[1..Max_Digital_Con] Of TDigitalInfoRec;
    Constructor Create(St: String);
    Destructor  Destroy;                                          Override;
    Procedure   LoadConfiguration(F: TINIFile);                   Override;
    Procedure   SaveConfiguration(F: TINIFile);                   Override;
    Procedure   GetControllerInfo(Var Controllers: TCapability;
                                  Var Info: TAtariControls);      Override;
    Procedure   RefreshControllers;                               Virtual;
    Function    GetVendorName(Slot: Integer): String;             Virtual;
    Function    GetProductName(Slot: Integer): String;            Virtual;
    Function    GetSlotMap: GRIP_BITFIELD;                        Virtual;
    Function    GetClassMap(S: GRIP_SLOT): GRIP_BITFIELD;         Virtual;
    Function    GetMaxIndex(S: GRIP_SLOT; C: GRIP_CLASS): GRIP_INDEX; Virtual;
    Function    GetMaxValue(S: GRIP_SLOT; C: GRIP_CLASS): GRIP_VALUE; Virtual;
    Function    GetPackedValues(S: GRIP_SLOT; C: GRIP_CLASS; IStart: GRIP_INDEX;
                                IEnd: GRIP_INDEX): GRIP_BITFIELD; Virtual;
    Function    GetValue(S: GRIP_SLOT; C: GRIP_CLASS;
                         I: GRIP_INDEX): GRIP_VALUE;              Virtual;
    Procedure   CopyTo(Intf: TInterface);                         Override;
  End; { TDigitalInterface }

Var
  InterfaceHandler: TInterfaceHandler;
  _MouseB           : Integer;
  Keys              : Packed Array[0..$FF] Of Boolean;

Implementation

Uses Dialogs,SysUtils,Forms,frmMainUnt,DXInput,Controls,Windows,DirectX;

Type
  TXYPoint = Record
    X,Y: Integer;
  End;
  TJoystickDir = (jdLeft,jdRight,jdUp,jdDown);
  TKeyStr = String[5];
  TKeyRec = Record
    Name   : TKeyStr;
    X      : Integer;
    Y      : Integer;
  End;

Const
  _32BIT_OPERAND = $66;
  JoySamples     = 10;

  PadMask   : Array[1..4] Of LongInt = (imPaddle1,imPaddle2,imPaddle3,imPaddle4);
  PadMaskL  : Array[1..4] Of LongInt = (ifPaddle1Left,ifPaddle2Left,ifPaddle3Left,ifPaddle4Left);
  PadMaskR  : Array[1..4] Of LongInt = (ifPaddle1Right,ifPaddle2Right,ifPaddle3Right,ifPaddle4Right);
  PadMaskA  : Array[1..4] Of LongInt = (imPaddle1Axis,imPaddle2Axis,imPaddle3Axis,imPaddle4Axis);
  PadMaskNA : Array[1..4] Of LongInt = (imNoPaddle1Axis,imNoPaddle2Axis,imNoPaddle3Axis,imNoPaddle4Axis);
  PadMaskB  : Array[1..4] Of LongInt = (ifPaddle1Btn,ifPaddle2Btn,ifPaddle3Btn,ifPaddle4Btn);
  PadBtnOn  : Array[1..4] Of Byte = ($7F,$BF,$F7,$FB);

  JoyMask   : Array[1..2] Of LongInt = (imJoy1,imJoy2);
  JoyMaskL  : Array[1..2] Of LongInt = (ifJoy1Left,ifJoy2Left);
  JoyMaskR  : Array[1..2] Of LongInt = (ifJoy1Right,ifJoy2Right);
  JoyMaskU  : Array[1..2] Of LongInt = (ifJoy1Up,ifJoy2Up);
  JoyMaskD  : Array[1..2] Of LongInt = (ifJoy1Down,ifJoy2Down);
  JoyMaskB  : Array[1..2] Of LongInt = (ifJoy1Btn,ifJoy2Btn);
  JoyMaskX  : Array[1..2] Of LongInt = (imJoy1X,imJoy2X);
  JoyMaskY  : Array[1..2] Of LongInt = (imJoy1Y,imJoy2Y);
  JoyMaskNX : Array[1..2] Of LongInt = (imNoJoy1X,imNoJoy2X);
  JoyMaskNY : Array[1..2] Of LongInt = (imNoJoy1Y,imNoJoy2Y);
  JoyDirOn  : Array[1..2,TJoystickDir] Of Byte = (($BF,$7F,$EF,$DF),($FB,$F7,$FE,$FD));

  DrvMask   : Array[1..2] Of LongInt = (imDriving1,imDriving2);
  DrvMaskA  : Array[1..2] Of LongInt = (imDriving1Axis,imDriving2Axis);
  DrvMaskL  : Array[1..2] Of LongInt = (ifDriving1Left,ifDriving2Left);
  DrvMaskR  : Array[1..2] Of LongInt = (ifDriving1Right,ifDriving2Right);
  DrvMaskB  : Array[1..2] Of LongInt = (ifDriving1Btn,ifDriving2Btn);

Procedure Popup(St: String);
Begin
  ShowMessage(St);
End; { Popup }

{ ----------------- }
{ TInterfaceHandler }
{ ----------------- }

Constructor TInterfaceHandler.Create;
Begin
  Inherited Create;
  Stack := TStringList.Create;
End; { TInterfaceHandler.Create }

Destructor TInterfaceHandler.Destroy;
Begin
  Stack.Free;
  Inherited;
End; { TInterfaceHandler.Destroy }

Procedure TInterfaceHandler.LoadConfiguration(F: TINIFile);
Var I: Integer;
Begin
  I := 0;
  While I < Stack.Count Do
  Begin
    TInterface(Stack.Objects[I]).LoadConfiguration(F);
    Inc(I);
  End; { While }
End; { TInterfaceHandler.LoadConfiguration }

Procedure TInterfaceHandler.SaveConfiguration(F: TINIFile);
Var I: Integer;
Begin
  I := 0;
  While I < Stack.Count Do
  Begin
    TInterface(Stack.Objects[I]).SaveConfiguration(F);
    Inc(I);
  End; { While }
End; { TInterfaceHandler.SaveConfiguration }

Procedure TInterfaceHandler.GetControllerInfo(Controllers: TCapability;
                                              Var Info: TAtariControls);
Var
  I    : Integer;
  Intf : TInterface;

Begin
  I := Stack.Count - 1;
  While I >= 0 Do
  Begin
    Intf := TInterface(Stack.Objects[I]);
    If Intf.Enabled And Intf.Active Then
     Intf.GetControllerInfo(Controllers,Info);
    Dec(I);
  End; { While }
End; { TInterfaceHandler.GetControllerInfo }

Procedure TInterfaceHandler.AddInterface(Int: TInterface);
Begin
  Stack.AddObject('',Int);
End; { TInterfaceHandler.AddInterface }

Procedure TInterfaceHandler.ActivateInterfaces;
Var
  I    : Integer;
  Intf : TInterface;

Begin
  I := Stack.Count - 1;
  While I >= 0 Do
  Begin
    Intf := TInterface(Stack.Objects[I]);
    If Intf.Enabled Then Intf.Activate;
    Dec(I);
  End; { While }
End; { TInterfaceHandler.ActivateInterfaces }

Procedure TInterfaceHandler.DeactivateInterfaces;
Var
  I    : Integer;
  Intf : TInterface;

Begin
  I := Stack.Count - 1;
  While I >= 0 Do
  Begin
    Intf := TInterface(Stack.Objects[I]);
    If Intf.Enabled Then Intf.Deactivate;
    Dec(I);
  End; { While }
End; { TInterfaceHandler.DeactivateInterfaces }

Procedure TInterfaceHandler.SetReadMask(Mask: Byte);
Var
  I    : Integer;
  Intf : TInterface;

Begin
  I := Stack.Count - 1;
  While I >= 0 Do
  Begin
    Intf := TInterface(Stack.Objects[I]);
    Intf.ReadMask := Mask;
    Dec(I);
  End; { While }
End; { TInterfaceHandler.SetReadMask }

Procedure TInterfaceHandler.WriteToPort(B: Byte);
Var
  I    : Integer;
  Intf : TInterface;

Begin
  I := Stack.Count - 1;
  While I >= 0 Do
  Begin
    Intf := TInterface(Stack.Objects[I]);
    Intf.WriteToPort(B);
    Dec(I);
  End; { While }
End; { TInterfaceHandler.WriteToPort }

{ ---------- }
{ TInterface }
{ ---------- }

Constructor TInterface.Create(St: String);
Begin
  Inherited Create;
  Name     := St;
  Enabled  := False;
  Active   := False;
  FillChar(Pad,SizeOf(Pad),#0);
  ReadMask := $FF;  { All bits readable }
End; { TInterface.Create }

Destructor TInterface.Destroy;
Begin
  Inherited;
End; { TInterface.Destroy }

Function TInterface.UsedCapability(Var Cap: TCapability): Boolean;
Begin
  UsedCapability := (Cap.A <> 0) Or (Cap.B <> 0);
End; { TInterface.UsedCapability }

Procedure TInterface.ReadCapability(F: TINIFile; Const Section,Ident: String; Var Cap: TCapability);
Begin
  Cap.A := F.ReadInteger(Section,Ident + 'A',0);
  Cap.B := F.ReadInteger(Section,Ident + 'B',0);
End; { TInterface.ReadCapability }

Procedure TInterface.WriteCapability(F: TINIFile; Const Section,Ident: String; Var Cap: TCapability);
Begin
  F.WriteInteger(Section,Ident + 'A',Cap.A);
  F.WriteInteger(Section,Ident + 'B',Cap.B);
End; { TInterface.WriteCapability }

Procedure TInterface.SetAllCapability(Var Cap: TCapability);
Begin
  Cap.A := imAllACaps;
  Cap.B := imAllBCaps;
End; { TInterface.SetAllCapability }

Procedure TInterface.AndCapabilities(Var Src,Dest: TCapability);
Begin
  Dest.A := Dest.A And Src.A;
  Dest.B := Dest.B And Src.B;
End; { TInterface.AndCapabilities }

Procedure TInterface.LoadConfiguration(F: TINIFile);
Begin
  Enabled := F.ReadBool(Name,'ENABLED',False);
End; { TInterface.LoadConfiguration }

Procedure TInterface.SaveConfiguration(F: TINIFile);
Begin
  F.EraseSection(Name);
  F.WriteBool(Name,'ENABLED',Enabled);
End; { TInterface.SaveConfiguration }

Procedure TInterface.Activate;
Begin
  Active := True;
End; { TInterface.Activate }

Procedure TInterface.Deactivate;
Begin
  Active := False;
End; { TInterface.Deactivate }

Procedure TInterface.GetDigitalAny(On: Boolean;
                                   Var Cap: TCapability;
                                   Var CapMask: TCapability;
                                   Var Controllers: TCapability;
                                   Var Info: TAtariControls);
Const
  PadInc = 100;
  PadMax = Trunc(3.1 * 6.4 * 800);

Var I: Integer;
Begin

  { Paddles }

  For I := 1 To 4 Do
  Begin
    If ((Cap.A          And PadMask[I]) <> 0) And
       ((Controllers.A  And PadMask[I]) <> 0) Then
    Begin
      If ((Cap.A         And PadMaskL[I]) <> 0) And
         ((Controllers.A And PadMask[I]) <> 0) Then
      Begin
        If On Then
        Begin
          Info.Pad[I] := Pad[I] + PadInc;
          If Info.Pad[I] >= PadMax Then Info.Pad[I] := 0;
          Pad[I] := Info.Pad[I];
        End
        Else Info.Pad[I] := Pad[I];
        CapMask.A := CapMask.A And (Not PadMaskL[I]);
      End;
      If ((Cap.A         And PadMaskR[I]) <> 0) And
         ((Controllers.A And PadMaskR[I]) <> 0) Then
      Begin
        If On Then
        Begin
          Info.Pad[I] := Pad[I] - PadInc;
          If Info.Pad[I] < 0 Then Info.Pad[I] := PadMax;
          Pad[I] := Info.Pad[I];
        End
        Else Info.Pad[I] := Pad[I];
        CapMask.A := CapMask.A And (Not PadMaskR[I]);
      End;
      If ((Cap.A         And PadMaskB[I]) <> 0) And
         ((Controllers.A And PadMaskB[I]) <> 0) Then
      Begin
        If On Then Info.PortA := Info.PortA And PadBtnOn[I];
        CapMask.A := CapMask.A And (Not PadMaskB[I]);
      End;
    End;
  End; { For I }

  { Joysticks }

  For I := 1 To 2 Do
  Begin
    If ((Cap.A         And JoyMask[I]) <> 0) And
       ((Controllers.A And JoyMask[I]) <> 0) Then
    Begin
      If ((Cap.A         And JoyMaskL[I]) <> 0) And
         ((Controllers.A And JoyMaskL[I]) <> 0) Then
      Begin
        If On Then Info.PortA := Info.PortA And JoyDirOn[I,jdLeft];
        CapMask.A := CapMask.A And (Not JoyMaskL[I]);
      End;
      If ((Cap.A         And JoyMaskR[I]) <> 0) And
         ((Controllers.A And JoyMaskR[I]) <> 0) Then
      Begin
        If On Then Info.PortA := Info.PortA And JoyDirOn[I,jdRight];
        CapMask.A := CapMask.A And (Not JoyMaskR[I]);
      End;
      If ((Cap.A         And JoyMaskU[I]) <> 0) And
         ((Controllers.A And JoyMaskU[I]) <> 0) Then
      Begin
        If On Then Info.PortA := Info.PortA And JoyDirOn[I,jdUp];
        CapMask.A := CapMask.A And (Not JoyMaskU[I]);
      End;
      If ((Cap.A         And JoyMaskD[I]) <> 0) And
         ((Controllers.A And JoyMaskD[I]) <> 0) Then
      Begin
        If On Then Info.PortA := Info.PortA And JoyDirOn[I,jdDown];
        CapMask.A := CapMask.A And (Not JoyMaskD[I]);
      End;
      If ((Cap.A         And JoyMaskB[I]) <> 0) And
         ((Controllers.A And JoyMaskB[I]) <> 0) Then
      Begin
        If On Then
        Begin
          If I = 1 Then Info.Input4 := True Else Info.Input5 := True;
        End;
        CapMask.A := CapMask.A And (Not JoyMaskB[I]);
      End;
    End;
  End; { For I }

  { CBS Booster-Grip }

  If ((Cap.A         And imCBS) <> 0) And
     ((Controllers.A And imCBS) <> 0) Then
  Begin
    If ((Cap.A         And ifCBSBtn1) <> 0) And
       ((Controllers.A And ifCBSBtn1) <> 0) Then
    Begin
      If On Then Info.CBS1 := True;
      CapMask.A := CapMask.A And (Not ifCBSBtn1);
    End;
    If ((Cap.A         And ifCBSBtn2) <> 0) And
       ((Controllers.A And ifCBSBtn2) <> 0) Then
    Begin
      If On Then Info.CBS2 := True;
      CapMask.A := CapMask.A And (Not ifCBSBtn2);
    End;
  End;

  { Driving controllers }

  For I := 1 To 2 Do
  Begin
    If ((Cap.A         And DrvMask[I]) <> 0) And
       ((Controllers.A And DrvMask[I]) <> 0) Then
    Begin
      If ((Cap.A         And DrvMaskL[I]) <> 0) And
         ((Controllers.A And DrvMaskL[I]) <> 0) Then
      Begin
        If On Then Dec(Info.Driv[I],3);
        CapMask.A := CapMask.A And (Not DrvMaskL[I]);
      End;
      If ((Cap.A         And DrvMaskR[I]) <> 0) And
         ((Controllers.A And DrvMaskR[I]) <> 0) Then
      Begin
        If On Then Inc(Info.Driv[I],3);
        CapMask.A := CapMask.A And (Not DrvMaskR[I]);
      End;
      If ((Cap.A         And DrvMaskB[I]) <> 0) And
         ((Controllers.A And DrvMaskB[I]) <> 0) Then
      Begin
        If On Then
        Begin
          If I = 1 Then Info.Input4 := True Else Info.Input5 := True;
        End;
        CapMask.A := CapMask.A And (Not DrvMaskB[I]);
      End;
    End;
  End; { For I }

  { Keyboard controller 1 }

  If ((Cap.B         And imKB1) <> 0) And
     ((Controllers.B And imKB1) <> 0) Then
  Begin
    If ((Cap.B         And ifKB1_1) <> 0) And
       ((Controllers.B And ifKB1_1) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000010;
      CapMask.B := CapMask.B And (Not ifKB1_1);
    End;
    If ((Cap.B         And ifKB1_4) <> 0) And
       ((Controllers.B And ifKB1_4) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000020;
      CapMask.B := CapMask.B And (Not ifKB1_4);
    End;
    If ((Cap.B         And ifKB1_7) <> 0) And
       ((Controllers.B And ifKB1_7) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000040;
      CapMask.B := CapMask.B And (Not ifKB1_7);
    End;
    If ((Cap.B         And ifKB1_Star) <> 0) And
       ((Controllers.B And ifKB1_Star) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000080;
      CapMask.B := CapMask.B And (Not ifKB1_Star);
    End;
    If ((Cap.B         And ifKB1_2) <> 0) And
       ((Controllers.B And ifKB1_2) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00001000;
      CapMask.B := CapMask.B And (Not ifKB1_2);
    End;
    If ((Cap.B         And ifKB1_5) <> 0) And
       ((Controllers.B And ifKB1_5) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00002000;
      CapMask.B := CapMask.B And (Not ifKB1_5);
    End;
    If ((Cap.B         And ifKB1_8) <> 0) And
       ((Controllers.B And ifKB1_8) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00004000;
      CapMask.B := CapMask.B And (Not ifKB1_8);
    End;
    If ((Cap.B         And ifKB1_0) <> 0) And
       ((Controllers.B And ifKB1_0) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00008000;
      CapMask.B := CapMask.B And (Not ifKB1_0);
    End;
    If ((Cap.B         And ifKB1_3) <> 0) And
       ((Controllers.B And ifKB1_3) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00100000;
      CapMask.B := CapMask.B And (Not ifKB1_3);
    End;
    If ((Cap.B         And ifKB1_6) <> 0) And
       ((Controllers.B And ifKB1_6) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00200000;
      CapMask.B := CapMask.B And (Not ifKB1_6);
    End;
    If ((Cap.B         And ifKB1_9) <> 0) And
       ((Controllers.B And ifKB1_9) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00400000;
      CapMask.B := CapMask.B And (Not ifKB1_9);
    End;
    If ((Cap.B         And ifKB1_Pound) <> 0) And
       ((Controllers.B And ifKB1_Pound) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00800000;
      CapMask.B := CapMask.B And (Not ifKB1_Pound);
    End;
  End;

  { Keyboard controller 2 }

  If ((Cap.B         And imKB2) <> 0) And
     ((Controllers.B And imKB2) <> 0) Then
  Begin
    If ((Cap.B         And ifKB2_1) <> 0) And
       ((Controllers.B And ifKB2_1) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000001;
      CapMask.B := CapMask.B And (Not ifKB2_1);
    End;
    If ((Cap.B         And ifKB2_4) <> 0) And
       ((Controllers.B And ifKB2_4) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000002;
      CapMask.B := CapMask.B And (Not ifKB2_4);
    End;
    If ((Cap.B         And ifKB2_7) <> 0) And
       ((Controllers.B And ifKB2_7) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000004;
      CapMask.B := CapMask.B And (Not ifKB2_7);
    End;
    If ((Cap.B         And ifKB2_Star) <> 0) And
       ((Controllers.B And ifKB2_Star) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000008;
      CapMask.B := CapMask.B And (Not ifKB2_Star);
    End;
    If ((Cap.B         And ifKB2_2) <> 0) And
       ((Controllers.B And ifKB2_2) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000100;
      CapMask.B := CapMask.B And (Not ifKB2_2);
    End;
    If ((Cap.B         And ifKB2_5) <> 0) And
       ((Controllers.B And ifKB2_5) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000200;
      CapMask.B := CapMask.B And (Not ifKB2_5);
    End;
    If ((Cap.B         And ifKB2_8) <> 0) And
       ((Controllers.B And ifKB2_8) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000400;
      CapMask.B := CapMask.B And (Not ifKB2_8);
    End;
    If ((Cap.B         And ifKB2_0) <> 0) And
       ((Controllers.B And ifKB2_0) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00000800;
      CapMask.B := CapMask.B And (Not ifKB2_0);
    End;
    If ((Cap.B         And ifKB2_3) <> 0) And
       ((Controllers.B And ifKB2_3) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00010000;
      CapMask.B := CapMask.B And (Not ifKB2_3);
    End;
    If ((Cap.B         And ifKB2_6) <> 0) And
       ((Controllers.B And ifKB2_6) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00020000;
      CapMask.B := CapMask.B And (Not ifKB2_6);
    End;
    If ((Cap.B         And ifKB2_9) <> 0) And
       ((Controllers.B And ifKB2_9) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00040000;
      CapMask.B := CapMask.B And (Not ifKB2_9);
    End;
    If ((Cap.B         And ifKB2_Pound) <> 0) And
       ((Controllers.B And ifKB2_Pound) <> 0) Then
    Begin
      If On Then Info.KB := Info.KB Or  $00080000;
      CapMask.B := CapMask.B And (Not ifKB2_Pound);
    End;
  End;

  { Console switches }

  If ((Cap.B         And imConsole) <> 0) And
     ((Controllers.B And imConsole) <> 0) Then
  Begin
    If ((Cap.B         And ifPower) <> 0) And
       ((Controllers.B And ifPower) <> 0) Then
    Begin
      If On Then Info.Console := Info.Console Or cfPower;
      CapMask.B := CapMask.B And (Not ifPower);
    End;
    If ((Cap.B         And ifReset) <> 0) And
       ((Controllers.B And ifReset) <> 0) Then
    Begin
      If On Then Info.Console := Info.Console Or cfReset;
      CapMask.B := CapMask.B And (Not ifReset);
    End;
    If ((Cap.B         And ifSelect) <> 0) And
       ((Controllers.B And ifSelect) <> 0) Then
    Begin
      If On Then Info.Console := Info.Console Or cfSelect;
      CapMask.B := CapMask.B And (Not ifSelect);
    End;
    If ((Cap.B         And ifColorBW) <> 0) And
       ((Controllers.B And ifColorBW) <> 0) Then
    Begin
      If On Then Info.Console := Info.Console Or cfColorBW;
      CapMask.B := CapMask.B And (Not ifColorBW);
    End;
    If ((Cap.B         And ifDiff1) <> 0) And
       ((Controllers.B And ifDiff1) <> 0) Then
    Begin
      If On Then Info.Console := Info.Console Or cfDiff1;
      CapMask.B := CapMask.B And (Not ifDiff1);
    End;
    If ((Cap.B         And ifDiff2) <> 0) And
       ((Controllers.B And ifDiff2) <> 0) Then
    Begin
      If On Then Info.Console := Info.Console Or cfDiff2;
      CapMask.B := CapMask.B And (Not ifDiff2);
    End;
  End;
End; { TInterface.GetDigitalAny }

Procedure TInterface.GetAnalog(Min,Mid,Max,Value,XM: LongInt;
                               Var Cap: TCapability;
                               Var CapMask: TCapability;
                               Var Controllers: TCapability;
                               Var Info: TAtariControls);
Var
  PadGround : LongInt;
  I         : Integer;

Begin
  If Max = 0 Then Exit;
  PadGround := Round(27 * (640 / Max) * (Max - Value));

  { Paddle controllers }

  For I := 1 To 4 Do
  Begin
    If ((Cap.A         And PadMaskA[I]) <> 0) And
       ((Controllers.A And PadMaskA[I]) <> 0) Then
    Begin
      Info.Pad[I] := PadGround;
      CapMask.A   := CapMask.A And PadMaskNA[I];
    End;
  End; { For I }

  { Joysticks }

  For I := 1 To 2 Do
  Begin
    If ((Cap.A         And JoyMaskX[I]) <> 0) And
       ((Controllers.A And JoyMaskX[I]) <> 0) Then
    Begin
      If (Value <= (Min + Mid) Div 2) Then Info.PortA := Info.PortA And JoyDirOn[I,jdLeft];
      If (Value >= (Max + Mid) Div 2) Then Info.PortA := Info.PortA And JoyDirOn[I,jdRight];
      CapMask.A := CapMask.A And JoyMaskNX[I];
    End;
    If ((Cap.A         And JoyMaskY[I]) <> 0) And
       ((Controllers.A And JoyMaskY[I]) <> 0) Then
    Begin
      If (Value <= (Min + Mid) Div 2) Then Info.PortA := Info.PortA And JoyDirOn[I,jdUp];
      If (Value >= (Max + Mid) Div 2) Then Info.PortA := Info.PortA And JoyDirOn[I,jdDown];
      CapMask.A := CapMask.A And JoyMaskNY[I];
    End;
  End; { For I }

  { Driving controllers }

  For I := 1 To 2 Do
  Begin
    If ((Cap.A         And DrvMaskA[I]) <> 0) And
       ((Controllers.A And DrvMaskA[I]) <> 0) Then
    Begin
      If (Value <= (Min + Mid) Div 2) Then Dec(Info.Driv[I],3);
      If (Value >= (Max + Mid) Div 2) Then Inc(Info.Driv[I],3);
      CapMask.A := CapMask.A And (Not DrvMaskA[I]);
    End;
  End; { For I }
End; { TInterface.GetAnalog }

Procedure TInterface.WriteToPort(B: Byte);
Begin
End; { TInterface.WriteToPort }

Procedure TInterface.CopyTo(Intf: TInterface);
Begin
  Intf.Name     := Name;
  Intf.Enabled  := Enabled;
  Intf.Active   := Active;
  Intf.Pad      := Pad;
  Intf.ReadMask := ReadMask;
End; // TInterface.CopyTo

{ --------------- }
{ TMouseInterface }
{ --------------- }

Constructor TMouseInterface.Create;
Begin
  Inherited Create('Mouse');
  FillChar(MouseX,SizeOf(MouseX),#0);
  FillChar(MouseY,SizeOf(MouseY),#0);
  FillChar(MouseBtn,SizeOf(MouseBtn),#0);
  MouseX.Min := 0;
  MouseY.Min := 0;
  MouseX.Mid := 320;
  MouseY.Mid := 100;
  MouseX.Max := 640;
  MouseY.Max := 200;
  DrivX      := 99;
  DrivCount  := 0;
  DrivAdd    := 0;
  OldX       := -1;
  OldY       := -1;
End; { TMouseInterface.Create }

Destructor TMouseInterface.Destroy;
Begin
  Inherited;
End; { TMouseInterface.Destroy }

Procedure TMouseInterface.LoadConfiguration(F: TINIFile);
Begin
  Inherited LoadConfiguration(F);
  ReadCapability(F,Name,'X',MouseX.Cap);
  MouseX.Min   := F.ReadInteger(Name,'XMIN',0);
  MouseX.Mid   := F.ReadInteger(Name,'XMID',0);
  MouseX.Max   := F.ReadInteger(Name,'XMAX',0);

  ReadCapability(F,Name,'Y',MouseY.Cap);
  MouseY.Min   := F.ReadInteger(Name,'YMIN',0);
  MouseY.Mid   := F.ReadInteger(Name,'YMID',0);
  MouseY.Max   := F.ReadInteger(Name,'YMAX',0);

  ReadCapability(F,Name,'B1',MouseBtn[1]);
  ReadCapability(F,Name,'B2',MouseBtn[2]);
  ReadCapability(F,Name,'B3',MouseBtn[3]);
End; { TMouseInterface.LoadConfiguration }

Procedure TMouseInterface.SaveConfiguration(F: TINIFile);
Begin
  Inherited SaveConfiguration(F);

  WriteCapability(F,Name,'X',MouseX.Cap);
  F.WriteInteger(Name,'XMIN',MouseX.Min);
  F.WriteInteger(Name,'XMID',MouseX.Mid);
  F.WriteInteger(Name,'XMAX',MouseX.Max);

  WriteCapability(F,Name,'Y',MouseY.Cap);
  F.WriteInteger(Name,'YMIN',MouseY.Min);
  F.WriteInteger(Name,'YMID',MouseY.Mid);
  F.WriteInteger(Name,'YMAX',MouseY.Max);

  WriteCapability(F,Name,'B1',MouseBtn[1]);
  WriteCapability(F,Name,'B2',MouseBtn[2]);
  WriteCapability(F,Name,'B3',MouseBtn[3]);
End; { TMouseInterface.SaveConfiguration }

Procedure TMouseInterface.GetControllerInfo(Var Controllers: TCapability;
                                            Var Info: TAtariControls);
Var
  MX,MY,MB  : Integer;
  XM,YM     : Integer;
  CapMask   : TCapability;
  Pt        : TPoint;

Begin

  { Initialize the capability mask to include all capabliities }

  SetAllCapability(CapMask);

  { Read the mouse position and calculate the paddle "position" }

  GetCursorPos(Pt);

  MB := _MouseB;
  MX := (Pt.X - frmMain.Left) * 2;
  MY := Pt.Y - frmMain.Top;
  If MX < MouseX.Min Then MX := 0;
  If MY < MouseY.Min Then MY := 0;
  If MX > MouseX.Max Then MX := MouseX.Max;
  If MY > MouseY.Max Then MY := MouseY.Max;
  If OldX >= 0 Then XM := MX - OldX Else XM := 0;
  If OldY >= 0 Then YM := MY - OldY Else YM := 0;

  { Left button }

  GetDigitalAny(((MB And 1) <> 0),MouseBtn[1],CapMask,Controllers,Info);

  { Right button }

  GetDigitalAny(((MB And 2) <> 0),MouseBtn[2],CapMask,Controllers,Info);

  { Middle button }

  GetDigitalAny(((MB And 4) <> 0),MouseBtn[3],CapMask,Controllers,Info);

  { X axis }

  GetAnalog(MouseX.Min,MouseX.Mid,MouseX.Max,MX,XM,MouseX.Cap,CapMask,Controllers,Info);

  { Y axis }

  GetAnalog(MouseY.Min,MouseY.Mid,MouseY.Max,MY,YM,MouseY.Cap,CapMask,Controllers,Info);

  { Remove capabilities that were used }

  AndCapabilities(CapMask,Controllers);

  // Save values for next time

  OldX := MX;
  OldY := MY;
End; { TMouseInterface.GetControllerInfo }

Procedure TMouseInterface.GetAnalog(Min,Mid,Max,Value,XM: LongInt;
                               Var Cap: TCapability;
                               Var CapMask: TCapability;
                               Var Controllers: TCapability;
                               Var Info: TAtariControls);
Var
  PadGround : LongInt;
  XInc      : Integer;
  I         : Integer;

Begin
  If Max = 0 Then Exit;
  PadGround := Round(27 * (640 / Max) * (Max - Value));

  { Paddle controllers }

  For I := 1 To 4 Do
  Begin
    If ((Cap.A         And PadMaskA[I]) <> 0) And
       ((Controllers.A And PadMaskA[I]) <> 0) Then
    Begin
      Info.Pad[I] := PadGround;
      CapMask.A   := CapMask.A And PadMaskNA[I];
    End;
  End; { For I }

  { Joysticks }

  For I := 1 To 2 Do
  Begin
    If ((Cap.A         And JoyMaskX[I]) <> 0) And
       ((Controllers.A And JoyMaskX[I]) <> 0) Then
    Begin
      If (Value <= (Min + Mid) Div 2) Then Info.PortA := Info.PortA And JoyDirOn[I,jdLeft];
      If (Value >= (Max + Mid) Div 2) Then Info.PortA := Info.PortA And JoyDirOn[I,jdRight];
      CapMask.A := CapMask.A And JoyMaskNX[I];
    End;
    If ((Cap.A         And JoyMaskY[I]) <> 0) And
       ((Controllers.A And JoyMaskY[I]) <> 0) Then
    Begin
      If (Value <= (Min + Mid) Div 2) Then Info.PortA := Info.PortA And JoyDirOn[I,jdUp];
      If (Value >= (Max + Mid) Div 2) Then Info.PortA := Info.PortA And JoyDirOn[I,jdDown];
      CapMask.A := CapMask.A And JoyMaskNY[I];
    End;
  End; { For I }

  { Driving controller 1 }

  If ((Cap.A         And imDriving1) <> 0) And
     ((Controllers.A And imDriving1) <> 0) Then
  Begin
    If ((Cap.A         And imDriving1Axis) <> 0) And
       ((Controllers.A And imDriving1Axis) <> 0) Then
    Begin
      If DrivX > 32 Then
      Begin
        XM    := 0;
        DrivX := 0;
      End;
      XInc := Round((XM + DrivAdd) / 64);
      If (XM <> 0) And (XInc = 0) Then
      Begin
        Inc(DrivAdd,XM);
      End;
      If XInc < 0 Then
      Begin
        Inc(DrivX,XInc);
        If DrivX < -32 Then DrivX := -32;
        DrivAdd := 0;
      End;
      If XInc > 0 Then
      Begin
        Inc(DrivX,XInc);
        If DrivX > 32 Then DrivX := 32;
        DrivAdd := 0;
      End;
      If DrivX < 0 Then
      Begin
        If DrivCount = 0 Then
        Begin
          Inc(DrivX);
          If DrivX > 0 Then DrivX := 0;
          Dec(Info.Driv[1],3);
          DrivCount := 1;
        End
        Else Dec(DrivCount);
      End;
      If DrivX > 0 Then
      Begin
        If DrivCount = 0 Then
        Begin
          Dec(DrivX);
          If DrivX < 0 Then DrivX := 0;
          Inc(Info.Driv[1],3);
          DrivCount := 1;
        End
        Else Dec(DrivCount);
      End;
    End;
    CapMask.A := CapMask.A And imNoDriving1;
  End;

  { Driving controller 2 }

  If ((Cap.A         And imDriving2) <> 0) And
     ((Controllers.A And imDriving2) <> 0) Then
  Begin
    If ((Cap.A         And imDriving2Axis) <> 0) And
       ((Controllers.A And imDriving2Axis) <> 0) Then
    Begin
      If (Value Shr 2) < DrivX Then
      Begin
        If DrivCount = 0 Then
        Begin
          Dec(Info.Driv[2],3);
          DrivCount := 4;
        End
        Else Dec(DrivCount);
        Dec(DrivX);
      End;
      If (Value Shr 2) > DrivX Then
      Begin
        If DrivCount = 0 Then
        Begin
          Inc(Info.Driv[2],3);
          DrivCount := 4;
        End
        Else Dec(DrivCount);
        Inc(DrivX);
      End;
    End;
    CapMask.A := CapMask.A And imNoDriving2;
  End;
End; { TMouseInterface.GetAnalog }

Procedure TMouseInterface.CopyTo(Intf: TInterface);
Begin
  Inherited CopyTo(Intf);
  TMouseInterface(Intf).MouseX    := MouseX;
  TMouseInterface(Intf).MouseY    := MouseY;
  TMouseInterface(Intf).MouseBtn  := MouseBtn;
  TMouseInterface(Intf).DrivX     := DrivX;
  TMouseInterface(Intf).DrivCount := DrivCount;
  TMouseInterface(Intf).DrivAdd   := DrivAdd;
End; // TMouseInterface.CopyTo

{ ------------------ }
{ TKeyboardInterface }
{ ------------------ }

Constructor TKeyboardInterface.Create;
Begin
  Inherited Create('Keyboard');
  FillChar(KeyCap,SizeOf(KeyCap),#0);

  { Predefine console switches - other function }
  { keys are handled in the main emulator.      }

  KeyCap[DIK_ESCAPE].B   := ifPower;
  KeyCap[DIK_F2].B       := ifReset;
  KeyCap[DIK_F4].B       := ifSelect;
  KeyCap[DIK_F5].B       := ifColorBW;
  KeyCap[DIK_F6].B       := ifDiff1;
  KeyCap[DIK_F7].B       := ifDiff2;
End; { TKeyboardInterface.Create }

Destructor TKeyboardInterface.Destroy;
Begin
  Inherited;
End; { TKeyboardInterface.Destroy }

Procedure TKeyboardInterface.LoadConfiguration(F: TINIFile);
Var I: Integer;
Begin
  Inherited LoadConfiguration(F);
  For I := 0 To 255 Do
   ReadCapability(F,Name,'K' + IntToStr(I),KeyCap[I]);
End; { TKeyboardInterface.LoadConfiguration }

Procedure TKeyboardInterface.SaveConfiguration(F: TINIFile);
Var I: Integer;
Begin
  Inherited SaveConfiguration(F);
  For I := 0 To 255 Do
  Begin
    If UsedCapability(KeyCap[I]) Then
     WriteCapability(F,Name,'K' + IntToStr(I),KeyCap[I]);
  End; { For I }
End; { TKeyboardInterface.SaveConfiguration }

Procedure TKeyboardInterface.GetControllerInfo(Var Controllers: TCapability;
                                               Var Info: TAtariControls);
Var
  TheKey  : Integer;
  CapMask : TCapability;

Begin

  { Initialize the capability mask to include all capabliities }

  SetAllCapability(CapMask);

  { Get the key information }

  frmMain.DXInput1.Update;
  For TheKey := 0 To $FF Do
  Begin
    Keys[TheKey] := frmMain.DXInput1.Keyboard.DIKeys[TheKey];
    GetDigitalAny(Keys[TheKey],KeyCap[TheKey],CapMask,Controllers,Info);
  End;

  { Remove capabilities that were used }

  AndCapabilities(CapMask,Controllers);
End; { TKeyboardInterface.GetControllerInfo }

Procedure TKeyboardInterface.CopyTo(Intf: TInterface);
Begin
  Inherited CopyTo(Intf);
  TKeyboardInterface(Intf).KeyCap := KeyCap;
End; // TKeyboardInterface.CopyTo

{ ------------------ }
{ TDigitalInterface  }
{ ------------------ }

Constructor TDigitalInterface.Create(St: String);
Begin
  Inherited Create(St);
  FillChar(Cap,SizeOf(Cap),#0);
  FillChar(ContInfo,SizeOf(ContInfo),#0);
  FillChar(ConInfo,SizeOf(ConInfo),#0);
End; { TDigitalInterface.Create }

Destructor TDigitalInterface.Destroy;
Begin
  Inherited;
End; { TDigitalInterface.Destroy }

Procedure TDigitalInterface.LoadConfiguration(F: TINIFile);
Var
  Con,Btn,Axis,Hat,Thr,Ana : Integer;
  ConStr,ThrStr,AnaStr     : String[4];

Begin
  Inherited LoadConfiguration(F);
  FillChar(Cap,SizeOf(Cap),#0);
  For Con := 1 To Max_Digital_Con Do
  Begin
    ConStr := 'C' + IntToStr(Con);
    If F.ValueExists(Name,ConStr + 'LOADED') Then
    Begin
      Cap[Con].Loaded   := F.ReadBool(Name,ConStr + 'LOADED',False);
      Cap[Con].ClassMap := F.ReadInteger(Name,ConStr + 'CLASSMAP',0);
      Cap[Con].NumBtns  := F.ReadInteger(Name,ConStr + 'NUMBTNS',0);
      Cap[Con].NumAxes  := F.ReadInteger(Name,ConStr + 'NUMAXES',0);
      Cap[Con].NumHats  := F.ReadInteger(Name,ConStr + 'NUMHATS',0);
      Cap[Con].NumThrs  := F.ReadInteger(Name,ConStr + 'NUMTHRS',0);
      Cap[Con].NumAnas  := F.ReadInteger(Name,ConStr + 'NUMANAS',0);
      For Btn := 1 To Max_Digital_Btn Do
       ReadCapability(F,Name,ConStr + 'B' + IntToStr(Btn),Cap[Con].Btn[Btn]);
      For Axis := 1 To Max_Digital_Axis Do
       ReadCapability(F,Name,ConStr + 'D' + IntToStr(Axis),Cap[Con].Axis[Axis]);
      For Hat := 1 To Max_Digital_Hat Do
       ReadCapability(F,Name,ConStr + 'H' + IntToStr(Hat),Cap[Con].Hat[Hat]);
      For Thr := 1 To Max_Digital_Thr Do
      Begin
        ThrStr := ConStr + 'T' + IntToStr(Thr);
        ReadCapability(F,Name,ThrStr,Cap[Con].Thr[Thr].Cap);
        Cap[Con].Thr[Thr].Min   := F.ReadInteger(Name,ThrStr + 'MIN',0);
        Cap[Con].Thr[Thr].Mid   := F.ReadInteger(Name,ThrStr + 'MID',0);
        Cap[Con].Thr[Thr].Max   := F.ReadInteger(Name,ThrStr + 'MAX',0);
      End; { For Thr }
      For Ana := 1 To Max_Digital_Ana Do
      Begin
        AnaStr := ConStr + 'A' + IntToStr(Ana);
        ReadCapability(F,Name,AnaStr,Cap[Con].Ana[Ana].Cap);
        Cap[Con].Ana[Ana].Min   := F.ReadInteger(Name,AnaStr + 'MIN',0);
        Cap[Con].Ana[Ana].Mid   := F.ReadInteger(Name,AnaStr + 'MID',0);
        Cap[Con].Ana[Ana].Max   := F.ReadInteger(Name,AnaStr + 'MAX',0);
      End; { For Ana }
    End;  
  End; { For Con }
End; { TDigitalInterface.LoadConfiguration }

Procedure TDigitalInterface.SaveConfiguration(F: TINIFile);
Var
  Con,Btn,Axis,Hat,Thr,Ana : Integer;
  ConStr,ThrStr,AnaStr     : String[4];

Begin
  Inherited SaveConfiguration(F);
  For Con := 1 To Max_Digital_Con Do
  Begin
    If Cap[Con].ClassMap <> 0 Then
    Begin
      ConStr := 'C' + IntToStr(Con);
      F.WriteBool(Name,ConStr + 'LOADED',Cap[Con].Loaded);
      F.WriteInteger(Name,ConStr + 'CLASSMAP',Cap[Con].ClassMap);
      F.WriteInteger(Name,ConStr + 'NUMBTNS',Cap[Con].NumBtns);
      F.WriteInteger(Name,ConStr + 'NUMAXES',Cap[Con].NumAxes);
      F.WriteInteger(Name,ConStr + 'NUMHATS',Cap[Con].NumHats);
      F.WriteInteger(Name,ConStr + 'NUMTHRS',Cap[Con].NumThrs);
      F.WriteInteger(Name,ConStr + 'NUMANAS',Cap[Con].NumAnas);
      For Btn := 1 To Max_Digital_Btn Do
      Begin
        If UsedCapability(Cap[Con].Btn[Btn]) Then
         WriteCapability(F,Name,ConStr + 'B' + IntToStr(Btn),Cap[Con].Btn[Btn]);
      End; { For Btn }
      For Axis := 1 To Max_Digital_Axis Do
      Begin
        If UsedCapability(Cap[Con].Axis[Axis]) Then
         WriteCapability(F,Name,ConStr + 'D' + IntToStr(Axis),Cap[Con].Axis[Axis]);
      End; { For Axis }
      For Hat := 1 To Max_Digital_Hat Do
      Begin
        If UsedCapability(Cap[Con].Hat[Hat]) Then
         WriteCapability(F,Name,ConStr + 'H' + IntToStr(Hat),Cap[Con].Hat[Hat]);
      End; { For Hat }
      For Thr := 1 To Max_Digital_Thr Do
      Begin
        If UsedCapability(Cap[Con].Thr[Thr].Cap) Then
        Begin
          ThrStr := ConStr + 'T' + IntToStr(Thr);
          WriteCapability(F,Name,ThrStr,Cap[Con].Thr[Thr].Cap);
          F.WriteInteger(Name,ThrStr + 'MIN',Cap[Con].Thr[Thr].Min);
          F.WriteInteger(Name,ThrStr + 'MID',Cap[Con].Thr[Thr].Mid);
          F.WriteInteger(Name,ThrStr + 'MAX',Cap[Con].Thr[Thr].Max);
        End;
      End; { For Thr }
      For Ana := 1 To Max_Digital_Ana Do
      Begin
        If UsedCapability(Cap[Con].Ana[Ana].Cap) Then
        Begin
          AnaStr := ConStr + 'A' + IntToStr(Ana);
          WriteCapability(F,Name,AnaStr,Cap[Con].Ana[Ana].Cap);
          F.WriteInteger(Name,AnaStr + 'MIN',Cap[Con].Ana[Ana].Min);
          F.WriteInteger(Name,AnaStr + 'MID',Cap[Con].Ana[Ana].Mid);
          F.WriteInteger(Name,AnaStr + 'MAX',Cap[Con].Ana[Ana].Max);
        End;
      End; { For Ana }
    End;
  End; { For Con }
End; { TDigitalInterface.SaveConfiguration }

Procedure TDigitalInterface.RefreshControllers;

Type BPtr = ^Byte;

Var
  I,J,K : Integer;
  Con   : Integer;
  Joy   : TJoystick2;
{
  JS    : DIJOYSTATE2;
  St    : String;
  A     : Array[0..1023] Of Byte;
}
Begin
  FillChar(ConInfo,SizeOf(ConInfo),#0);
  frmMain.DXInput1.Update;


//  BPtr(SDesc.lpSurface)^ := 15;


  For Con := 0 To frmMain.DXInput1.NumJoysticks - 1 Do
  Begin
    Joy := frmMain.DXInput1.Joystick[Con];
    J := Joy.ButtonCount - 1;
//    J := frmMain.DXInput1.Joystick.ButtonCount - 1;
    K := 0;
    If J > 31 Then J := 31;

    For I := 0 To J Do If Joy.Buttons[I] Then K := K Or (1 Shl I);
//    For I := 0 To J Do If frmMain.DXInput1.Joystick.Buttons[I] Then K := K Or (1 Shl I);

    ConInfo[Con + 1].Buttons := K;
(*
    frmMain.Label1.Caption := 'NumJoys = ' + IntToStr(frmMain.DXInput1.NumJoysticks) + #13#10 +
                              'X = ' + IntToStr(frmMain.DXInput1.Joystick[2].X) + #13#10 +
                              'RangeX = ' + IntToStr(frmMain.DXInput1.Joystick[2].RangeX) + #13#10 +
                              'Y = ' + IntToStr(frmMain.DXInput1.Joystick[2].Y) + #13#10 +
                              'RangeY = ' + IntToStr(frmMain.DXInput1.Joystick[2].RangeY) + #13#10 +
                              'Z = ' + IntToStr(frmMain.DXInput1.Joystick[2].Z) + #13#10 +
                              'RangeZ = ' + IntToStr(frmMain.DXInput1.Joystick[2].RangeZ);
*)
    I := Joy.AxisCount;
    If I >= 1 Then ConInfo[Con + 1].Analog[1] := Joy.X + Joy.RangeX;
    If I >= 2 Then ConInfo[Con + 1].Analog[2] := Joy.Y + Joy.RangeY;
    If I >= 3 Then ConInfo[Con + 1].Analog[3] := Joy.Z + Joy.RangeZ;
    If I >= 4 Then ConInfo[Con + 1].Analog[4] := Joy.U + Joy.RangeX;
    If I >= 5 Then ConInfo[Con + 1].Analog[5] := Joy.V + Joy.RangeX;
    If I >= 6 Then ConInfo[Con + 1].Analog[6] := Joy.W + Joy.RangeX;

    For I := 0 To Joy.POVCount - 1 Do
    Begin
      J := Joy.POV[I];
      If J < 0 Then
      Begin
        ConInfo[Con + 1].Hat[I * 2 + 1] := 10;
        ConInfo[Con + 1].Hat[I * 2 + 2] := 10;
      End
      Else
      Begin
        // Horizontal direction

        Case J Of
          0,18000:           ConInfo[Con + 1].Hat[I * 2 + 1] := 10;
          4500,9000,13500:   ConInfo[Con + 1].Hat[I * 2 + 1] := 20;
          22500,27000,31500: ConInfo[Con + 1].Hat[I * 2 + 1] := 0;
        End; // Case

        // Vertical direction

        Case J Of
          0,4500,31500:      ConInfo[Con + 1].Hat[I * 2 + 2] := 0;
          9000,27000:        ConInfo[Con + 1].Hat[I * 2 + 2] := 10;
          13500,18000,22500: ConInfo[Con + 1].Hat[I * 2 + 2] := 20;
        End; // Case }
      End;
    End; // For I
(*
    // Display entire joystick status (testing purposes only)

    If Con = 0 Then
    Begin
      St := '';
      For I := 1 To 6 Do St := St + IntToStr(ConInfo[Con + 1].Analog[I]) + #13#10;
      St := St + IntToStr(ConInfo[Con + 1].Hat[1]) + #13#10;
      St := St + IntToStr(ConInfo[Con + 1].Hat[2]) + #13#10;
      St := St + BinLongInt(ConInfo[Con + 1].Buttons) + #13#10;
      JS := Joy.JoyState;
      Move(JS,A,SizeOf(JS));
      For I := 0 To SizeOf(JS) - 1 Do
      Begin
        St := St + HexByte(A[I]);
        If (I And $F) = 7 Then St := St + '-' Else St := St + ' ';
        If (I And $F) = $F Then St := St + #13#10;
      End; // For I
      frmMain.Label1.Caption := St;
    End;
*)
//    I := frmMain.DXInput1.Joystick.AxisCount;
//    If I >= 1 Then ConInfo.Analog[1] := frmMain.DXInput1.Joystick.X + frmMain.DXInput1.Joystick.RangeX;
//    If I >= 2 Then ConInfo.Analog[2] := frmMain.DXInput1.Joystick.Y + frmMain.DXInput1.Joystick.RangeY;
//    If I >= 3 Then ConInfo.Analog[3] := frmMain.DXInput1.Joystick.Z + frmMain.DXInput1.Joystick.RangeZ;

  {
    frmMain.Label1.Caption := 'ButtonCount=' + IntToStr(J) + '   Buttons=' + IntToStr(K) + #13#10 +
     'X=' + IntToStr(frmMain.DXInput1.Joystick.X) + #13#10 +
     'Y=' + IntToStr(frmMain.DXInput1.Joystick.Y) + #13#10 +
     'Name=' + frmMain.DXInput1.Joystick.ProductName + #13#10 +
     'Time=' + IntToStr(GetTickCount);
  }
  End; // For Con
End; { TDigitalInterface.RefreshControllers }

Function TDigitalInterface.GetVendorName(Slot: Integer): String;
Begin
  GetVendorName := '';
End; { TDigitalInterface.GetVendorName }

Function TDigitalInterface.GetProductName(Slot: Integer): String;
Begin
  If Slot < frmMain.DXInput1.NumJoysticks
   Then GetProductName := frmMain.DXInput1.Joystick[Slot].ProductName
   Else GetProductName := '';
(*
  Case Slot Of
    0: GetProductName := frmMain.DXInput1.Joystick.ProductName;
//    1: GetProductName := frmMain.DXInput2.Joystick.ProductName;
//    2: GetProductName := frmMain.DXInput3.Joystick.ProductName;
//    3: GetProductName := frmMain.DXInput4.Joystick.ProductName;
  Else
    GetProductName := '';
  End; // Case
*)  
End; { TDigitalInterface.GetProductName }

Function TDigitalInterface.GetSlotMap: GRIP_BITFIELD;
Var
  SMap : GRIP_BITFIELD;
  I    : Integer;

Begin
  SMap := 0;
  For I := 0 To Max_Digital_Con - 1 Do
   If frmMain.DXInput1.NumJoysticks > I Then SMap := SMap + (1 Shl I);
  GetSlotMap := SMap;
End; { TDigitalInterface.GetSlotMap }

Function TDigitalInterface.GetClassMap(S: GRIP_SLOT): GRIP_BITFIELD;
Var CMap: GRIP_BITFIELD;
Begin
  CMap := 0;
  If S < frmMain.DXInput1.NumJoysticks Then
  Begin
    If frmMain.DXInput1.Joystick[S].ButtonCount > 0 Then CMap := CMap + (1 Shl GRIP_CLASS_BUTTON);
    If frmMain.DXInput1.Joystick[S].POVCount    > 0 Then CMap := CMap + (1 Shl GRIP_CLASS_POV_HAT);
    If frmMain.DXInput1.Joystick[S].AxisCount   > 0 Then CMap := CMap + (1 Shl GRIP_CLASS_ANALOG);
  End;
  GetClassMap := CMap;
End; { TDigitalInterface.GetClassMap }

Function TDigitalInterface.GetMaxIndex(S: GRIP_SLOT; C: GRIP_CLASS): GRIP_INDEX;
Var MaxInd: Integer;
Begin
  MaxInd := 0;
  If S < frmMain.DXInput1.NumJoysticks Then
  Begin
    Case C Of
      GRIP_CLASS_BUTTON: MaxInd := frmMain.DXInput1.Joystick[S].ButtonCount - 1;
     GRIP_CLASS_POV_HAT: MaxInd := frmMain.DXInput1.Joystick[S].POVCount * 2 - 1;
      GRIP_CLASS_ANALOG: MaxInd := frmMain.DXInput1.Joystick[S].AxisCount   - 1;
    Else
      MaxInd := 0;
    End; // Case
  End;
  GetMaxIndex := MaxInd;
End; { TDigitalInterface.GetMaxIndex }

Function TDigitalInterface.GetMaxValue(S: GRIP_SLOT; C: GRIP_CLASS): GRIP_VALUE;
Var MaxVal: Integer;

//St : String;

Begin
  MaxVal := 0;
  If S < frmMain.DXInput1.NumJoysticks Then
  Begin
    Case C Of
     GRIP_CLASS_POV_HAT: MaxVal := 20;
      GRIP_CLASS_ANALOG: MaxVal := 2 * frmMain.DXInput1.Joystick[S].RangeX;
    Else
      MaxVal := 0;
    End; // Case
  End;
  GetMaxValue := MaxVal;
End; { TDigitalInterface.GetMaxValue }

Function TDigitalInterface.GetPackedValues(S: GRIP_SLOT; C: GRIP_CLASS; IStart: GRIP_INDEX;
                         IEnd: GRIP_INDEX): GRIP_BITFIELD;
Var
  I : Integer;
  L : GRIP_BITFIELD;

Begin
  L := 0;
  For I := IStart To IEnd Do L := L Or (1 Shl I);
  If (GetClassMap(S) And (1 Shl C)) <> 0 Then
  Begin
    If C = GRIP_CLASS_BUTTON
     Then GetPackedValues := ConInfo[S + 1].Buttons And L
     Else GetPackedValues := 0;
  End
  Else GetPackedValues := 0;
End; { TDigitalInterface.GetPackedValues }

Function TDigitalInterface.GetValue(S: GRIP_SLOT; C: GRIP_CLASS;
                                    I: GRIP_INDEX): GRIP_VALUE;
{Var
  J  : Integer;
  St : String;}

Begin
  If (GetClassMap(S) And (1 Shl C)) <> 0 Then
  Begin
    If I <= GetMaxIndex(S,C) Then
    Begin
      Case C Of
        GRIP_CLASS_AXIS: GetValue := ConInfo[S + 1].Axis[I + 1];
     GRIP_CLASS_POV_HAT: GetValue := ConInfo[S + 1].Hat[I + 1];
    GRIP_CLASS_THROTTLE: GetValue := ConInfo[S + 1].Throttle[I + 1];
      GRIP_CLASS_ANALOG: GetValue := ConInfo[S + 1].Analog[I + 1];
      Else
        GetValue := 0;
      End; { Case }
    End
    Else GetValue := 0;
  End
  Else GetValue := 0;
End; { TDigitalInterface.GetValue }

Procedure TDigitalInterface.GetControllerInfo(Var Controllers: TCapability;
                                              Var Info: TAtariControls);
Var
  SlotMap  : LongInt;
//  ClassMap : LongInt;
  Slot     : Integer;
  Cont     : Integer;
  Mask     : LongInt;
//  NumBtns  : Integer;
//  NumAxes  : Integer;
//  NumHats  : Integer;
//  NumThrs  : Integer;
//  NumAnas  : Integer;
  I        : Integer;
  CapMask  : TCapability;
  MaxThr   : Word;
  MaxAna   : Word;

Begin

  { Initialize the capability mask to include all capabliities }

  SetAllCapability(CapMask);

  { Get the slot map }

  SlotMap := GetSlotMap;
  Mask    := 1;
  Cont    := 0;

  { Refresh the controllers }

  RefreshControllers;
  For Slot := 0 To 31 Do
  Begin
    If (SlotMap And Mask) <> 0 Then
    Begin
      Inc(Cont);
      If Not Cap[Cont].Loaded Then
      Begin

        { Fill in the Cap entry }

        { Get the class map and find out what the controller has }

        Cap[Cont].ClassMap := GetClassMap(Slot);

        { Find out how much of each component the controller has }

        If (Cap[Cont].ClassMap And (1 Shl GRIP_CLASS_BUTTON)) <> 0
         Then Cap[Cont].NumBtns := GetMaxIndex(Slot,GRIP_CLASS_BUTTON) + 1
         Else Cap[Cont].NumBtns := 0;
        If (Cap[Cont].ClassMap And (1 Shl GRIP_CLASS_AXIS)) <> 0
         Then Cap[Cont].NumAxes := GetMaxIndex(Slot,GRIP_CLASS_AXIS) + 1
         Else Cap[Cont].NumAxes := 0;
        If (Cap[Cont].ClassMap And (1 Shl GRIP_CLASS_POV_HAT)) <> 0
         Then Cap[Cont].NumHats := GetMaxIndex(Slot,GRIP_CLASS_POV_HAT) + 1
         Else Cap[Cont].NumHats := 0;
        If (Cap[Cont].ClassMap And (1 Shl GRIP_CLASS_THROTTLE)) <> 0
         Then Cap[Cont].NumThrs := GetMaxIndex(Slot,GRIP_CLASS_THROTTLE) + 1
         Else Cap[Cont].NumThrs := 0;
        If (Cap[Cont].ClassMap And (1 Shl GRIP_CLASS_ANALOG)) <> 0
         Then Cap[Cont].NumAnas := GetMaxIndex(Slot,GRIP_CLASS_ANALOG) + 1
         Else Cap[Cont].NumAnas := 0;

        { Bound the components }

        If Cap[Cont].NumBtns > Max_Digital_Btn  Then Cap[Cont].NumBtns := Max_Digital_Btn;
        If Cap[Cont].NumAxes > Max_Digital_Axis Then Cap[Cont].NumAxes := Max_Digital_Axis;
        If Cap[Cont].NumHats > Max_Digital_Hat  Then Cap[Cont].NumHats := Max_Digital_Hat;
        If Cap[Cont].NumThrs > Max_Digital_Thr  Then Cap[Cont].NumThrs := Max_Digital_Thr;
        If Cap[Cont].NumAnas > Max_Digital_Ana  Then Cap[Cont].NumAnas := Max_Digital_Ana;
        If Cap[Cont].ClassMap <> 0 Then Cap[Cont].Loaded := True;
      End;
      If Cap[Cont].Loaded Then
      Begin

        { Get the raw info }

        ContInfo[Cont].Buttons := GetPackedValues(Slot,GRIP_CLASS_BUTTON,0,31);
        For I := 1 To Cap[Cont].NumAxes Do ContInfo[Cont].Axis[I]     := GetValue(Slot,GRIP_CLASS_AXIS    ,I - 1);
        For I := 1 To Cap[Cont].NumHats Do ContInfo[Cont].Hat[I]      := GetValue(Slot,GRIP_CLASS_POV_HAT ,I - 1);
        For I := 1 To Cap[Cont].NumThrs Do ContInfo[Cont].Throttle[I] := GetValue(Slot,GRIP_CLASS_THROTTLE,I - 1);
        For I := 1 To Cap[Cont].NumAnas Do ContInfo[Cont].Analog[I]   := GetValue(Slot,GRIP_CLASS_ANALOG  ,I - 1);

        { Get the maximum values }

        MaxThr := GetMaxValue(Slot,GRIP_CLASS_THROTTLE);
        MaxAna := GetMaxValue(Slot,GRIP_CLASS_ANALOG);
        For I := 1 To Cap[Cont].NumThrs Do
        Begin
          Cap[Cont].Thr[I].Min := 0;
          Cap[Cont].Thr[I].Mid := MaxThr Div 2;
          Cap[Cont].Thr[I].Max := MaxThr;
        End; // For I
        For I := 1 To Cap[Cont].NumAnas Do
        Begin
          Cap[Cont].Ana[I].Min := 0;
          Cap[Cont].Ana[I].Mid := MaxAna Div 2;
          Cap[Cont].Ana[I].Max := MaxAna;
        End; // For I

        { Process the buttons }

        For I := 1 To Cap[Cont].NumBtns Do
         GetDigitalAny(((ContInfo[Cont].Buttons And (1 Shl (I - 1))) <> 0),
          Cap[Cont].Btn[I],CapMask,Controllers,Info);

        { Process the digital axes }

        For I := 1 To Cap[Cont].NumAxes Do
        Begin

          { Assume that even-numbered axes have to be flipped }

          If (I And 1) <> 0 Then
          Begin
            GetAnalog(0,10,20,ContInfo[Cont].Axis[I],0,Cap[Cont].Axis[I],
             CapMask,Controllers,Info);
          End
          Else
          Begin
            GetAnalog(0,10,20,ContInfo[Cont].Axis[I],0,Cap[Cont].Axis[I],
             CapMask,Controllers,Info);
          End;
        End; { For I }

        { Process the hats }

        For I := 1 To Cap[Cont].NumHats Do
        Begin

          { Assume that even-numbered hat axes have to be flipped }

          If (I And 1) <> 0 Then
          Begin
            GetAnalog(0,10,20,ContInfo[Cont].Hat[I],0,Cap[Cont].Hat[I],
             CapMask,Controllers,Info);
          End
          Else
          Begin
            GetAnalog(0,10,20,ContInfo[Cont].Hat[I],0,Cap[Cont].Hat[I],
             CapMask,Controllers,Info);
          End;
        End; { For I }

        { Process the throttles }

        For I := 1 To Cap[Cont].NumThrs Do
         GetAnalog(0,MaxThr Div 2,MaxThr,ContInfo[Cont].Throttle[I],0,Cap[Cont].Thr[I].Cap,
          CapMask,Controllers,Info);

        { Process the analogs }

        For I := 1 To Cap[Cont].NumAnas Do
         GetAnalog(0,MaxAna Div 2,MaxAna,ContInfo[Cont].Analog[I],0,Cap[Cont].Ana[I].Cap,
          CapMask,Controllers,Info);
      End;
    End
    Else Cap[Cont].Loaded := False;
    Mask := Mask Shl 1;
  End; { For Slot }

  { Remove capabilities that were used }

  AndCapabilities(CapMask,Controllers);
End; { TDigitalInterface.GetControllerInfo }

Procedure TDigitalInterface.CopyTo(Intf: TInterface);
Begin
  Inherited CopyTo(Intf);
  TDigitalInterface(Intf).Cap      := Cap;
  TDigitalInterface(Intf).ContInfo := ContInfo;
  TDigitalInterface(Intf).ConInfo  := ConInfo;
End; // TDigitalInterface.CopyTo

(*
{ ---------------------- }
{ TExternalInterface     }
{ ---------------------- }

Constructor TExternalInterface.Create;
Begin
  Inherited Create('External');
  Wrote := 0;
  FillChar(LPTCap,SizeOf(LPTCap),#0);
  FillChar(JoyCap,SizeOf(JoyCap),#0);
  Deactivate;
End; { TExternalInterface.Create }

Destructor TExternalInterface.Destroy;
Begin
  Deactivate;
  Inherited;
End; { TExternalInterface.Destroy }

Procedure TExternalInterface.Deactivate;
Var
  LPTPort : Word;
  LPT     : Integer;
  OldPort : Byte;

Begin

  { Set all ports to output }

  For LPT := 0 To 2 Do
  Begin
    LPTPort := MemW[0:$408 + LPT * 2];
    If LPTPort <> 0 Then
    Begin
      OldPort := Port[LPTPort + 2];
      Port[LPTPort + 2] := OldPort And $DF;
    End;
  End; { For LPT }
End; { TExternalInterface.Deactivate }

Procedure TExternalInterface.LoadConfiguration(F: TINIFile);
Var LPT: Integer;
Begin
  Inherited LoadConfiguration(F);
  For LPT := 0 To 2 Do
   ReadCapability(F,Name,'L' + IntToStr(LPT),LPTCap[LPT]);
  ReadCapability(F,Name,'J',JoyCap.Cap);
  JoyCap.Min   := F^.ReadInteger(Name,'JMIN',0);
  JoyCap.Mid   := F^.ReadInteger(Name,'JMID',0);
  JoyCap.Max   := F^.ReadInteger(Name,'JMAX',0);
End; { TExternalInterface.LoadConfiguration }

Procedure TExternalInterface.SaveConfiguration(F: TINIFile);
Var LPT: Integer;
Begin
  Inherited SaveConfiguration(F);
  Inherited LoadConfiguration(F);
  For LPT := 0 To 2 Do
   WriteCapability(F,Name,'L' + IntToStr(LPT),LPTCap[LPT]);
  WriteCapability(F,Name,'J',JoyCap.Cap);
  F^.WriteInteger(Name,'JMIN',JoyCap.Min);
  F^.WriteInteger(Name,'JMID',JoyCap.Mid);
  F^.WriteInteger(Name,'JMAX',JoyCap.Max);
End; { TExternalInterface.SaveConfiguration }

Procedure TExternalInterface.Configure;
Var
  Menu : DMenu;
  I    : Integer;
  JoyB : Byte;
  JoyX : LongInt;
  JoyY : LongInt;
  C    : Char;
  MaxJ : Integer;
  OldX : Integer;
  NewX : Integer;
  St1  : String[1];
  St2  : String[1];

Begin
  Inherited Configure;
  Menu := New(DMenu,Create(46,6,
              Black,LightGray,Black,LightGray,Blue,LightGray,
              Red,LightGray,Black,Green,Red,Green,
              DarkGray,LightGray,DarkGray,Green,
              SingleBorder,' External port configuration ',
              ' <Enter> selects, <Esc> exits ',True));
  Menu.AddSelection(1, 1,22,3,False,((JoyCap.Cap.A And imPaddleAxis) <> 0),'  Calibrate paddles');
  Menu.AddSelection(1, 4,22,3,False,((JoyCap.Cap.A And imPaddleAxis) =  0),'  None');
  For I := 0 To 2 Do
  Begin
    If MemW[0:$408 + I * 2] <> 0 Then
     Menu.AddSelection(23,I + 1,22,6,False,UsedCapability(LPTCap[I]),'  LPT' + IntToStr(I + 1));
  End; { For I }
  Menu.AddSelection(23, 4,22,3,False,Not (UsedCapability(LPTCap[0]) Or
                                           UsedCapability(LPTCap[1]) Or
                                           UsedCapability(LPTCap[2])),'  None');
  Repeat
    Menu^.Execute;

    { If a port is enabled, set the capabliity, otherwise clear it }

    Case Menu.CurSel Of
      1: Begin
           JoyCap.Cap.A := imPaddleAxis + imCBS + imJoyBtn + imDrivingBtn;
           StayPopup4('Rotate paddle 1 to its extremes several times and press any key,',
                      '                    or hit <Esc> to exit:',
                      '',
                      '  [                                                          ]');
           MaxJ := 0;
           OldX := (_Win^.X2 - _Win^.X1) Div 2;
           St1  := ' ';
           St2  := '*';
           Repeat
             JoyStatus(1,JoyB,JoyX,JoyY);

             If JoyY >= 0 Then
             Begin
               If (JoyY > MaxJ) And (JoyY <= MaxJoyCount) Then MaxJ := JoyY;
               If JoyY <= MaxJ Then
               Begin
                 Screen.FastWrite(Lo(Screen.WindMin) + OldX + 6,Hi(Screen.WindMin) + 4,Screen.TextAttr,St1);
                 NewX := Trunc((1 - (JoyY / MaxJ)) * ((_Win^.X2 - _Win^.X1) - 10));
                 Screen.FastWrite(Lo(Screen.WindMin) + NewX + 6,Hi(Screen.WindMin) + 4,Screen.TextAttr,St2);
                 OldX := NewX;
               End;
             End;
           Until KeyPressed;
           C := ReadKey;
           Dispose(_Win,Done);
           If C <> #27 Then
           Begin
             JoyCap.Max := MaxJ;
             JoyCap.Min := 0;
             JoyCap.Mid := MaxJ Div 2;
           End;
         End;
      2: JoyCap.Cap.A := imCBS + imJoyBtn + imDrivingBtn;
      3: Begin
           LPTCap[0].A := imPaddleBtn +
                          imJoy1X + imJoy1Y + imJoy2X + imJoy2Y +
                          imDriving;
           LPTCap[1].A := 0;
           LPTCap[2].A := 0;
           LPTCap[0].B := imKeyboard;
           LPTCap[1].B := 0;
           LPTCap[2].B := 0;
         End;
      4: Begin
           LPTCap[0].A := 0;
           LPTCap[1].A := imPaddleBtn +
                          imJoy1X + imJoy1Y + imJoy2X + imJoy2Y +
                          imDriving;
           LPTCap[2].A := 0;
           LPTCap[0].B := 0;
           LPTCap[1].B := imKeyboard;
           LPTCap[2].B := 0;
         End;
      5: Begin
           LPTCap[0].A := 0;
           LPTCap[1].A := 0;
           LPTCap[2].A := imPaddleBtn +
                          imJoy1X + imJoy1Y + imJoy2X + imJoy2Y +
                          imDriving;
           LPTCap[0].B := 0;
           LPTCap[1].B := 0;
           LPTCap[2].B := imKeyboard;
         End;
      6: Begin
           LPTCap[0].A := 0;
           LPTCap[1].A := 0;
           LPTCap[2].A := 0;
           LPTCap[0].B := 0;
           LPTCap[1].B := 0;
           LPTCap[2].B := 0;
         End;
    End; { Case }
  Until Menu.CurSel = 0;
  Dispose(Menu,Done);
End; { TExternalInterface.Configure }

Procedure TExternalInterface.GetControllerInfo(Var Controllers: TCapability;
                                           Var Info: TAtariControls);
Var
  CapMask : TCapability;
  Pad1X   : LongInt;
  Pad2X   : LongInt;
  Pad3X   : LongInt;
  Pad4X   : LongInt;
  Pad1Cap : TCapability;
  Pad2Cap : TCapability;
  Pad3Cap : TCapability;
  Pad4Cap : TCapability;
  Joy1Cap : TCapability;
  Joy2Cap : TCapability;
  JoyB    : Byte;
  LPT     : Integer;
  LPTPort : Word;
  OldPort : Byte;
  B       : Byte;
  OldCont : TCapability;

Begin

  { Copy the initial controller list }

  OldCont := Controllers;

  { Initialize the capability mask to include all capabliities }

  SetAllCapability(CapMask);

  { Poll the joystick port }

  If ((Controllers.A And imPaddleAxis) <> 0) Or
     ((Controllers.B And imKeyboard)   <> 0) Then
  Begin
    JoyStatus(1,JoyB,Pad2X,Pad1X);
    JoyStatus(2,JoyB,Pad4X,Pad3X);
  End
  Else
  Begin
    JoyStatusButtons(JoyB);
    Pad1X := 0;
    Pad2X := 0;
    Pad3X := 0;
    Pad4X := 0;
  End;

  { Process analog portion of joystick port (requires calibration) }

  If (JoyCap.Cap.A And imPaddleAxis) <> 0 Then
  Begin
    Pad1Cap.A := JoyCap.Cap.A And imPaddle1Axis;
    Pad2Cap.A := JoyCap.Cap.A And imPaddle2Axis;
    Pad3Cap.A := JoyCap.Cap.A And imPaddle3Axis;
    Pad4Cap.A := JoyCap.Cap.A And imPaddle4Axis;
    Pad1Cap.B := JoyCap.Cap.B;
    Pad2Cap.B := JoyCap.Cap.B;
    Pad3Cap.B := JoyCap.Cap.B;
    Pad4Cap.B := JoyCap.Cap.B;

    If Pad1X <= JoyCap.Max Then
     GetAnalog(JoyCap.Min,JoyCap.Mid,JoyCap.Max,JoyCap.Max - Pad1X,0,Pad1Cap,CapMask,Controllers,Info);
    If Pad2X <= JoyCap.Max Then
     GetAnalog(JoyCap.Min,JoyCap.Mid,JoyCap.Max,JoyCap.Max - Pad2X,0,Pad2Cap,CapMask,Controllers,Info);
    If Pad3X <= JoyCap.Max Then
     GetAnalog(JoyCap.Min,JoyCap.Mid,JoyCap.Max,JoyCap.Max - Pad3X,0,Pad3Cap,CapMask,Controllers,Info);
    If Pad4X <= JoyCap.Max Then
     GetAnalog(JoyCap.Min,JoyCap.Mid,JoyCap.Max,JoyCap.Max - Pad4X,0,Pad4Cap,CapMask,Controllers,Info);
  End;

  { Process digital portion of joystick port }

  Joy1Cap.A := JoyCap.Cap.A And (ifJoy1Btn Or ifDriving1Btn);
  Joy2Cap.A := JoyCap.Cap.A And (ifJoy2Btn Or ifDriving2Btn);
  Joy1Cap.B := JoyCap.Cap.B;
  Joy2Cap.B := JoyCap.Cap.B;
  GetDigitalAny((JoyB And 1) <> 0,Joy1Cap,CapMask,Controllers,Info);
  GetDigitalAny((JoyB And 2) <> 0,Joy2Cap,CapMask,Controllers,Info);

  Joy1Cap.A := JoyCap.Cap.A And ifCBSBtn1;
  Joy2Cap.A := JoyCap.Cap.A And ifCBSBtn2;
  Joy1Cap.B := JoyCap.Cap.B;
  Joy2Cap.B := JoyCap.Cap.B;

  GetDigitalAny(Pad1X < Round(MaxJoyCount * 0.02),Joy1Cap,CapMask,Controllers,Info);
  GetDigitalAny(Pad2X < Round(MaxJoyCount * 0.02),Joy2Cap,CapMask,Controllers,Info);

  { Process parallel port }

  For LPT := 0 To 2 Do
  Begin
    If UsedCapability(LPTCap[LPT]) Then
    Begin
      LPTPort := MemW[0:$408 + LPT * 2];
      If LPTPort <> 0 Then
      Begin
        If ReadMask <> 0 Then
        Begin

          { Set to input }

          OldPort := Port[LPTPort + 2];

          If (OldPort And $20) = 0 Then Port[LPTPort + 2] := OldPort Or $20;

          { Read the data }

          B := Port[LPTPort];
          Info.PortA := B;

          { Set the port back to whatever it was }

          Port[LPTPort + 2] := OldPort;

          { Write any data that had been written }

          If (OldPort And $20) = 0 Then Port[LPTPort] := Wrote;

          { Kill all remaining capabilities }

          FillChar(CapMask,SizeOf(CapMask),#0);
        End;
      End;
    End;
  End; { For LPT }

  { Special processing for Indy 500 controllers }

  If (OldCont.A And imDriving1Axis) <> 0 Then
  Begin
    For B := 0 To 3 Do
     If Indy500Trans1[B] = (Info.PortA Or $F) Then Info.Driv[1] := B * 4;
  End;
  If (OldCont.A And imDriving2Axis) <> 0 Then
  Begin
    For B := 0 To 3 Do
     If Indy500Trans2[B] = (Info.PortA Or $F0) Then Info.Driv[2] := B * 4;
  End;

  If ((OldCont.B And imKB1) <> 0) And (ReadMask <> $FF) Then
  Begin
    If (Wrote And $10) = 0 Then
    Begin
      If Pad1X > MaxJoyCount Then Info.KB := Info.KB Or $00000010;
      If Pad2X > MaxJoyCount Then Info.KB := Info.KB Or $00001000;
      If (JoyB And 1) <> 0   Then Info.KB := Info.KB Or $00100000;
    End;
    If (Wrote And $20) = 0 Then
    Begin
      If Pad1X > MaxJoyCount Then Info.KB := Info.KB Or $00000020;
      If Pad2X > MaxJoyCount Then Info.KB := Info.KB Or $00002000;
      If (JoyB And 1) <> 0   Then Info.KB := Info.KB Or $00200000;
    End;
    If (Wrote And $40) = 0 Then
    Begin
      If Pad1X > MaxJoyCount Then Info.KB := Info.KB Or $00000040;
      If Pad2X > MaxJoyCount Then Info.KB := Info.KB Or $00004000;
      If (JoyB And 1) <> 0   Then Info.KB := Info.KB Or $00400000;
    End;
    If (Wrote And $80) = 0 Then
    Begin
      If Pad1X > MaxJoyCount Then Info.KB := Info.KB Or $00000080;
      If Pad2X > MaxJoyCount Then Info.KB := Info.KB Or $00008000;
      If (JoyB And 1) <> 0   Then Info.KB := Info.KB Or $00800000;
    End;
    CapMask.B := CapMask.B And imNoKB1;
  End;

  If ((OldCont.B And imKB2) <> 0) And (ReadMask <> $FF) Then
  Begin
    If (Wrote And $1) = 0 Then
    Begin
      If Pad3X > MaxJoyCount Then Info.KB := Info.KB Or $00000001;
      If Pad4X > MaxJoyCount Then Info.KB := Info.KB Or $00000100;
      If (JoyB And 2) <> 0   Then Info.KB := Info.KB Or $00010000;
    End;
    If (Wrote And $2) = 0 Then
    Begin
      If Pad3X > MaxJoyCount Then Info.KB := Info.KB Or $00000002;
      If Pad4X > MaxJoyCount Then Info.KB := Info.KB Or $00000200;
      If (JoyB And 2) <> 0   Then Info.KB := Info.KB Or $00020000;
    End;
    If (Wrote And $4) = 0 Then
    Begin
      If Pad3X > MaxJoyCount Then Info.KB := Info.KB Or $00000004;
      If Pad4X > MaxJoyCount Then Info.KB := Info.KB Or $00000400;
      If (JoyB And 2) <> 0   Then Info.KB := Info.KB Or $00040000;
    End;
    If (Wrote And $8) = 0 Then
    Begin
      If Pad3X > MaxJoyCount Then Info.KB := Info.KB Or $00000008;
      If Pad4X > MaxJoyCount Then Info.KB := Info.KB Or $00000800;
      If (JoyB And 2) <> 0   Then Info.KB := Info.KB Or $00080000;
    End;
    CapMask.B := CapMask.B And imNoKB2;
  End;

  { Remove capabilities that were used }

  AndCapabilities(CapMask,Controllers);
End; { TExternalInterface.GetControllerInfo }

Procedure TExternalInterface.WriteToPort(B: Byte);
Var
  LPTPort : Word;
  LPT     : Integer;
  OldPort : Byte;

Begin
  For LPT := 0 To 2 Do
  Begin
    If UsedCapability(LPTCap[LPT]) Then
    Begin
      LPTPort := MemW[0:$408 + LPT * 2];
      If LPTPort <> 0 Then
      Begin
        If ReadMask <> $FF Then
        Begin

          { Set to output }

          OldPort := Port[LPTPort + 2];
          Port[LPTPort + 2] := OldPort And $DF;

          { Write the data }

          Port[LPTPort] := B And (ReadMask Xor $FF);
          Wrote         := B And (ReadMask Xor $FF);
        End;
      End;
    End;
  End; { For LPT }
End; { TExternalInterface.WriteToPort }
*)

Initialization
  InterfaceHandler := TInterfaceHandler.Create;
Finalization
  InterfaceHandler.Free;
End.

// ----------------------------------------------------------------------
// PCAE and PCAEWin - PC Atari Emulator - Atari 2600 emulator
// Copyright (C) 2000 John Dullea
//
//  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., 675 Mass Ave, Cambridge, MA 02139, USA.
// ----------------------------------------------------------------------
