{
    *********************************************************************
    Version: 2003.02.02
    Copyright (C) 1998 Gertjan Schouten

    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.
    *********************************************************************

    VESA unit for Free Pascal

    important:
    this unit requires VESA compatible videocard or VESA tsr (univbe)

    useage:
    SV_Init to check if there is VESA support
    SV_SetMode to set the desired graphics mode
    SV_Segment to get the segment for your drawing routines

    To set a linear mode you need to add vmLinear to the mode number.
    Example:
    Mode := vm640_480_256;
    if SV_Vbe2 then
       Mode := Mode or vmLinear;
    SV_SetMode(Mode);
}

unit VesaVBE;

interface

uses go32;

{$ifndef go32v2}
{$error Only works with FPC-Go32v2 target}
{$endif}

const
   vbe_supported        = $004F;
   vbe_success          = $004F;
   vbe_failed           = $014F;
   vbe_hardware_failure = $024F;
   vbe_invalid          = $034F;

{  4 bit modes, 16 possible colors  }
   um640_350_16 = $150; // univbe mode
   um640_400_16 = $151; // unvibe mode
   um640_480_16 = $152; // univbe mode
   vm800_600_16 = $102;
   vm1024_768_16 = $104;
   vm1280_1024_16 = $106;
   vm1600_1200_16 = $123;

{  8 bit modes, 256 possible colors  }
   um320_200_256 = $153; // univbe mode
   um320_240_256 = $154; // univbe mode
   um320_400_256 = $155; // univbe mode
   um360_200_256 = $156; // univbe mode
   um360_240_256 = $157; // univbe mode
   um350_400_256 = $158; // univbe mode
   vm640_350_256 = $11C;
   vm640_400_256 = $100;
   vm640_480_256 = $101;
   vm800_600_256 = $103;
   vm1024_768_256 = $105;
   vm1280_1024_256 = $107;
   vm1600_1200_256 = $124;

{  15 bit modes, 32K possible colors  }
   vm320_200_32K = $10D;
   um320_240_32K = $12D; // univbe mode
   um320_400_32K = $12E; // univbe mode
   um360_200_32K = $12F; // univbe mode
   um360_240_32K = $130; // univbe mode
   um360_400_32K = $131; // univbe mode
   vm640_480_32K = $110;
   vm800_600_32K = $113;
   vm1024_768_32K = $116;
   vm1280_1024_32K = $119;
   vm1600_1200_32K = $125;

{  16 bit modes, 64K possible colors  }
   vm320_200_64K = $10E;
   um320_240_64K = $132; // univbe mode
   um320_400_64K = $133; // univbe mode
   um360_200_64K = $134; // univbe mode
   um360_240_64K = $135; // univbe mode
   um360_400_64K = $136; // univbe mode
   vm640_350_64K = $11F;
   vm640_480_64K = $111;
   vm800_600_64K = $114;
   vm1024_768_64K = $117;
   vm1280_1024_64K = $11A;
   vm1600_1200_64K = $126;

{  24 bit modes, 16M possible colors   }
   vm320_200_16M = $10F;
   um320_240_16M = $137; // univbe mode
   um320_400_16M = $138; // univbe mode
   um640_350_16M = $121; // univbe mode
   um640_400_16M = $122; // univbe mode
   vm640_480_16M = $112;
   vm800_600_16M = $115;
   vm1024_768_16M = $118;
   vm1280_1024_16M = $11B;
   vm1600_1200_16M = $127;

{  32 bit modes, 16M possible colors   }
   um640_350_16MA = $142; // univbe mode
   um640_400_16MA = $143; // univbe mode
   vm640_480_16MA = $128;
   vm800_600_16MA = $129;
   vm1024_768_16MA = $12A;
   vm1200_1024_16MA = $12B;
   vm1600_1200_16MA = 0;

{  add vmLinear to one of the above modes numbers
   to set a linear frame buffer mode.   }

   vmLinear = $4000;

