VOGONS


Keen 4-5-6 choppy scrolling on EGA card

Topic actions

  • This topic is locked. You cannot reply or edit posts.

Reply 60 of 68, by TheFiend

User metadata
Rank Newbie
Rank
Newbie
K1n9_Duk3 wrote on 2021-03-11, 23:09:

That doesn't really make sense. With these patches applied, the basic frame timing in Dave 3 and 4 should be more or less the same as in Keen Dreams, and the code that updates the CRTC start and panning registers of the video card (i.e. what actually makes the scrolling happen) is exactly the same in Keen Dreams and Dave 3 and 4. It could still have something to do with the frame timing, but then you should also get to see some issues in Keen Dreams from time to time (while walking around on the world map, for example).

The only suggestion I can give is to make sure that you are actually using the patched executables and not the original files.

It might help if you could record some video footage that shows these issues in action. That could tell me whether the issue lies with the CRTC start and panning registers or the drawing code.

I'll try again and also try to possibly record footage. I'll also try this on another machine on which the game works perfectly fine in Windows 98SE with no issues whatsoever but the jerky motion still exists in real DOS mode using MS-DOS 6.22.

UPDATE: After some further testing on the HP t5720 and a Pentium 3 Gateway machine from 2000 that I have, it appears that the problem is related to having Legacy USB enabled in the BIOS. The hp t5720 does not have a Legacy USB option in the BIOS so I have to disable the entire USB controller in the BIOS just to play Keen 1-3 and the Dave games which can get fairly annoying. Upon disabling Legacy USB the problem is totally gone. I can even play the unpatched versions of the games with no jerky motion issues however I need to rely on using Setmul for Dave 3 and Dave 4 to detect my Yamaha 724 sound card which can be annoying. Aside from that everything is working fine now. Sucks that I can't use USB peripherals and have to keep PS/2 mouse and keyboard on hand for this computer but oh well.

Reply 61 of 68, by K1n9_Duk3

User metadata
Rank Member
Rank
Member

Glad to hear you found a workaround for the problems you were experiencing.

I still can't think of a good reason why Keen Dreams would run fine while Dave 3 and 4 (with the 35 fps patch) have issues on the same system. But now that you mention Setmul, is this how you've been running Dave 3 and 4 all this time? This additional slowdown could explain why Dave 3 and 4 behave differently, assuming you've been running Keen Dreams without slowdown.

Reply 62 of 68, by TheFiend

User metadata
Rank Newbie
Rank
Newbie
K1n9_Duk3 wrote on 2021-03-23, 22:16:

Glad to hear you found a workaround for the problems you were experiencing.

I still can't think of a good reason why Keen Dreams would run fine while Dave 3 and 4 (with the 35 fps patch) have issues on the same system. But now that you mention Setmul, is this how you've been running Dave 3 and 4 all this time? This additional slowdown could explain why Dave 3 and 4 behave differently, assuming you've been running Keen Dreams without slowdown.

I've been running all these games without Setmul by default. The only real reason for me to try out Setmul was to get sound in Dave 3 and 4 with a PCI Sound Card. I don't need it with my ISA Sound Card based computer build that I have and I really prefer to not use it. I find it quite weird that Keen Dreams, a very problematic game, runs perfectly fine by default while other games that don't generally exhibit problems are causing issues for me.

Reply 63 of 68, by K1n9_Duk3

User metadata
Rank Member
Rank
Member

Well, I decided to give this another try and replace the screen update code with the method that I used for the most recent version of my Keen 4-6 code.

patch scripts
#==========================================================
# Dangerous Dave 3 - Dave's Risky Rescue v1.00 (Rev 1.04):
#==========================================================

%exefile DAVE.EXE 207760

#-------------------------------------------------------
# fix CPU speed sensitivity in AdLib/SoundBlaster code:

%patch $1606F
$BA $0388w # mov dx, 388h
$B9 $0009w # mov cx, 9
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $16083
$BA $0388w # mov dx, 388h
$B9 $0064w # mov cx, 100
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $16376
$BA $0388w # mov dx, 388h
$B9 $0006w # mov cx, 6
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $16387
$BA $0388w # mov dx, 388h
$B9 $0023w # mov cx, 35
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $16A05
$BA $0388w # mov dx, 388h
$B9 $0064w # mov cx, 100
$EC #inl: in al, dx
$E2 $FD # loop inl

#-------------------------------------------------------


#-------------------------------------------------------
# Customized Screen Update Code

# add /JERK parameter check:
%patch $28686
$00E0w # "COMP"
$419Cw # "JERK"
$0000w
"JERK" $00

# replace old VW_SetScreen code (doesn't all fit, though!)
%patch $1295F
$55 # push bp
$8B $EC # mov bp, sp
$BA $03DAw # mov dx, 3DAh
$FA # cli
#@l1:
$EC # in al, dx
Show last 343 lines
	$A8 $01			#	test	al, 1
$74 $FB # jz @l1
#@l2:
$EC # in al, dx
$A8 $01 # test al, 1
$75 $FB # jnz @l2
$8B $4E $06 # mov cx, [bp+arg_start]
$BA $03D4w # mov dx, 3D4h
$B0 $0C # mov al, 0Ch
$EE # out dx, al
$42 # inc dx
$8A $C5 # mov al, ch
$EE # out dx, al
$4A # dec dx
$B0 $0D # mov al, 0Dh
$EE # out dx, al
$8A $C1 # mov al, cl
$42 # inc dx
$EE # out dx, al
$8B $0E $D022w # mov cx, fixjerky
$E3 $03 # jcxz @l3
$E8 $F054w # call WaitVBL
#@l3:
$BA $03C0w # mov dx, 3C0h
$B0 $33 # mov al, 33h
$EE # out dx, al
$EB $00 # jmp $
$8A $46 $08 # mov al, [bp+arg_pan]
$EE # out dx, al
$E9 $F060w # jmp rest_of_VW_SetScreen

# replace VW_Plot with new code (VW_Plot now uses VW_Vlin to plot one pixel)
%patch $119CC
$55 # push bp
$8B $EC # mov bp, sp
$FF $76 $0A # push [bp+10] ;color
$FF $76 $06 # push [bp+6] ;x
$8B $46 $08 # mov ax, [bp+8] ;y
$50 # push ax ;y
$50 # push ax ;y
$0E # push cs
$E8 $003Dw # call VW_Vlin
$8B $E5 # mov sp, bp
$5D # pop bp
$CB # retf