type
   {  VBE controller info record  }

   TVBEInfo = packed record
      Signature: array[0..3] of char;
      Version: word;
      OemStringPtr: pointer;
      Capabilities: longint;
      VideoModePtr: longint;
      TotalMemory: word;
      OemSoftwareRev: word;
      OemVendorNamePtr: longint;
      OemProductNamePtr: longint;
      OemProductRevPtr: longint;
      Reserved: array[0..221] of byte;
      OemData: array[0..255] of byte;
   end ;

   {  Mode info record required for GetModeInfo  }

   TModeInfo = packed record
      ModeAttributes: word;
      WinAAttributes: byte;
      WinBAttributes: byte;
      WinGranularity: word;
      WinSize: word;
      WinASegment: word;
      WinBSegment: word;
      WinFuncPtr: longint;
      BytesPerScanLine: word;
      XResolution: word;
      YResolution: word;
      XCharSize: byte;
      YCharSize: byte;
      NumberOfPlanes: byte;
      BitsPerPixel: byte;
      NumberOfBanks: byte;
      MemoryModel: byte;
      BankSize: byte;
      NumberOfImagePages: byte;
      Reserved: byte;
      RedMaskSize: byte;
      RedFieldPosition: byte;
      GreenMaskSize: byte;
      GreenFieldPosition: byte;
      BlueMaskSize: byte;
      BlueFieldPosition: byte;
      RsvdMaskSize: byte;
      RsvdFieldPosition: byte;
      DirectColorModeInfo: byte;
      PhysBasePtr: longint;
      OffScreenMemOffset: longint;
      OffScreenMemSize: word;
      Reserved2: array[0..205] of byte;
   end ;

   TEnumModesCallBack = procedure(Mode: longint; const ModeInfo: TModeInfo);

   TSetDisplayStart = function(X, Y: longint; WaitVrt: Boolean): longint;

   TSetDisplayOffset = function(Offset: longint; WaitVrt: Boolean): longint;

   TSwitchBankFunc = procedure(Window, Bank: longint);

var
{  VBE controller information   }

   VBEInfo: TVBEInfo;

{  Vendor name string  }

   SV_VendorName: string;

{  Product name string  }

   SV_ProductName: string;

{  Product revision string  }

   SV_ProductRev: string;

{  Version number of VESA implementation   }

   SV_Version: longint;

{  Visible number pixels accross  }

   SV_XResolution : longint;

{  Visible number of scan lines   }

   SV_YResolution : longint;

{  Logical number of ScanLines (equals: SV_MemSize div SV_BytesPerLine)   }

   SV_ScanLineCount : longint;

{  Logical number of bytes accross   }

   SV_BytesPerLine : longint;

{  Logical number of pixels accross   }

   SV_PixelsPerLine : longint;

{  Significant number of color bits   }

   SV_BitsPerPixel : longint;

{  Maximum number of colors   }

   SV_ColorCount: longint;

{  Size of one "page" (logical scanline length * YResolution)   }

   SV_PageSize : longint;                  // BytesPerLine * YResolution

{  Available video memory   }

   SV_MemSize: longint;                    // size of video Memory (in bytes)

{  VESA 2 compliant (enables linear frame buffer)  }

   SV_Vbe2: boolean;

{  Selector for frame buffer   }

   SV_Segment: word;

{  Selector for window 0  (applicable only to banked modes)  }

   SV_Seg0: word;

{  Selector for window 2  (applicable only to banked modes)  }

   SV_Seg1: word;

{  Offset of frame buffer   }

   SV_Offset: longint;

{  linear address of video Memory  }

   SV_Handle : longint;

{  Because some VBE functions have two implementation ( real mode /
   protected mode ). Applications should never call these directly
   but use the function pointers presented here.   }

{  SV_SwitchBank (a call to SV_SwitchBank assures the invocation
   of the correct function for bank switching )  }

   SV_SwitchBank: TSwitchBankFunc;

{  SV_SetDisplayStart  }

   SV_SetDisplayStart: TSetDisplayStart;

{  SV_SetDisplayOffset  }

   SV_SetDisplayOffset: TSetDisplayOffset;

{  SV_Init checks if a VESA compatible video system exists   }

function SV_Init: longint;

{  SV_Done releases any resources used by this unit  }

procedure SV_Done;

{  SV_ErrorToString returns an "understandable" error string for the given
   VESA errorcode   }

function SV_ErrorToString(Error: longint): string;

{  SV_SetMode sets the current display mode to Mode which is a VESA defined
   mode or a mode obtained with SV_EnumModes    }

function SV_SetMode(mode: longint): longint;

{  SV_GetCurrentMode returns the active video mode  }

function SV_GetCurrentMode: longint;

{  SV_EnumModes calls for each available mode the given TEnumModesCallBack
   callback function, this allows your application to "register"
   the CallBack function has the following form:
   procedure(Mode: longint; const ModeInfo: TModeInfo);
}

function SV_EnumModes(CallBack: TEnumModesCallBack): longint;

{  SV_GetModeInfo returns (in ModeInfo) information about the supplied
   Mode, you can use this function to see if a certain
   mode is available   }

function SV_GetModeInfo(Mode: longint; var ModeInfo: TModeInfo): longint;

{  SV_SetDACWidth sets the DAC width to Width,
   this function works only with programable DACS  }

function SV_SetDACWidth(Width: byte): longint;

{  SV_SetDisplayStart sets the first displayed pixel (that is the top-left
   corner of the screen) to coordinates (X, Y).
   if WaitVrt is true the adapter waits for the vertical retrace.
   You should not call these function directly, instead call SV_SetDisplayStart
   which always maps to the appropriate function.  }

function SV_SetDisplayStartRM(X, Y: longint; WaitVrt: Boolean): longint;
function SV_SetDisplayStartPM(X, Y: longint; WaitVrt: Boolean): longint;

{  SV_SetDisplayOffset sets the first displayed pixel to Offset  }

function SV_SetDisplayOffsetRM(Offset: longint; WaitVrt: Boolean): longint;
function SV_SetDisplayOffsetPM(Offset: longint; WaitVrt: Boolean): longint;

{  SV_SetPalette updates (from Index) Count entries of the global palette,
   Palette is an array of longint values representing RGB values }

function SV_SetPalettePM(const Palette {: array of longint}; Index, Count: longint; WaitVrt: boolean): longint;
function SV_SetPaletteRM(var Palette; Index, Count: longint; WaitVrt: boolean): longint;

{  SV_SetVisualPage sets the display start to Page * PageSize,
   where PageSize is equal to SV_BytesPerLine * SV_YResolution  }

function SV_SetVisualPage(Page: longint; WaitVrt: boolean): longint;

{   SV_SwitchBank** sets the position of the specified drawing window in the
    frame buffer, bank is the bank number in 64K units.
    You should not call these functions directly, instead call SV_SwitchBank
    which always maps to the appropriate SV_SwitchBank** function.  }

procedure SV_SwitchBankPM(Window, Bank: longint);
procedure SV_SwitchBankRM(Window, Bank: longint);

{   SV_SetPixelsPerLine sets the logical number of pixels per scanline   }

function SV_SetPixelsPerLine(Value: longint): longint;

implementation

type
   WordArray = array[0..65535] of word;

var
   vbReady: boolean;
   vbDosBuffer: longint;

   {  protected mode interface  }
   vbPMI: pointer;                       // Protected mode interface
   vbPMIsize: longint;                   // Size of pmode interface
   vbPMFSeg: word;                       // Selector required for PMode functions
   vbPMISetWindow: pointer;             // Pointer to setWindow function
   vbPMISetDisplayStart: pointer;       // Pointer to setDisplayStart function
   vbPMISetPalette: pointer;            // Pointer to setPalette function

   {  for banked modes  }
   vbWinSize: longint;
   vbWinLoMask: longint;
   vbGranshift: byte;
   vbGranularity: longint;
   vbGranular: longint;
   vbWinShift: byte;
   vbSwitchIPCS: longint;