#WaitVBL:
$8B $1E $E3F2w # mov bx, word ptr TimeCount
$BA $03DAw # mov dx, 3DAh
#@wait:
$FB # sti
$EB $00 # jmp short $
$FA # cli
$EC # in al, dx
$A8 $08 # test al, 8
$75 $0A # jnz @done
$A1 $E3F2w # mov ax, word ptr TimeCount
$2B $C3 # sub ax, bx
$3D $0001w # cmp ax, 1
$76 $ED # jbe @wait
#@done:
$C3 # retn

#rest_of_VW_SetScreen:
$0B $C9 # or cx, cx
$75 $03 # jnz @l5
$E8 $FFDEw # call WaitVBL
#@l5:
$FB # sti
$5D # pop bp
$CB # retf

#-------------------------------------------------------



#=======================================================
# Dangerous Dave 3 - Dave's Risky Rescue v1.01 (Rev 3):
#=======================================================
%exefile DAVE.EXE 208432

#-------------------------------------------------------
# fix CPU speed sensitivity in AdLib/SoundBlaster code:
%patch $161F5
$BA $0388w # mov dx, 388h
$B9 $0009w # mov cx, 9
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $16209
$BA $0388w # mov dx, 388h
$B9 $0064w # mov cx, 100
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $164FC
$BA $0388w # mov dx, 388h
$B9 $0006w # mov cx, 6
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $1650D
$BA $0388w # mov dx, 388h
$B9 $0023w # mov cx, 35
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $16B8B
$BA $0388w # mov dx, 388h
$B9 $0064w # mov cx, 100
$EC #inl: in al, dx
$E2 $FD # loop inl

#-------------------------------------------------------


#-------------------------------------------------------
# Customized Screen Update Code

# add /JERK parameter check:
%patch $28930
$00E0w # "COMP"
$42B6w # "JERK"
$0000w
"JERK" $00

# replace old VW_SetScreen code (doesn't all fit, though!)
%patch $12AE5
$55 # push bp
$8B $EC # mov bp, sp
$BA $03DAw # mov dx, 3DAh
$FA # cli
#@l1:
$EC # in al, dx
$A8 $01 # test al, 1
$74 $FB # jz @l1
#@l2:
$EC # in al, dx
$A8 $01 # test al, 1
$75 $FB # jnz @l2
$8B $4E $06 # mov cx, [bp+arg_start]
$BA $03D4w # mov dx, 3D4h
$B0 $0C # mov al, 0Ch
$EE # out dx, al
$42 # inc dx
$8A $C5 # mov al, ch
$EE # out dx, al
$4A # dec dx
$B0 $0D # mov al, 0Dh
$EE # out dx, al
$8A $C1 # mov al, cl
$42 # inc dx
$EE # out dx, al
$8B $0E $D13Aw # mov cx, fixjerky
$E3 $03 # jcxz @l3
$E8 $F054w # call WaitVBL
#@l3:
$BA $03C0w # mov dx, 3C0h
$B0 $33 # mov al, 33h
$EE # out dx, al
$EB $00 # jmp $
$8A $46 $08 # mov al, [bp+arg_pan]
$EE # out dx, al
$E9 $F060w # jmp rest_of_VW_SetScreen

# replace VW_Plot with new code (VW_Plot now uses VW_Vlin to plot one pixel)
%patch $11B52
$55 # push bp
$8B $EC # mov bp, sp
$FF $76 $0A # push [bp+10] ;color
$FF $76 $06 # push [bp+6] ;x
$8B $46 $08 # mov ax, [bp+8] ;y
$50 # push ax ;y
$50 # push ax ;y
$0E # push cs
$E8 $003Dw # call VW_Vlin
$8B $E5 # mov sp, bp
$5D # pop bp
$CB # retf

#WaitVBL:
$8B $1E $E50Aw # mov bx, word ptr TimeCount
$BA $03DAw # mov dx, 3DAh
#@wait:
$FB # sti
$EB $00 # jmp short $
$FA # cli
$EC # in al, dx
$A8 $08 # test al, 8
$75 $0A # jnz @done
$A1 $E50Aw # mov ax, word ptr TimeCount
$2B $C3 # sub ax, bx
$3D $0001w # cmp ax, 1
$76 $ED # jbe @wait
#@done:
$C3 # retn

#rest_of_VW_SetScreen:
$0B $C9 # or cx, cx
$75 $03 # jnz @l5
$E8 $FFDEw # call WaitVBL
#@l5:
$FB # sti
$5D # pop bp
$CB # retf

#-------------------------------------------------------


#=======================================================
# Dangerous Dave 4 - Dave Goes Nutz! v1.01 (Rev 1):
#=======================================================

%exefile DAVEGAME.EXE 195136

#-------------------------------------------------------
# fix CPU speed sensitivity in AdLib/SoundBlaster code:
%patch $15B83
$BA $0388w # mov dx, 388h
$B9 $0009w # mov cx, 9
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $15B97
$BA $0388w # mov dx, 388h
$B9 $0064w # mov cx, 100
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $15E8A
$BA $0388w # mov dx, 388h
$B9 $0006w # mov cx, 6
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $15E9B
$BA $0388w # mov dx, 388h
$B9 $0023w # mov cx, 35
$EC #inl: in al, dx
$E2 $FD # loop inl

%patch $16519
$BA $0388w # mov dx, 388h
$B9 $0064w # mov cx, 100
$EC #inl: in al, dx
$E2 $FD # loop inl

#-------------------------------------------------------


#-------------------------------------------------------
# Customized Screen Update Code

# add /JERK parameter check:
%patch $26218
$00E0w # "COMP"
$420Ew # "JERK"
$0000w
"JERK" $00