function SV_Init: longint;
var
   Regs: Registers;
   Seg: word;
   Ofs: longint;
   s: pchar;
   P: array[0..255] of char;
begin
if not vbReady then begin
   vbDosBuffer := Global_Dos_Alloc(1024);
   s := 'VBE2';
   move(s^, VBEInfo.Signature, 4);
   DosMemPut(vbDosBuffer shr 16, vbDosBuffer and 65535, VBEInfo, 4);
   Regs.es := vbDosBuffer shr 16;
   Regs.di := vbDosBuffer and 65535;
   Regs.ax := $4F00;
   RealIntr($10, Regs);
   DosmemGet(vbDosBuffer shr 16, vbDosBuffer and 65535, VBEInfo, 512);
   SV_Init := Regs.ax;
   vbReady := (Regs.ax = vbe_success);
   if vbReady then begin
      SV_VBE2 := hi(VBEInfo.Version) >= 2;
      SV_Version := VBEInfo.Version;
      SV_Seg0 := Allocate_Ldt_Descriptors(1);
      SV_Seg1 := Allocate_Ldt_Descriptors(1);
      SV_Segment := SV_Seg0;
      vbPMFSeg := Allocate_Ldt_Descriptors(1);
      SV_MemSize := VBEInfo.TotalMemory * 65536;
      if VBEInfo.OemVendorNamePtr <> 0 then begin
         seg_move(Segment_To_Descriptor(VBEInfo.OemVendorNamePtr shr 16),
               VBEInfo.OemVendorNamePtr and $FFFF,
            get_ds, longint(@P), 256);
         SV_VendorName := StrPas(@P);
         end ;
      if VBEInfo.OemProductNamePtr <> 0 then begin
         seg_move(Segment_To_Descriptor(VBEInfo.OemProductNamePtr shr 16),
               VBEInfo.OemProductNamePtr and $FFFF,
            get_ds, longint(@P), 256);
         SV_ProductName := StrPas(@P);
         end ;
      if VBEInfo.OemProductNamePtr <> 0 then begin
         seg_move(Segment_To_Descriptor(VBEInfo.OemProductRevPtr shr 16),
               VBEInfo.OemProductRevPtr and $FFFF,
            get_ds, longint(@P), 256);
         SV_ProductRev := StrPas(@P);
         end ;
      end ;
   end
else SV_Init := vbe_success;
end ;

   procedure Free_PMI;
   begin
   if vbPMI <> nil then
      Freemem(vbPMI, vbPMIsize);
   vbPMI := nil;
   end ;

procedure SV_Done;
begin
if vbReady then begin
   Global_Dos_Free(vbDosBuffer);
   Free_Ldt_Descriptor(SV_Seg0);
   Free_Ldt_Descriptor(SV_Seg1);
   Free_PMI;
   Free_Ldt_Descriptor(vbPMFSeg);
   vbReady := false;
   end ;
end ;

function SV_ErrorToString(Error: longint): string;
begin
SV_ErrorToString := '';
If lo(error) <> $4F then
   SV_ErrorToString := 'function is not supported'
else begin
      case hi(error) of
         1: SV_ErrorToString := 'function call failed';
         2: SV_ErrorToString := 'function is not supported in the current hardware configuration';
         3: SV_ErrorToString := 'function call invalid in current video mode';
      end ;
   end ;
end ;

function SV_SetMode(Mode: longint): longint;
var
   Regs: Registers;
   ModeInfo: TModeInfo;
   Ofs: longint;
begin
if ((vbReady) or (SV_Init = vbe_Success)) and
   (SV_GetModeInfo(Mode and $3FFF, ModeInfo) = vbe_Success) then begin
{   if (hi(VBEInfo.Version) >= 2) then mode := mode or vmLinear;  }
   if (Mode and vmLinear = 0) and (ModeInfo.WinAAttributes and 6 <> 6) then begin
      SV_SetMode := vbe_failed;
      exit;
      end ;
   Regs.ax := $4F02;
   Regs.bx := mode;
   RealIntr($10, Regs);
   SV_SetMode := Regs.ax;
   if Regs.ax = VBE_Success then begin
      SV_XResolution := Modeinfo.XResolution;
      SV_YResolution := Modeinfo.YResolution;
      SV_PixelsPerLine := Modeinfo.XResolution;
      SV_BytesPerLine := Modeinfo.BytesPerScanLine;
      SV_ScanLineCount := SV_MemSize div SV_BytesPerLine;
      SV_BitsPerPixel := ModeInfo.BitsPerPixel;
      SV_PageSize := SV_BytesPerLine * SV_YResolution;
      SV_ColorCount := 1 shl SV_BitsPerPixel;
      if (Mode and vmLinear = vmLinear) then begin
         SV_Handle := Get_Linear_Addr(ModeInfo.PhysBasePtr, VBEInfo.TotalMemory shl 16);
         Set_Segment_Base_Address(SV_Seg0, SV_Handle);
         Set_Segment_Base_Address(SV_Seg1, SV_Handle);
         Set_Segment_Limit(SV_Seg0, SV_MemSize - 1);
         Set_Segment_Limit(SV_Seg1, SV_MemSize - 1);
         end
      else begin
         {   from ibm.ppi ( FPC run-time-library ) }
         vbWinSize := ModeInfo.Winsize * 1024;
         vbWinLoMask := vbWinSize - 1;
         case ModeInfo.WinSize of
            64 : vbWinShift := 16;      { x div 65536 = x shr 16 }
            32 : vbWinShift := 15;      { x div 32768 = x shr 15 }
            16 : vbWinShift := 14;      { ... }
             8 : vbWinShift := 13;
             4 : vbWinShift := 12;
             2 : vbWinShift := 11;
             1 : vbWinShift := 10;
         end;
         vbGranularity := ModeInfo.WinGranularity;
         vbGranular := ModeInfo.WinSize div vbGranularity;
         case vbGranular of
            32 : vbGranShift:=5;
            16 : vbGranShift:=4;
             8 : vbGranShift:=3;
             4 : vbGranShift:=2;
             2 : vbGranShift:=1;
             1 : vbGranShift:=0;
         end;
         vbSwitchIPCS := ModeInfo.WinFuncPtr;
         SV_Handle := ModeInfo.WinASegment shl 4;
         Set_Segment_Base_Address(SV_Seg0, SV_Handle);
         Set_Segment_Limit(SV_Seg0, $FFFF);
         Set_Segment_Base_Address(SV_Seg1, ModeInfo.WinBSegment shl 4);
         Set_Segment_Limit(SV_Seg1, $FFFF);
         end ;
      if hi(VBEInfo.Version) >= 2 then begin
         // obtain protected mode interface
         Regs.ax := $4F0A;
         Regs.bl := 0;
         RealIntr($10, Regs);
         if Regs.ax = vbe_success then begin
            Free_PMI;
            vbPMISize := Regs.cx;
            GetMem(vbPMI, vbPMIsize);
            DosMemGet(Regs.es, Regs.di, vbPMI^, vbPMISize);
            Ofs := Get_Segment_Base_Address(DosMemSelector) + Regs.es * 16 + Regs.di;
            Set_Segment_Base_Address(vbPMFSeg, Ofs);
            Set_Segment_Limit(vbPMFSeg, Get_Page_Size); // 1 page will do
            vbPMISetWindow := vbPMI + WordArray(vbPMI^)[0];
            vbPMISetDisplayStart := vbPMI + WordArray(vbPMI^)[1];
            vbPMISetPalette := vbPMI + WordArray(vbPMI^)[2];
            SV_SetDisplayStart := @SV_SetDisplayStartPM;
            SV_SetDisplayOffset := @SV_SetDisplayOffsetPM;
{//}        SV_SwitchBank := @SV_SwitchBankPM;
//          SV_SwitchBank := @SV_SwitchBankRM;
            end ;
         end
      else begin
         SV_SetDisplayStart := @SV_SetDisplayStartRM;
         SV_SetDisplayOffset := @SV_SetDisplayOffsetRM;
         SV_SwitchBank := @SV_SwitchBankRM;
         end ;
      end ;
   end
else SV_SetMode := vbe_Failed;
end ;

function SV_SetDisplayStartRM(x, y: longint; WaitVrt: Boolean): longint; assembler;
asm
        movl    $0x4F07,%eax
        movb    WaitVrt, %bl
        shlb    $7, %bl
        movw    x, %cx
        movw    y, %dx
        int     $0x10
end ;

function SV_SetDisplayOffsetPM(Offset: longint; WaitVrt: Boolean): longint; assembler; [alias: 'SV_SetDisplayOffsetPM'];
asm
        pushw   %es
        movw    VBPMFSEG, %ax
        movw    %ax, %es
        movl    $0x4F07, %eax
        movl    Offset, %ecx
        shrl    $2, %ecx
        movl    %ecx, %edx
        shrl    $16, %edx
        movb    WaitVrt, %bl
        shlb    $7, %bl
        call    *VBPMISETDISPLAYSTART
        popw    %es
end ;

function SV_SetDisplayStartPM(x, y: longint; WaitVrt: Boolean): longint;
begin
SV_SetDisplayStartPM := SV_SetDisplayOffsetPM((SV_BytesPerLine * y) +
  (x * SV_BytesPerLine div SV_PixelsPerLine), WaitVrt);
end ;

function SV_SetDisplayOffsetRM(offset: longint; WaitVrt: Boolean): longint;
begin
SV_SetDisplayOffsetRM := SV_SetDisplayStartRM(Offset mod SV_BytesPerLine, Offset div SV_BytesPerLine, WaitVrt);
end ;

function SV_SetVisualPage(Page: longint; WaitVrt: boolean): longint;
begin
SV_SetVisualPage := SV_SetDisplayOffset(SV_PageSize * Page, WaitVrt);
end ;

function SV_SetPaletteRM(var Palette; Index, Count: longint; WaitVrt: boolean): longint;
var
   Regs: Registers;
begin
DosMemPut(vbDosBuffer shr 4, vbDosBuffer and 15, Palette, Count * 4);
Regs.es := vbDosBuffer shr 4;
Regs.di := vbDosBuffer and 15;
Regs.ecx := Count;
Regs.edx := Index;
Regs.bl := 0;
if WaitVrt then
   Regs.bl := Regs.bl or $80;
Regs.ax := $4F09;
RealIntr($10, Regs);
SV_SetPaletteRM := Regs.ax;
end ;

function SV_SetPalettePM(const Palette {: array of longint}; Index, Count: longint; WaitVrt: boolean): longint; assembler;
asm
        movl    Palette, %eax
        movl    %eax, %edi
        movl    VBPMISETPALETTE, %eax
        movl    %eax, %esi
        movl    Index, %edx
        movl    Count, %ecx
        xorl    %ebx, %ebx
        movb    WaitVrt, %bl
        shlb    $7, %bl
        pushw   %ds
        movw    VBPMFSEG, %ax
        movw    %ax, %ds
        call    *%esi
        popw    %ds
end ;

procedure SV_SwitchBankPM(Window, Bank: longint); assembler;
asm
        movl    Bank, %edx
        movzbl  VBGRANSHIFT, %ecx
        shll    %cl, %edx
        movl    Window, %ebx
        movl    VBPMISETWINDOW, %eax
        pushw   %ds
        movw    VBPMFSEG, %cx
        movw    %cx, %ds
        call    *%eax
        popw    %ds
end;

procedure SV_SwitchBankRM(Window, Bank: longint); assembler;
var Regs: Registers;
asm
        leal    Regs, %edi
        movl    Bank, %eax
        movzbl  VBGRANSHIFT, %ecx
        shll    %cl, %eax
        movl    %eax, 20(%edi)      // Edx
        movl    VBSWITCHIPCS, %eax
        movl    %eax, 42(%edi)      // IP:CS
        xorl    %ecx, %ecx
        movl    %ecx, 46(%edi)      // SS, SP
        movl    Window, %ecx
        movl    %ecx, 16(%edi)      // EBX
        movw    $0x0301, %ax
        int     $0x31               // Call Real mode proc
end;

function SV_SetPixelsPerLine(Value: longint): longint;
var Regs: Registers;
begin
Regs.ax := $4F06;
Regs.bl := 0;
Regs.cx := value;
RealIntr($10, Regs);
SV_SetPixelsPerLine := Regs.Ax;
If (Regs.Ax = VBE_SUCCESS) then begin
   SV_BytesPerLine := Regs.bx;
   SV_PixelsPerLine := Regs.cx;
   SV_ScanLineCount := (SV_MemSize div SV_BytesPerLine);
   SV_PageSize := (SV_BytesPerLine * SV_YResolution);
   end ;
end ;

function SV_GetCurrentMode: longint; assembler;
asm
        movl    $0x4f03, %eax
        int     $0x10
        movzwl  %bx, %eax
end ;

function SV_SetDACWidth(Width: byte): longint; assembler;
asm
        movl    $0x4F08, %eax
        movb    Width, %bh
        movb    $1, %bl
        int     $0x10
end ;

function SV_GetModeInfo(Mode: longint; var ModeInfo: TModeInfo): longint;
var Regs: Registers;
begin
Regs.ax := $4F01;
Regs.es := vbDosBuffer shr 16;
Regs.di := vbDosBuffer and 65535;
Regs.cx := mode;
realintr($10, Regs);
SV_GetModeInfo := Regs.ax;
if Regs.ax = VBE_Success then
   DosMemGet(vbDosBuffer shr 16, vbDosBuffer and 65535, ModeInfo, 256);
end ;

function SV_EnumModes(CallBack: TEnumModesCallBack): longint;
var
   Ofs: longint;
   Seg: word;
   ModeInfo: TModeInfo;
   a: array[0..255] of word;
   i: integer;
begin
seg := Segment_To_Descriptor(VBEInfo.VideoModePtr shr 16);
ofs := VBEInfo.VideoModePtr and $FFFF;
seg_move(seg, ofs, get_ds, longint(@a), SizeOf(a));
i := 0;
while (i <= high(a)) and (a[i] <> $FFFF) do begin
   if SV_GetModeInfo(a[i], ModeInfo) = vbe_success then
      CallBack(a[i], ModeInfo);
   Inc(i);
   end ;
SV_EnumModes := i;
end ;

begin
end .

{
   change log
   02/02/2003  Adapted to Free Pascal 1.0.x (Carl Eric Codere)
   20/11/1998: Removed obsolute functions
               Better (more) documentation
               deleted functions which simply returned the value of a "private"
               variable, and made those public
               SV_EnumModes added.
   08/07/1998: protected mode interface works now
               re-organised lots of code
               converted some functions to assembly
               changed all word return values to longint
               added:
          SV_FindMode SV_Error SV_Done
               SV_SetDisplayStartXX functions
               Renamed internal variables
               Changed univbe mode constants to umXXX
   04/07/1998: started protected mode interface
   25/06/1998: added: vmLinear constant
               added: SV_OemVendorName / SV_OemProductName / SV_OemProductRev
   18/04/1998: added: VESA mode constants
   17/04/1998: sv_setMode and sv_init return a real VESA
               error code instead of true or false
   31/03/1998: set_Segment_Limit (bug) fixed in SV_InitBuffer
               the unit now works under Windows 95
}