# replace old VW_SetScreen code (doesn't all fit, though!)
%patch $12473
$55 # push bp
$8B $EC # mov bp, sp
$BA $03DAw # mov dx, 3DAh
$FA # cli
#@l1:
$EC # in al, dx
$A8 $01 # test al, 1
$74 $FB # jz @l1
#@l2:
$EC # in al, dx
$A8 $01 # test al, 1
$75 $FB # jnz @l2
$8B $4E $06 # mov cx, [bp+arg_start]
$BA $03D4w # mov dx, 3D4h
$B0 $0C # mov al, 0Ch
$EE # out dx, al
$42 # inc dx
$8A $C5 # mov al, ch
$EE # out dx, al
$4A # dec dx
$B0 $0D # mov al, 0Dh
$EE # out dx, al
$8A $C1 # mov al, cl
$42 # inc dx
$EE # out dx, al
$8B $0E $C56Cw # mov cx, fixjerky
$E3 $03 # jcxz @l3
$E8 $F054w # call WaitVBL
#@l3:
$BA $03C0w # mov dx, 3C0h
$B0 $33 # mov al, 33h
$EE # out dx, al
$EB $00 # jmp $
$8A $46 $08 # mov al, [bp+arg_pan]
$EE # out dx, al
$E9 $F060w # jmp rest_of_VW_SetScreen

# replace VW_Plot with new code (VW_Plot now uses VW_Vlin to plot one pixel)
%patch $114E0
$55 # push bp
$8B $EC # mov bp, sp
$FF $76 $0A # push [bp+10] ;color
$FF $76 $06 # push [bp+6] ;x
$8B $46 $08 # mov ax, [bp+8] ;y
$50 # push ax ;y
$50 # push ax ;y
$0E # push cs
$E8 $003Dw # call VW_Vlin
$8B $E5 # mov sp, bp
$5D # pop bp
$CB # retf

#WaitVBL:
$8B $1E $D78Cw # mov bx, word ptr TimeCount
$BA $03DAw # mov dx, 3DAh
#@wait:
$FB # sti
$EB $00 # jmp short $
$FA # cli
$EC # in al, dx
$A8 $08 # test al, 8
$75 $0A # jnz @done
$A1 $D78Cw # mov ax, word ptr TimeCount
$2B $C3 # sub ax, bx
$3D $0001w # cmp ax, 1
$76 $ED # jbe @wait
#@done:
$C3 # retn

#rest_of_VW_SetScreen:
$0B $C9 # or cx, cx
$75 $03 # jnz @l5
$E8 $FFDEw # call WaitVBL
#@l5:
$FB # sti
$5D # pop bp
$CB # retf

#-------------------------------------------------------

%end

I also included some patches that should make the AdLib and SoundBlaster detection routines more reliable on systems with a fast CPU.

Please note that the screen update code supports two different modes of operation. The default mode might be even more choppy than the original code on certain video cards. You would need to run the game with the parameter "/JERK" to switch to the second mode. The default mode should work fine on most systems, I only have one system that would need the /JERK parameter and that's a 486 SX laptop from 1994.

As I type this, I realize that actually getting Dave 4 to recognize this parameter is complicated, since you would normally have to run the wrapper program DAVE.EXE, which wouldn't pass the /JERK parameter on to the main executable DAVEGAME.EXE. You can bypass the wrapper by starting the game like this:

DAVEGAME.EXE ^(a@&r` 1 0 /JERK

The ` character at the end of the first parameter is an accent character (ASCII code 96), not an apostrophe ( ' ). The second parameter (1) is for starting a new game - replace it with a 0 to make the game show the loading menu right away. The third paramter selects the difficulty level: 0 is for "Stud Mode", 1 is for "Wimp Mode". The first three parameters must always be passed in this order, any additional parameters like "/JERK" or "/COMP" (for SVGA compatibilty) can be passed in any order after these first three parameters.

Reply 65 of 68, by maxtherabbit

User metadata
Rank l33t
Rank
l33t
K1n9_Duk3 wrote on 2020-02-28, 01:17:
I'm most definitely not going to upload full versions of these games, neither patched nor unpatched. That would be illegal. […]
Show full quote

I'm most definitely not going to upload full versions of these games, neither patched nor unpatched. That would be illegal.

The point of using these patch scripts instead of providing fully patched executables is that the patching program can check if your game version matches the version the patch was made for. The graphics, sound and map files for these games are compressed and all the information necessary to decompress them is stored in the executable itself. This information varies for each game version, so copying a v1.4 executable into a non-v1.4 game folder will cause all kinds of issues.

If you need fully patched executables, then you need to use my patching utility and modify the patch scripts a little. Instead of the %ver and %ext commands, you need to enter one of the following at the beginning of the patch script:

#Keen 4 v1.4:
%exefile keen4e.exe 251712

#Keen 5 v1.4:
%exefile keen5e.exe 254832

#Keen 6 v1.4:
%exefile keen6.exe 260432

#Keen 6 v1.5:
%exefile keen6.exe 259632

Please take a look at this. The archive contains a set of slightly different patch scripts, which fix some display issues commonly seen in DOSBox (at the default settings) as well as on certain hardware. According to keropi, these new patches also fix the choppy scrolling issues on his EGA cards. Remember to edit the script files as mentioned above if you want to create fully patched executables with my patching utility.

If you don't trust the targeted patching utilities, then I guess providing fully patched executables for download (which would still be illegal) is not going to solve that problem.

I'd like to report that the "ck456_screenfix" patches are not effective on my matrox cards, however the original patch scripts from page one of this thread are working 100%. Smooth scrolling on a matrox! On my Mystique the game is flawless with only these patches, on my millennium both these and the 'KEENFIX' TSR are required. The other TSR corrects the off-colours.

Reply 67 of 68, by Jo22

User metadata
Rank l33t++
Rank
l33t++
alexclaudiu2003 wrote on 2023-03-31, 07:16:

is there a fix for the mario & luigi dos clone for ati radeon card (black screen)?
http://www.wieringsoftware.nl/mario/source.html
I tried with turbo pascal but I get error 200 divided by 0 or something like that

Hi there. Your PC might be too fast, the game needs patching thus.

Fix "Error 200" (Divide by zero) - by Snover and Stiletto (updated!)

XVI32 has a patching feature, too.
But it's meant for EXE files made with Borland Pascal 7.
Not sure if it works with Turbo Pascal 5.5/6.0 EXE files. It's worth a try, though.

http://www.chmaas.handshake.de/delphi/freewar … xvi32/xvi32.htm

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 68 of 68, by alexclaudiu2003

User metadata
Rank Newbie
Rank
Newbie
Jo22 wrote on 2023-03-31, 10:22:
Hi there. Your PC might be too fast, the game needs patching thus. […]
Show full quote
alexclaudiu2003 wrote on 2023-03-31, 07:16:

is there a fix for the mario & luigi dos clone for ati radeon card (black screen)?
http://www.wieringsoftware.nl/mario/source.html
I tried with turbo pascal but I get error 200 divided by 0 or something like that

Hi there. Your PC might be too fast, the game needs patching thus.

Fix "Error 200" (Divide by zero) - by Snover and Stiletto (updated!)

XVI32 has a patching feature, too.
But it's meant for EXE files made with Borland Pascal 7.
Not sure if it works with Turbo Pascal 5.5/6.0 EXE files. It's worth a try, though.

http://www.chmaas.handshake.de/delphi/freewar … xvi32/xvi32.htm

Thank you, I managed to compile and solve the error with 200 Divide by zero but it still does not start on Ati Radeon.
I tried to change the resolution from 320x200 to 320x400, thinking that it would work

unit VGA256;

{ (C) Copyright 1994-2001, Mike Wiering, e-mail: mike.wiering@wxs.nl }

{
Turbo Pascal VGA unit (Mode 13h, 320x200 256 colors), designed
for side-scrolling games, uses planar mode, page-flipping (2 pages),
statusline
}

{$DEFINE DEBUG}

{$R-} { no range-checking }
{$I-} { no I/O-checking }
{$G+} { allow 286 instructions }

interface

const
VGA_SEGMENT = $A000;

WINDOWHEIGHT = 13 * 14;
WINDOWWIDTH = 16 * 20;

SCREEN_WIDTH = 320;
SCREEN_HEIGHT = 200;

VIR_SCREEN_WIDTH = SCREEN_WIDTH + 2 * 20;
VIR_SCREEN_HEIGHT = 182;
BYTES_PER_LINE = VIR_SCREEN_WIDTH div 4;

MISC_OUTPUT = $03C2;
SC_INDEX = $03C4;
GC_INDEX = $03CE;
CRTC_INDEX = $03D4;
VERT_RESCAN = $03DA;

MAP_MASK = 2;
MEMORY_MODE = 4;

VERT_RETRACE_MASK = 8;

MAX_SCAN_LINE = 9;
START_ADDRESS_HIGH = $C;
START_ADDRESS_LOW = $D;
UNDERLINE = $14;
MODE_CONTROL = $17;

READ_MAP = 4;
GRAPHICS_MODE = 5;
MISCELLANEOUS = 6;

MAX_SCREENS = 24;
MAX_PAGE = 1;
PAGE_SIZE = (VIR_SCREEN_HEIGHT + MAX_SCREENS) * BYTES_PER_LINE;
PAGE_0 = 0;
PAGE_1 = $8000;

YBASE = 9;

Show last 1476 lines
  function DetectVGA: Boolean;
procedure InitVGA;
procedure OldMode;
function GetMode: Byte;
procedure SetMode (NewMode: Byte);
procedure ClearVGAMem;
procedure WaitDisplay;
procedure WaitRetrace;
procedure SetView (X, Y: Integer);
procedure SetViewport (X, Y: Integer; PageNr: Byte);
procedure SwapPages;
procedure ShowPage;
procedure Border (Attr: Byte);
procedure SetYStart (NewYStart: Integer);
procedure SetYEnd (NewYEnd: Integer);
procedure SetYOffset (NewYOffset: Integer);
function GetYOffset: Integer;
procedure PutPixel (X, Y: Integer; Attr: Byte);
function GetPixel (X, Y: Integer): Byte;
procedure DrawImage (XPos, YPos, Width, Height: Integer; var BitMap);
procedure RecolorImage (XPos, YPos, Width, Height: Integer; var BitMap; Diff: Byte);
procedure DrawPart (XPos, YPos, Width, Height, Y1, Y2: Integer; var BitMap);
procedure UpSideDown (XPos, YPos, Width, Height: Integer; var BitMap);
procedure PutImage (XPos, YPos, Width, Height: Integer; var BitMap);
procedure GetImage (XPos, YPos, Width, Height: Integer; var BitMap);
procedure Fill (X, Y, W, H: Integer; Attr: Integer);
procedure SetPalette (Color, Red, Green, Blue: Byte);
procedure ReadPalette (var NewPalette);
procedure ClearPalette;
function CurrentPage: Integer;
function GetPageOffset: Word;
procedure ResetStack;
function PushBackGr (X, Y, W, H: Integer): Word;
procedure PopBackGr (Address: Word);
procedure DrawBitmap (X, Y: Integer; var BitMap; Attr: Byte);

const
InGraphicsMode: Boolean = FALSE;

implementation

var
OldExitProc: Pointer;
OldScreenMode: Byte;

const
XView: Integer = 0;
YView: Integer = 0;

Page: Integer = 0;
PageOffset: Word = 0;

YOffset: Integer = 0;

SAFE = 34 * BYTES_PER_LINE;

Stack: array[0..MAX_PAGE] of Word =
(PAGE_0 + PAGE_SIZE + SAFE,
PAGE_1 + PAGE_SIZE + SAFE);


{$F+}
procedure NewExitProc;
{ Be sure to return to textmode if program is halted }
begin
OldMode;
ExitProc := OldExitProc;
end;
{$F-}

function GetMode: Byte;
{ Get video mode }
begin
asm
push bp
mov ah, 0Fh
int 10h
mov @Result, al
pop bp
end;
end;

procedure SetMode (NewMode: Byte);
{ Set video mode }
begin
asm
push bp
xor ah, ah
mov al, NewMode
int 10h
pop bp
end;
end;

procedure SetWidth (NewWidth: Word);
{ Set screen width (NewWidth >= 40) }
begin
asm
mov ax, NewWidth
push ax
mov dx, CRTC_INDEX
mov ax, 13h
out dx, al
pop ax
inc dx
out dx, al
end;
end;

function DetectVGA: Boolean;
var
VGADetected: Boolean;
begin
VGADetected := False;
asm
push bp
mov ax, 1A00h
int 10h
cmp al, 1Ah
jnz @NoVGA
inc VGADetected
@NoVGA:
pop bp
end;
DetectVGA := VGADetected;
end;

procedure InitVGA;
{ Start graphics mode 320x200 256 colors }
begin
ClearPalette;
SetMode ($13);
ClearPalette;
SetWidth (BYTES_PER_LINE shr 1);
asm
mov dx, SC_INDEX
mov al, MEMORY_MODE
out dx, al
inc dx
in al, dx
and al, not 8
or al, 4
out dx, al
mov dx, GC_INDEX
mov al, GRAPHICS_MODE
out dx, al
inc dx
in al, dx
and al, not 10h
out dx, al
dec dx
mov al, MISCELLANEOUS
out dx, al
inc dx
in al, dx
and al, not 2
out dx, al
end;
ClearVGAMem;
asm
mov dx, CRTC_INDEX
mov al, UNDERLINE
out dx, al
inc dx
in al, dx
and al, not 40h
out dx, al
dec dx
mov al, MODE_CONTROL
out dx, al
inc dx
in al, dx
or al, 40h
out dx, al
end;
if not InGraphicsMode then
begin
OldExitProc := ExitProc;
ExitProc := @NewExitProc;
end;
InGraphicsMode := TRUE;
end;

procedure OldMode;
{ Return to the original screenmode }
begin
if InGraphicsMode then
begin
ClearVGAMem;
ClearPalette;
ShowPage;
end;
SetMode (OldScreenMode);
InGraphicsMode := FALSE;
ExitProc := OldExitProc;
end;

procedure ClearVGAMem;
begin
asm
push es
mov dx, SC_INDEX
mov ax, 0F00h + MAP_MASK
out dx, ax
mov ax, VGA_SEGMENT
mov es, ax
xor ax, ax
mov di, ax
mov cx, 8000h
cld
rep stosw
pop es
end;
end;

procedure WaitDisplay;
begin
asm
mov dx, VERT_RESCAN
@1: in al, dx
test al, VERT_RETRACE_MASK
jnz @1
end;
end;

procedure WaitRetrace;
begin
asm
mov dx, VERT_RESCAN
@1: in al, dx
test al, VERT_RETRACE_MASK
jz @1
end;
end;

procedure SetView (X, Y: Integer);
begin
XView := X;
YView := Y;
end;

procedure SetViewport (X, Y: Integer; PageNr: Byte);
{ Set the offset of video memory }
var
i: Integer;
begin
asm
cli

mov dx, VERT_RESCAN { wait for display }
@1: in al, dx
test al, VERT_RETRACE_MASK
jnz @1

shl X, 1
shl Y, 1
mov ax, Y
mov bx, BYTES_PER_LINE / 2
mul bx
mov bx, X
mov cl, 3
shr bx, cl
add bx, ax
mov al, START_ADDRESS_HIGH
mov ah, PageNr
ror ah, 1
add ah, bh
mov dx, CRTC_INDEX
out dx, ax
mov al, START_ADDRESS_LOW
mov ah, bl
out dx, ax

mov dx, VERT_RESCAN { wait for retrace }
@2: in al, dx
test al, VERT_RETRACE_MASK
jz @2

mov ax, X
and ax, 7
add al, 10h
mov dx, 3c0h
mov ah, al
mov al, 33h
out dx, al
xchg ah, al
out dx, al
sti
end;
end;

procedure SwapPages;
begin
case Page of
0: begin
Page := 1;
PageOffset := PAGE_1 + YOffset * BYTES_PER_LINE;
end;
1: begin
Page := 0;
PageOffset := PAGE_0 + YOffset * BYTES_PER_LINE;
end;
end;
end;

procedure ShowPage;
begin
SetViewport (XView, YView, Page);
SwapPages;
end;

procedure Border (Attr: Byte);
{ Draw a border around the screen }
begin
asm
push bp
mov ax, 1001h
mov bh, Attr
int 10h
pop bp
end;
end;

procedure SetYStart (NewYStart: Integer);
begin
asm
mov dx, CRTC_INDEX
mov al, 16h
mov ah, Byte Ptr [NewYStart]
and ah, 7Fh
out dx, ax
end;
end;

procedure SetYEnd (NewYEnd: Integer);
begin
asm
mov dx, CRTC_INDEX
mov al, 15h
mov ah, Byte Ptr [NewYEnd]
out dx, ax
end;
end;

procedure SetYOffset (NewYOffset: Integer);
begin
YOffset := NewYOffset;
end;

function GetYOffset: Integer;
begin
GetYOffset := YOffset;
end;

procedure PutPixel (X, Y: Integer; Attr: Byte);
{ Draw a single pixel at (X, Y) with color Attr }
begin
asm
push es
mov ax, VGA_SEGMENT
mov es, ax
mov dx, Y
mov ax, BYTES_PER_LINE
mul dx
mov cx, X
push cx
shr cx, 1
shr cx, 1
add ax, cx
mov di, ax
add di, PageOffset
pop cx
and cl, 3
mov ah, 1
shl ah, cl
mov al, MAP_MASK
mov dx, SC_INDEX
out dx, ax
mov al, Attr
stosb
pop es
end;
end;

function GetPixel (X, Y: Integer): Byte;
{ Get color of pixel at (X, Y) }
begin
asm
push es
mov ax, VGA_SEGMENT
mov es, ax
mov dx, Y
mov ax, BYTES_PER_LINE
mul dx
mov cx, X
push cx
shr cx, 1
shr cx, 1
add ax, cx
mov si, ax
add si, PageOffset
pop ax
and al, 3
mov ah, al
mov al, READ_MAP
mov dx, GC_INDEX
out dx, ax
seges mov al, [si]
pop es
mov @Result, al
end;
end;

procedure DrawImage (XPos, YPos, Width, Height: Integer; var BitMap);
{ Draw an image on the screen (NULL-bytes are ignored) }
begin
asm
push ds

mov ax, VGA_SEGMENT
mov es, ax

mov ax, YPos
cmp ax, VIR_SCREEN_HEIGHT
jb @NotNeg
jg @End
mov bx, ax
add bx, Height
jnc @End
@NotNeg:
mov bx, BYTES_PER_LINE
mul bx
mov di, XPos
mov bx, di
shr di, 1
shr di, 1
add di, ax { DI = (YPos * 80) + XPos / 4 }
add di, PageOffset

lds si, BitMap { Point to bitmap }

and bl, 3
mov cl, bl
mov ah, 1
shl ah, cl
sub bl, 4
mov cx, 4 { 4 planes }

@Plane:
push bx
push cx { Planes to go }
push ax { Mask in AH }

mov al, MAP_MASK
mov dx, SC_INDEX
out dx, ax

cld
push di
mov bx, Width
shr bx, 1
shr bx, 1
mov ax, BYTES_PER_LINE
sub ax, bx { Space before next line }
mov dx, Height
@Line:
mov cx, bx
shr cx, 1

push ax
pushf

@Pixel:
lodsw
or al, al
jz @Skip1
seges
mov [di], al
@Skip1:
inc di
or ah, ah
jz @Skip2
seges
mov [di], ah
@Skip2:
inc di
loop @Pixel

popf
rcl cx, 1
jcxz @Skip3

lodsb
or al, al
jz @Odd
stosb
jmp @Skip3
@Odd: inc di
@Skip3:
pop ax
add di, ax
dec dx
jnz @Line

pop di

pop ax
mov al, ah
mov cl, 4
shl al, cl
or ah, al { Mask for next byte }
rol ah, 1 { Bit mask for next plane }
pop cx { Planes }
pop bx
inc bl { Still in the same byte? }
adc di, 0
loop @Plane

@End:
pop ds
end;
end;

procedure RecolorImage (XPos, YPos, Width, Height: Integer; var BitMap; Diff: Byte);
begin
asm
push ds

mov ax, VGA_SEGMENT
mov es, ax

mov ax, YPos
cmp ax, VIR_SCREEN_HEIGHT
jb @NotNeg
jg @End
mov bx, ax
add bx, Height
jnc @End
@NotNeg:
mov bx, BYTES_PER_LINE
mul bx
mov di, XPos
mov bx, di
shr di, 1
shr di, 1
add di, ax { DI = (YPos * 80) + XPos / 4 }
add di, PageOffset

lds si, BitMap { Point to bitmap }

and bl, 3
mov cl, bl
mov ah, 1
shl ah, cl
sub bl, 4
mov cx, 4 { 4 planes }

@Plane:
push bx
push cx { Planes to go }
push ax { Mask in AH }

mov al, MAP_MASK
mov dx, SC_INDEX
out dx, ax

cld
push di
mov bx, Width
shr bx, 1
shr bx, 1
mov ax, BYTES_PER_LINE
sub ax, bx { Space before next line }
mov dx, Height
@Line:
mov cx, bx
shr cx, 1

push ax
pushf

@Pixel:
lodsw
or al, al
jz @Skip1
add al, Diff
seges
mov [di], al
@Skip1:
inc di
or ah, ah
jz @Skip2
add ah, Diff
seges
mov [di], ah
@Skip2:
inc di
loop @Pixel

popf
rcl cx, 1
jcxz @Skip3

lodsb
or al, al
jz @Odd
add al, Diff
stosb
jmp @Skip3
@Odd: inc di
@Skip3:
pop ax
add di, ax
dec dx
jnz @Line

pop di

pop ax
mov al, ah
mov cl, 4
shl al, cl
or ah, al { Mask for next byte }
rol ah, 1 { Bit mask for next plane }
pop cx { Planes }
pop bx
inc bl { Still in the same byte? }
adc di, 0
loop @Plane

@End:
pop ds
end;
end;

procedure DrawPart (XPos, YPos, Width, Height, Y1, Y2: Integer; var BitMap);
begin
asm
push ds
cmp Height, 0
jle @End

mov ax, VGA_SEGMENT
mov es, ax

mov ax, YPos
cmp ax, VIR_SCREEN_HEIGHT
jb @NotNeg
jg @End
mov bx, ax
add bx, Height
jnc @End
@NotNeg:
mov bx, BYTES_PER_LINE
mul bx
mov di, XPos
mov bx, di
shr di, 1
shr di, 1
add di, ax { DI = (YPos * 80) + XPos / 4 }
add di, PageOffset

lds si, BitMap { Point to bitmap }

and bl, 3
mov cl, bl
mov ah, 1
shl ah, cl
sub bl, 4
mov cx, 4 { 4 planes }

@Plane:
push bx
push cx { Planes to go }
push ax { Mask in AH }

mov al, MAP_MASK
mov dx, SC_INDEX
out dx, ax

cld
push di
mov bx, Width
shr bx, 1
shr bx, 1
mov ax, BYTES_PER_LINE
sub ax, bx { Space before next line }

xor dx, dx
@Line:
cmp dx, Y1
jl @EndLine
cmp dx, Y2
jg @EndLine

mov cx, bx
shr cx, 1

push ax
pushf

@Pixel:
lodsw
or al, al
jz @Skip1
seges
mov [di], al
@Skip1:
inc di
or ah, ah
jz @Skip2
seges
mov [di], ah
@Skip2:
inc di
loop @Pixel

popf
rcl cx, 1
jcxz @Skip3

lodsb
or al, al
jz @Odd
stosb
jmp @Skip3
@Odd: inc di
@Skip3:
pop ax
add di, ax
jmp @1

@EndLine:
add si, bx
add di, BYTES_PER_LINE

@1: inc dx
cmp dx, Height
jb @Line

pop di

pop ax
mov al, ah
mov cl, 4
shl al, cl
or ah, al { Mask for next byte }
rol ah, 1 { Bit mask for next plane }
pop cx { Planes }
pop bx
inc bl { Still in the same byte? }
adc di, 0
loop @Plane

@End:
pop ds
end;
end;

procedure UpSideDown (XPos, YPos, Width, Height: Integer; var BitMap);
{ Draw an image on the screen up-side-down (NULL-bytes are ignored) }
begin
asm
push ds

mov ax, VGA_SEGMENT
mov es, ax

mov ax, YPos
cmp ax, VIR_SCREEN_HEIGHT
jb @NotNeg
jg @End
mov bx, ax
add bx, Height
jnc @End
@NotNeg:
add ax, Height
dec ax
mov bx, BYTES_PER_LINE
mul bx
mov di, XPos
mov bx, di
shr di, 1
shr di, 1
add di, ax { DI = (YPos * 80) + XPos / 4 }
add di, PageOffset

lds si, BitMap { Point to bitmap }

and bl, 3
mov cl, bl
mov ah, 1
shl ah, cl
sub bl, 4
mov cx, 4 { 4 planes }

@Plane:
push bx
push cx { Planes to go }
push ax { Mask in AH }

mov al, MAP_MASK
mov dx, SC_INDEX
out dx, ax

cld
push di
mov bx, Width
shr bx, 1
shr bx, 1
mov ax, BYTES_PER_LINE
add ax, bx { Space before next line }
mov dx, Height
@Line:
mov cx, bx
shr cx, 1

push ax
pushf

@Pixel:
lodsw
or al, al
jz @Skip1
seges
mov [di], al
@Skip1:
inc di
or ah, ah
jz @Skip2
seges
mov [di], ah
@Skip2:
inc di
loop @Pixel

popf
rcl cx, 1
jcxz @Skip3

lodsb
or al, al
jz @Odd
stosb
jmp @Skip3
@Odd: inc di
@Skip3:
pop ax
sub di, ax
dec dx
jnz @Line

pop di

pop ax
mov al, ah
mov cl, 4
shl al, cl
or ah, al { Mask for next byte }
rol ah, 1 { Bit mask for next plane }
pop cx { Planes }
pop bx
inc bl { Still in the same byte? }
adc di, 0
loop @Plane
@End:
pop ds
end;
end;

procedure PutImage (XPos, YPos, Width, Height: Integer; var BitMap);
{ Draw an image on the screen (NULL-bytes are NOT ignored) }
begin
asm
push ds
push es
mov ax, VGA_SEGMENT
mov es, ax

mov ax, YPos
mov bx, BYTES_PER_LINE
mul bx
mov di, XPos
mov bx, di
shr di, 1
shr di, 1
add di, ax { DI = (YPos * 80) + XPos / 4 }
add di, PageOffset

lds si, BitMap { Point to bitmap }

and bl, 3
mov cl, bl
mov ah, 1
shl ah, cl
sub bl, 4
mov cx, 4 { 4 planes }

@Plane:
push bx
push cx { Planes to go }
push ax { Mask in AH }

mov al, MAP_MASK
mov dx, SC_INDEX
out dx, ax

cld
push di
mov bx, Width
shr bx, 1
shr bx, 1
mov ax, BYTES_PER_LINE
sub ax, bx { Space before next line }
mov dx, Height
@Line:
mov cx, bx
shr cx, 1
rep movsw
rcl cx, 1
rep movsb
add di, ax
dec dx
jnz @Line

pop di

pop ax
mov al, ah
mov cl, 4
shl al, cl
or ah, al { Mask for next byte }
rol ah, 1 { Bit mask for next plane }
pop cx { Planes }
pop bx
inc bl { Still in the same byte? }
adc di, 0
loop @Plane


pop es
pop ds
end;
end;


procedure GetImage (XPos, YPos, Width, Height: Integer; var BitMap);
begin
asm
push ds
push es

mov cx, PageOffset

mov ax, VGA_SEGMENT
mov ds, ax

mov ax, YPos
mov bx, BYTES_PER_LINE
mul bx
mov si, XPos
mov bx, si
shr si, 1
shr si, 1
add si, ax { SI = (YPos * 80) + XPos / 4 }
add si, cx

les di, BitMap { Point to bitmap }

and bl, 3
sub bl, 4
mov cx, 4 { 4 planes }

@Plane:
push bx
push cx { Planes to go }

mov ah, bl
and ah, 3
mov al, READ_MAP
mov dx, GC_INDEX
out dx, ax

cld
push si
mov bx, Width
shr bx, 1
shr bx, 1
mov ax, BYTES_PER_LINE
sub ax, bx { Space before next line }
mov dx, Height
@Line:
mov cx, bx
shr cx, 1
rep movsw
rcl cx, 1
rep movsb
add si, ax
dec dx
jnz @Line

pop si

pop cx { Planes }
pop bx
inc bl { Still in the same byte? }
adc si, 0
loop @Plane


pop es
pop ds
end;
end;

procedure Fill (X, Y, W, H: Integer; Attr: Integer);
{ Fills an area on the screen with Attr }
begin
asm
mov ax, VGA_SEGMENT
mov es, ax

cld
mov dx, Y
mov ax, BYTES_PER_LINE
mul dx
mov di, X
push di
shr di, 1
shr di, 1
add di, ax { DI = Y * (width / 4) + X / 4 }
add di, PageOffset
pop cx
and cx, 3 { CX = X mod 4 }

mov ah, 0Fh
shl ah, cl
and ah, 0Fh

mov si, H
or si, si
jz @End { Height 0 }
mov bh, byte ptr Attr
mov dx, W
or dx, dx
jz @End { Width 0 }
add cx, dx
mov dx, SC_INDEX
mov al, MAP_MASK
sub cx, 4
jc @2
test cl, 3h
jnz @0
sub cx, 4
@0: jc @2
out dx, ax

mov al, bh { Attr }
push si { Height }
push di
@4: stosb { Left vertical line }
add di, BYTES_PER_LINE - 1
dec si
jnz @4
pop di
inc di
pop si

push ax
mov ax, 0F00h + MAP_MASK
out dx, ax
pop ax

mov ah, al { Attr }
push cx { Width }
shr cx, 1
shr cx, 1

push si { Height }
push di
@5: push di
push cx
shr cx, 1
rep stosw { Fill middle part }
rcl cx, 1
rep stosb
pop cx
pop di
add di, BYTES_PER_LINE
dec si
jnz @5
pop di
add di, cx { Point to last strip }
pop si { Height }

pop cx { Width }
mov bh, al { Attr }
mov bl, 0Fh { Mask }
jmp @3

@2: mov bl, ah { Begin and end in one single byte }

@3: and cl, 3
mov ah, 0
@1: shl ah, 1
add ah, 1
dec cl
jnz @1

and ah, bl { Use both masks }
mov al, MAP_MASK
out dx, ax
mov al, bh { Attr }
@6: stosb { Draw right vertical line }
add di, BYTES_PER_LINE - 1
dec si
jnz @6
@End:
end;
end;

procedure SetPalette (Color, Red, Green, Blue: Byte);
begin
asm
mov dx, 03C8h { DAC Write Address Register }
mov al, Color
out dx, al
inc dx
mov al, Red
out dx, al
mov al, Green
out dx, al
mov al, Blue
out dx, al
end;
end;

procedure ReadPalette (var NewPalette);
{ Read whole palette }
begin
asm
push ds
lds si, NewPalette
mov dx, 3C8h { VGA pel address }
mov al, 0
cli
cld
out dx, al
inc dx
mov cx, 3 * 100h
@1: lodsb
out dx, al
dec cx
jnz @1
sti
pop ds

{ push es
push bp
mov ax, 1012h
xor bx, bx
mov cx, 256
les dx, NewPalette
int 10h
pop bp
pop es }
end;
end;

procedure ClearPalette; assembler;
asm
cli
mov dx, 3C8h { VGA pel address }
mov al, 0
out dx, al
inc dx
mov cx, 3 * 100h
@1: out dx, al
dec cx
jnz @1
sti
end;


function CurrentPage: Integer;
begin
CurrentPage := Page;
end;

function GetPageOffset: Word;
begin
GetPageOffset := PageOffset;
end;

procedure ResetStack;
begin
Stack[0] := PAGE_0 + PAGE_SIZE + SAFE;
Stack[1] := PAGE_1 + PAGE_SIZE + SAFE;
end;

function PushBackGr (X, Y, W, H: Integer): Word;
{ Save background (X mod 4 = 0, W mod 4 = 0) }
var
StackPointer: Word;
begin
PushBackGr := 0;
if not ((Y + H >= 0) and (Y < 200)) then
Exit;
StackPointer := Stack [Page];
asm
mov bx, PageOffset
mov di, StackPointer
push ds
push es

mov ax, VGA_SEGMENT
mov ds, ax
mov es, ax

cld
mov dx, SC_INDEX
mov ax, 0100h + MAP_MASK
out dx, ax
mov ax, X
mov [di], ax
mov ax, 0200h + MAP_MASK
out dx, ax
mov ax, Y
mov [di], ax
mov ax, 0400h + MAP_MASK
out dx, ax
mov ax, W
mov [di], ax
mov ax, 0800h + MAP_MASK
out dx, ax
mov ax, H
stosw
mov al, 'M'
stosb

mov dx, GC_INDEX
mov al, GRAPHICS_MODE
out dx, al
inc dx
in al, dx
push ax
mov al, 41h
out dx, al

mov dx, SC_INDEX
mov ax, 0F00h + MAP_MASK
out dx, ax

mov ax, READ_MAP
mov dx, GC_INDEX
out dx, ax

mov dx, Y
mov ax, BYTES_PER_LINE
mul dx
mov si, X
shr si, 1
shr si, 1
add si, ax
add si, bx

mov cx, W
shr cx, 1
shr cx, 1

mov bx, H

@1: push cx
rep
movsb { copy 4 pixels }
pop cx
add si, BYTES_PER_LINE
sub si, cx
dec bx
jnz @1

mov dx, GC_INDEX
pop ax
mov ah, al
mov al, GRAPHICS_MODE
out dx, ax

pop es
pop ds
end;
PushBackGr := Stack [Page];
Inc (Stack [Page], W * H + 8);
end;

procedure PopBackGr (Address: Word);
var
X, Y, W, H: Integer;
begin
if Address = 0 then
Exit;
asm
mov bx, PageOffset
mov si, Address

push ds
push es

mov ax, VGA_SEGMENT
mov ds, ax
mov es, ax

cld
mov dx, GC_INDEX
mov ax, 0000h + READ_MAP
out dx, ax
mov ax, [si]
mov X, ax
mov ax, 0100h + READ_MAP
out dx, ax
mov ax, [si]
mov Y, ax
mov ax, 0200h + READ_MAP
out dx, ax
mov ax, [si]
mov W, ax
mov ax, 0300h + READ_MAP
out dx, ax
lodsw
mov H, ax
lodsb
cmp al, 'M'
jz @@1
{$IFDEF DEBUG}
int 3
{$ENDIF}
jmp @End
@@1:
mov dx, GC_INDEX
mov al, GRAPHICS_MODE
out dx, al
inc dx
in al, dx
push ax
mov al, 41h
out dx, al

mov dx, SC_INDEX
mov ax, 0F00h + MAP_MASK
out dx, ax

mov ax, READ_MAP
mov dx, GC_INDEX
out dx, ax

mov dx, Y
mov ax, BYTES_PER_LINE
mul dx
mov di, X
shr di, 1
shr di, 1
add di, ax
add di, bx

mov cx, W
shr cx, 1
shr cx, 1

mov bx, H

@1: push cx
rep
movsb { copy 4 pixels }
pop cx
add di, BYTES_PER_LINE
sub di, cx
dec bx
jnz @1

mov dx, GC_INDEX
pop ax
mov ah, al
mov al, GRAPHICS_MODE
out dx, ax

@end: pop es
pop ds
end;
end;

procedure DrawBitmap (X, Y: Integer; var BitMap; Attr: Byte);
{ Bitmap starts with size W, H (Byte) }
var
W, H, PageOffset: Integer;
begin
PageOffset := GetPageOffset;
asm
push es
push ds

lds si, BitMap
mov ah, 0
cld
lodsb
mov W, ax
lodsb
mov H, ax
mov ax, VGA_SEGMENT
mov es, ax

mov bl, 0
mov cx, H
mov dx, Y
@1: push cx
mov cx, X
mov di, W
@2: push cx
push dx
or bl, bl
jnz @3
lodsb
mov bh, al
mov bl, 8
@3: dec bl
shr bh, 1
jnc @4

push si
push di
push bx
mov al, Attr

@PutPixel:
{ CX = X, DX = Y, AL = Attr }
push ax
mov ax, BYTES_PER_LINE
mul dx
push cx
shr cx, 1
shr cx, 1
add ax, cx
mov di, ax
add di, PageOffset
pop cx
and cl, 3
mov ah, 1
shl ah, cl
mov al, MAP_MASK
mov dx, SC_INDEX
out dx, ax
pop ax
stosb

pop bx
pop di
pop si

@4:
pop dx
pop cx
inc cx
dec di
jnz @2

inc dx
pop cx
dec cx
jnz @1
pop ds
pop es
end;
end;

begin
OldScreenMode := GetMode;
end.