Hello, all!
I'm programming in assembly for MSDOS, using TASM, and I'm trying to write a couple of subroutines that put a pixel in a buffer, and then dump the buffer on video memory. But it's not working. I can put pixel directly on video memory, but this is very slow when I have to render a big sprite or a picture. If I use the buffer, no pixel shows up on screen.
I declared the buffer after the data segment and before the code segment like this:
SBUFFER SEGMENT
SCREEN_BUFFER DB 64000 DUP(?)
SBUFFER ENDS
This is the subroutine that puts a pixel in the buffer:
;########################################################
;# SUBROUTINE: PUT_PIXEL_IN_BUFFER
;#
;# EXAMPLE: YELLOW PIXEL AT X=40, Y=20
;#
;# MOV MODE13H_X, 40
;# MOV MODE13H_Y, 20
;# MOV MODE13H_COLOR, 12
;#
;########################################################
PUT_PIXEL_IN_BUFFER PROC NEAR
PUSH AX
PUSH BX
PUSH ES
PUSH DI
MOV AX, SEG SBUFFER
MOV ES, AX
ASSUME ES:SBUFFER
MOV DI, OFFSET SCREEN_BUFFER
MOV AX, MODE13H_Y
MOV BX, MODE13H_Y
SHL AX, 8
SHL BX, 6
ADD AX, BX
ADD AX, MODE13H_X
ADD DI, AX
MOV AL, MODE13H_COLOR
MOV ES:[DI], AL
POP DI
POP ES
POP BX
POP AX
RET
PUT_PIXEL_IN_BUFFER ENDP
Next is the subroutine that dumps the buffer on video memory:
;########################################################
;# SUBROUTINE: DUMP_VIDEO_BUFFER
;########################################################
DUMP_VIDEO_BUFFER PROC NEAR
PUSH AX
PUSH BX
PUSH DS
PUSH ES
PUSH SI
PUSH DI
;=============
MOV AX, 0A000H
MOV DS, AX
MOV AX, SEG SBUFFER
MOV ES, AX
ASSUME ES:SBUFFER
MOV DI, OFFSET SCREEN_BUFFER
MOV SI, 0
ADD DI, 9600
ADD SI, 9600
;=============
DUMP_VIDEO_BUFFER_:
MOV AL, ES:[DI]
CMP AL, 16
JE NO_DUMP
MOV DS:[SI], AL
NO_DUMP:
INC SI
INC DI
CMP SI, 0FA00H
JL DUMP_VIDEO_BUFFER_
;=============
POP DI
POP SI
POP ES
POP DS
POP BX
POP AX
RET
DUMP_VIDEO_BUFFER ENDP
Finally, I have a subroutine that waits for the end of a CRT trace:
;#################################################
;# WAIT FOR NEW VERTICAL RETRACE
;#################################################
WAIT_VR PROC NEAR
PUSH AX
PUSH DX
MOV DX, 3DAH
;WAIT FOR BIT 3 TO BE ZERO
WAIT_END_OLDVR:
IN AL, DX
TEST AL, 08H
JNZ WAIT_END_OLDVR
;WAIT FOR BIT 3 TO BE ONE
WAIT_BEGIN_NEWVR:
IN AL, DX
TEST AL, 08H
JZ WAIT_BEGIN_NEWVR
POP DX
POP AX
RET
WAIT_VR ENDP
And this is how I call the subroutines:
MOV MODE13H_X, 40
MOV MODE13H_Y, 20
MOV MODE13H_COLOR, 12
CALL PUT_PIXEL_IN_VIDEO_BUFFER
CALL WAIT_VR
CALL DUMP_VIDEO_BUFFER
The result is that nothing shows up on screen. I've been troubleshooting this for hours now, and can't find what I'm doing wrong. I'm hoping a good samaritan will spot the mistake in my code... Thanks a lot!
Heh; just so happens I was looking at some of my old DOS code when I came across this. Ah, the goodbad old days of writing directly to screen memory ...
Your code looks fine. That is, until I did some arithmetic on your values. First of all, I'm wondering why you're offsetting your reads and writes to memory (both source and dest) by 9600. Also mystified by your left-shifting the same y-value by 8 and 6 and then adding them. I can only assume you know what you're doing there.
Taking your x- and y-values:
20 << 8 = 5120
20 << 6 = 1280
40 +
---------------
result: 6440
That might explain it; you're skipping right over the data you wrote into your buffer by starting at offset 9600.
I can't see anything else that looks wrong. Not sure about your vertical-retrace code; have you tried it without that? I assume that's to avoid flicker, yes?
One thing that strikes pretty instantly is this piece of code:
NO_DUMP:
INC SI
INC DI
CMP SI, 0FA00H
JL DUMP_VIDEO_BUFFER_
JL is used for signed compares - and 0FA00h in 16 bit is a negative number. So your little loop will probably end just after one byte has been transfered. Better is to use JB.
Quote from: NoCforMe on February 07, 2024, 06:36:11 PMFirst of all, I'm wondering why you're offsetting your reads and writes to memory (both source and dest) by 9600. Also mystified by your left-shifting the same y-value by 8 and 6 and then adding them. I can only assume you know what you're doing there.
I wrote this code 20 years ago, and perhaps it worked with other macros or code I don't have any more... I don't remember what the 9600 is... The left shifting is just to multiply the y coordinate times 320.
Quote from: _japheth on February 07, 2024, 07:35:33 PMOne thing that strikes pretty instantly is this piece of code:
JL is used for signed compares - and 0FA00h in 16 bit is a negative number. So your little loop will probably end just after one byte has been transfered. Better is to use JB.
Corrected. Thanks.
Ok. So I took away the two instructions that added 9600 to both SI and DI and it works now. It's still slow. It takes a little less than a second to dump the buffer (plus the time prior to that to put the sprites in the buffer). It's better than before, because it dumped every individual sprite one after the other, and now it does the whole screen at once. But still slow...
I'm blown away by very old msdos games that, when you moved to the next room, the screen changed instantly...
I fixed the code using the instruction MOVS, instead of moving pixel by pixel. In the original code, the CMP AL, 16 was meant to skip black pixels, but I got rid of it. I also disabled interrupts while the buffer is being copied. This is the new code (I skipped the pushes and the pops):
CLI
MOV AX, 0A000H
MOV ES, AX ; Set ES to the video memory segment
MOV AX, SEG SBUFFER
MOV DS, AX ; Set DS to the segment of SBUFFER
MOV DI, OFFSET SCREEN_BUFFER ; Set DI to the destination address in video memory
MOV SI, 0 ; Set SI to the source index
DUMP_VIDEO_BUFFER_:
MOVS BYTE PTR ES:[DI], BYTE PTR DS:[SI] ;This also increments SI and DI
CMP SI, 0FA00H
JB DUMP_VIDEO_BUFFER_
STI
Now there's no lag or flickering. The only problem is that my subroutine to put a sprite in the buffer, goes pixel by pixel, so there's a wait before I can see the image while the sprite is being transferred to the buffer.
This is the code of the subroutine that puts a sprite in the buffer:
;#################################################
;# SUBROUTINE: PRINT_256SPRITE
;#
;# EXAMPLE:
;# MOV MODE13H_DELETE, 1 (TO DELETE)
;# MOV MODE13H_X, 100
;# MOV MODE13H_Y, 105
;# LEA DI, SPRITE_ADDRESS
;# CALL WAIT_VR
;# CALL PRINT_256SPRITE
;#################################################
PRINT_256SPRITE PROC NEAR
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH DI
MOV CX, MODE13H_X ;XCOORD
MOV DX, CX
MOV BX, MODE13H_Y ;YCOORD
PRINT_256SPRITE_LOOP:
CMP BYTE PTR [DI],'$' ;CHECK END OF SPRITE
JE PRINT_256SPRITE_END
MOV AL, BYTE PTR [DI] ;GET PIXEL COLOR
MOV MODE13H_COLOR, AL
CALL PUT_PIXEL_IN_BUFFER ;PUT PIXEL IN BUFFER
INC DI
INC MODE13H_X
CMP BYTE PTR [DI], '#' ;CHECK END OF LINE
JNE PRINT_256SPRITE_LOOP
MOV MODE13H_X, CX ;GO TO NEXT LINE
INC MODE13H_Y
INC DI
JMP PRINT_256SPRITE_LOOP
PRINT_256SPRITE_END:
MOV MODE13H_DELETE, 0
POP DI
POP DX
POP CX
POP BX
POP AX
RET
PRINT_256SPRITE ENDP
Perhaps there's a better way than transferring pixel by pixel, but then I'd have to make sure that the dimensions of the sprite are multiples of 8 of something. The only problem is that I won't be able to handle transparent pixels.
You can do better than that for your byte-moving code. Take advantage of the x86's more advanced instructions. No need for a comparison to end the loop. In fact, no need for a loop at all:
MOV AX, 0A000h
MOV ES, EX
MOV AX, SEG Sbuffer
MOV DS, AX
XOR SI, SI
MOV DI, OFFSET SCREEN_BUFFER
MOV CX, <# of bytes to move>
; This moves DS:SI--> ES:DI, increments SI & DI, does it CX times:
REP MOVSB
Another opportunity for a speed-up: in your PRINT_256SPRITE routine, instead of calling PUT_PIXEL_IN_BUFFER for every single pixel, put that code in-line in your loop (PRINT_256SPRITE_LOOP), but minus the setup. Do your setup (setting your buffer pointer and the segment register) outside of the loop. Since you're using DI as your sprite-data pointer, use either SI or BX as a buffer pointer. That should reduce a lot of overhead, at the expense of slightly more code.
Quote from: NoCforMe on February 08, 2024, 05:43:03 AMYou can do better than that for your byte-moving code. Take advantage of the x86's more advanced instructions. No need for a comparison to end the loop. In fact, no need for a loop at all:
MOV AX, 0A000h
MOV ES, EX
MOV AX, SEG Sbuffer
MOV DS, AX
XOR SI, SI
MOV DI, OFFSET SCREEN_BUFFER
MOV CX, <# of bytes to move>
; This moves DS:SI--> ES:DI, increments SI & DI, does it CX times:
REP MOVSB
I would fill
CX with
320*200/4 and change
MOVSB to
MOVSD, moving aligned DWORDs will speed things up.
No need to wait for a vertical refresh if you're writing to the buffer, only when writing to the screen.
Also, as it is your code doesn't check for a sprite that is partly off the screen, is this deliberate?
Does movsd work in 16-bit code?
Quote from: jj2007 on February 09, 2024, 02:57:19 AMDoes movsd work in 16-bit code?
Yes, but I think it uses the operand size override
66HIt also requires a 386 or better :biggrin:
Hi popcalent
example of scrolling in mode 13h
and keyboard control and rossler with help of fpu
https://masm32.com/board/index.php?topic=9319.30 (https://masm32.com/board/index.php?topic=9319.30)
Thank you all for your replies. MOVSB won't work unless using 386 assembly, which I'm not.
Quote from: daydreamer on February 09, 2024, 04:53:02 AMHi popcalent
example of scrolling in mode 13h
and keyboard control and rossler with help of fpu
https://masm32.com/board/index.php?topic=9319.30 (https://masm32.com/board/index.php?topic=9319.30)
Thanks! Is this also taking care of the keyboard lag when using arrow keys to move a sprite?
Quote from: popcalent on February 09, 2024, 08:09:56 AMMOVSB won't work unless using 386 assembly, which I'm not.
Not true! Where did you get that idea? I've been using that (including
REP MOVSB) since 8088 days.
What assembler are you using?
Quote from: NoCforMe on February 09, 2024, 09:49:03 AMQuote from: popcalent on February 09, 2024, 08:09:56 AMMOVSB won't work unless using 386 assembly, which I'm not.
Not true! Where did you get that idea? I've been using that (including REP MOVSB) since 8088 days.
What assembler are you using?
I'm sorry. I meant MOVSD. If I'm not mistaken, MOVSB is the same as MOVS and forcing the parameters to be bytes, which is what I'm doing, correct? I can't remember if MOVSW works in < 386 assembly...
Both MOVSB and MOVSW will work with any x86 assembler; those have been in the instruction set since day 1.
You're correct, MOVSD requires 386 or better. But I don't think you need to mess with that in order to see a significant speed increase of your code. Why don't you try my suggestion and see what it does for you? (Including inlining your PUT_PIXEL_IN_BUFFER code.)
If you're moving an even number of bytes, then by all means use MOVSW instead of MOVSB. Take every advantage.
Quote from: NoCforMe on February 09, 2024, 10:40:34 AMWhy don't you try my suggestion and see what it does for you? (Including inlining your PUT_PIXEL_IN_BUFFER code.)
Will do when I have a chance. Probably later tonight.
Quote from: NoCforMe on February 09, 2024, 10:40:34 AMIf you're moving an even number of bytes, then by all means use MOVSW instead of MOVSB. Take every advantage.
I'm copying a buffer the size of the 320x200 screen, so yes, it's an even number of bytes.
Quote from: popcalent on February 09, 2024, 08:09:56 AMThanks! Is this also taking care of the keyboard lag when using arrow keys to move a sprite?
no idea,I am spoiled by running Dosbox emulation on 3+ghz cpu,guess its capable of ca pentium cpu speed
depending on what kind of game you can make,simple physics engine ala moonlander and asteroids kind of control ,it always
add x,xspeed
add y,yspeed each frame
keyboard controls indirectly accelerate/decelerate xspeed and yspeed
if you have fpu,might be faster to copy with fld/fstp 10 bytes at a time
Quote from: daydreamer on February 10, 2024, 04:08:26 AMno idea,I am spoiled by running Dosbox emulation on 3+ghz cpu
I'm talking about the lag that happens after you press a key on any computer, even a modern one. For instance, you press 'a' on your favorite word processor and don't release, then just one 'a' appears on screen, and after a second of doing nothing the screen starts getting filled with a's until you release.
On a game, you would press an arrow key, the sprite would take a step towards that direction, stop for a second, then start walking. Of course, professional games don't do that, you press a key and the sprite starts walking without pausing for a second after the first step.
Quote from: popcalent on February 10, 2024, 07:26:26 AMQuote from: daydreamer on February 10, 2024, 04:08:26 AMno idea,I am spoiled by running Dosbox emulation on 3+ghz cpu
I'm talking about the lag that happens after you press a key on any computer, even a modern one. For instance, you press 'a' on your favorite word processor and don't release, then just one 'a' appears on screen, and after a second of doing nothing the screen starts getting filled with a's until you release.
On a game, you would press an arrow key, the sprite would take a step towards that direction, stop for a second, then start walking. Of course, professional games don't do that, you press a key and the sprite starts walking without pausing for a second after the first step.
I use lowlevel in al,60h keyboard port and combine it with simple physics
in al,60h
.IF al== VK_LEFT
mov middx,-3
mov middy,0
.ENDIF
.IF al==VK_RIGHT
mov middx,3
mov middy,0
.ENDIF
.IF al==VK_UP
mov middy,-640
mov middx,0
.ENDIF
.IF al==VK_DOWN
mov middy,640
mov middx,0
.ENDIF
cmp al,1
cmp al,1 =escape key =exit game loop
I have written a VK_codes.asm files with equates compatible with windows keycodes to make it easier to port code between windows and DOS 16 bit
So how are you accessing the keyboard in your program?
If you're using the DOS INT 21h interface, well, there's your problem; that's a high-level interface with lots of latency (time delay).
Your other choices are:
- The ROM BIOS keyboard interface (INT 16h), which gives you low-level access to the keyboard. One of the functions here gives you an immediate status report on whether a keystroke is available, and what that keystroke is.
- Intercepting the keyboard hardware interrupt (INT 9) directly, which will give you an immediate notification anytime a key is depressed.
None of these are very hard to implement.
Have a 128-byte table for keys and hook int 15h, AH=4Fh. The INT 9 code reads the scan code from port 60h and calls this function. Set or clear the byte in the array depending on whether it's a make code, then your main loop can just check the array for keys down. Easier then hooking INT 9 and having to worry about LEDs and weird key codes (like SysRq).
Are you sure about INT 15h? My book shows that as the cassette interface.
It's been a long time since I used any of that stuff, so I may be missing something here. Is AH=4FH a keyboard function?
I was thinking that hooking INT 9 would only involve peeking at the keycode, setting flags for certain keys only, then simply passing the request on to the real INT 9 handler. Basically a passive handler.
INT 15h, AH=4Fh (https://fragglet.github.io/dos-help-files/alang.hlp/x_at_L8123.html)
Ah, nice clean interface there. That sounds like the way to go.
Invoked for each keystroke by the ROM BIOS's Int 09h keyboard
interrupt handler.
Input Output
AH = 4Fh If scan code consumed
AL = Scan code Carry flag: clear
If scan code not consumed
Carry flag: set
AL = Unchanged or new scan code
Quote from: daydreamer on February 10, 2024, 07:42:14 AMI use lowlevel in al,60h keyboard port and combine it with simple physics
in al,60h
There's still a problem with IN AL, 60h. If the user goes too fast with his fingers (which is pretty common) and presses another arrow key before releasing the current one, the sprite stalls.
I'm thinking about implementing a two-level stack. When the user presses an arrow key, the program pushes a corresponding value into the stack (for instance, UP=1, DOWN=2, etc) , when the user releases an arrow key, the program pops the corresponding value out of the stack.
An example sequence would be this:
1) User doesn't push any arrow key. Stack is 00. Sprite doesn't move.
2) User pushes right arrow key. Stack is 0R. Sprite moves to the right.
3) User pushes left arrow key without releasing right arrow key. Stack is RL. Sprite moves to the left.
4) User releases right arrow key without releasing left arrow key. Stack is 0L. Sprite still moves to the left.
An alternative to step 4:
4) User releases left arrow key without releasing right arrow key. Stack is 0R. Sprite moves to the right.
I've done this with C before, and it works "most" of the time. Sometimes, you release the keys, and the sprite is stuck walking. I guess it happens when you release the key while the code is doing something else and can't register the release. I guess the solution is to do it through an interrupt. But perhaps it's not necessary in ASM since it's faster than C.
Try hooking INT 15h as sinsi suggested; that might take care of that latency problem.
This code should do the hooking part:
OldINT15Vector LABEL DWORD
OldINT15off DW ?
OldINT15seg DW ?
PUSH ES ;Save segment reg.
XOR AX, AX ;Point to "zero segment".
MOV ES, AX
MOV BX, 15h * 4 ;Address of INT 15h vector.
MOV AX, ES:[BX] ;Get offset.
MOV OldINT15off, AX
MOV AX, ES:[BX + 2] ;Get segment.
MOV OldINT15seg, AX
CLI ;Disable interrupts.
MOV AX, OFFSET INT15handler
MOV ES:[BX], AX ;Plug in new offset.
MOV AX, CS ;Get our code segment.
MOV ES:[BX + 2], AX ;Plug it in.
STI ;Re-enable interrupts.
POP ES ;Restore segment reg.
. . .
INT15Handler PROC
CMP AH, 4Fh ;We're only interested in this subfunction.
JNE passon
; Set your flags or whatever here;
; key scan code is in AL.
passon: JMP FAR PTR OldINT15Vector ;Go to the original INT 15 handler.
INT15handler ENDP
QuoteThere's still a problem with IN AL, 60h. If the user goes too fast with his fingers (which is pretty common) and presses another arrow key before releasing the current one, the sprite stalls.
Are the arrow keys being used as a toggle? For example, when you press the right arrow key the sprite moves right, what happens when the key is released? Does the sprite keep moving or stop?
Quote from: sinsi on February 12, 2024, 11:51:00 AMQuoteThere's still a problem with IN AL, 60h. If the user goes too fast with his fingers (which is pretty common) and presses another arrow key before releasing the current one, the sprite stalls.
Are the arrow keys being used as a toggle? For example, when you press the right arrow key the sprite moves right, what happens when the key is released? Does the sprite keep moving or stop?
Not as a toggle, but as a momentary switch. When the key is released, the sprite is supposed to stop.
I implemented a two level stack to prevent this from happening. This is similar to the 128-key table you proposed. This solves the problem with the user switching keys too fast. The problem now is that, most of the time, the sprite stops when the key is released, but sometimes it keeps moving. I'm not handling this as an interrupt (that's my next step), but as a procedure. My guess is that there are times the user releases a key, but the program is doing something else and doesn't register the release.
Welcome to the world of tricky timing.
I think hooking interrupts is the way to go for you. All the interrupt hook will do is set a flag or two; it'll be up to your other code to check these flags--frequently!--to figure out what state it should be in.
I have confidence that you'll figure this out.
Quote from: NoCforMe on February 12, 2024, 09:14:51 AMTry hooking INT 15h as sinsi suggested; that might take care of that latency problem.
This code should do the hooking part:
So, I had
CALL CHECK_KEYBOARD first thing in the main loop. The first thing in this subroutine is
IN AL, 60h. Then, depending on what was in AL, do this or that. And that worked most of the time except for the few cases where I released a key and the sprite got stuck moving.
I put the code to hook up the handler in a subroutine and I call the subroutine before the main loop. I basically copied your code. Then, in the interrupt handler, I copied your code and I added
CALL CHECK_KEYBOARD between
JNE passon and
passon: JMP FAR PTR OldINT15Vector. Inside the CHECK_KEYBOARD subroutine I deleted the
IN AL, 60h, and left everything else the same.
I made it this way because I didn't want to cut and paste large chunks of code to avoid possible mess-ups. However, the program is now not responding to any key presses. It's basically stalled.
Quote from: popcalent on February 13, 2024, 04:52:25 AMHowever, the program is now not responding to any key presses. It's basically stalled.
Ok, and now? Do you expect us to make wild guesses of what the error may be, based on your "description" of what you've done and what's going wrong? Post the current source!
Um, not quite. You're over-complicating things.
When your program gets called at your INT 15h entry point, that means that there's already a keystroke available. The key code is already in AL. So don't call CHECK_KEYBOARD; just use that key code to decide what to do by setting a flag (in your data segment) that can be checked by other parts of your program.
Interrupt service routines (ISRs) need to be lean and mean. The less you do there the better. No CALLs, just setting flags and then getting the hell out of there.
To set a flag in your data segment you'll need to do something like this:
PUSH ES
MOV AX, <your data segment>
MOV ES, AX
MOV ES:YourFlag, SomeValue
POP ES
but you probably already know that.
Quote from: NoCforMe on February 13, 2024, 08:06:49 AMUm, not quite. You're over-complicating things.
When your program gets called at your INT 15h entry point, that means that there's already a keystroke available. The key code is already in AL. So don't call CHECK_KEYBOARD; just use that key code to decide what to do by setting a flag (in your data segment) that can be checked by other parts of your program.
That's why I deleted the
IN AL, 60h line from CHECK_KEYBOARD. But it's possible that when I call the procedure AL is overwritten by something else (though it shouldn't...)
Quote from: NoCforMe on February 13, 2024, 08:06:49 AMInterrupt service routines (ISRs) need to be lean and mean. The less you do there the better. No CALLs, just setting flags and then getting the hell out of there.
To set a flag in your data segment you'll need to do something like this:
PUSH ES
MOV AX, <your data segment>
MOV ES, AX
MOV ES:YourFlag, SomeValue
POP ES
but you probably already know that.
Alright. I'll just set the flag to whatever AL is, leave the handler, then call CHECK_KEYBOARD from inside the main loop, and check the flag there. Thanks!
Then change the ISR code to:
PUSH ES
MOV DX, <your data segment>
MOV ES, DX
MOV ES:Keystroke, AL
POP ES
or whatever (so you don't overwrite the keycode in AL).
I can't get it to work. The program crashes, so I'm pasting the whole thing. I just made it shorter by handling only the ESC key. The program installs the handler, then enters the loop. When a key is pressed, the handler puts AL in PRESSED_KEY. The main loop is constantly checking for PRESSED_KEY. If it's ESC, then it calls EXIT_TO_DOS, if not it loops.
;#################################################
;#
;# PRESS_ESC.ASM
;#
;#################################################
;#################################################
;# DEFINITIONS
;#################################################
KB_ESC EQU 1h
;#################################################
;# STACK
;#################################################
STACKSG SEGMENT STACK
STACKSG ENDS
;#################################################
;# DATA
;#################################################
DATASG SEGMENT PARA 'Data'
OLD_INT15_VECTOR LABEL DWORD
OLD_INT15_OFF DW ?
OLD_INT15_SEG DW ?
PRESSED_KEY DB 0
DATASG ENDS
;#################################################
;# PROGRAM STARTS HERE
;#################################################
CODESG SEGMENT PARA 'Code'
PRESS_ESC PROC FAR
ASSUME CS:CODESG, DS:DATASG
MOV AX, DATASG
MOV DS, AX
;CALL SET_INT15H
;####################################
MAIN_LOOP:
MOV AL, PRESSED_KEY
CMP AL, KB_ESC
JNE CALL_EXIT_TO_MSDOS
MOV PRESSED_KEY, 0 ;For future use
JMP MAIN_LOOP
CALL_EXIT_TO_MSDOS:
CALL EXIT_TO_MSDOS
JMP MAIN_LOOP ;For future use
PRESS_ESC ENDP
;#################################################
;# SUBROUTINE: EXIT_TO_MSDOS
;#################################################
EXIT_TO_MSDOS PROC NEAR
FLUSH_KEYBOARD:
MOV AH, 0 ;READ FROM KB BUFFER
INT 16H
CMP AH, KB_ESC
JNE FLUSH_KEYBOARD
POP AX
MOV AH, 4CH ; GO TO MSDOS
INT 21H
RET
EXIT_TO_MSDOS ENDP
;#################################################
;# SUBROUTINE: SET_INT15H
;#################################################
SET_INT15H PROC NEAR
PUSH ES ;Save segment reg.
XOR AX, AX ;Point to "zero segment".
MOV ES, AX
MOV BX, 15h * 4 ;Address of INT 15h vector.
MOV AX, ES:[BX] ;Get offset.
MOV OLD_INT15_OFF, AX
MOV AX, ES:[BX + 2] ;Get segment.
MOV OLD_INT15_SEG, AX
CLI ;Disable interrupts.
MOV AX, OFFSET INT_15H_HANDLER
MOV ES:[BX], AX ;Plug in new offset.
MOV AX, CS ;Get our code segment.
MOV ES:[BX + 2], AX ;Plug it in.
STI ;Re-enable interrupts.
POP ES ;Restore segment reg.
RET
SET_INT15H ENDP
;#################################################
;# INTERRUPT HANDLER
;#################################################
INT_15H_HANDLER PROC NEAR
CMP AH, 4Fh
JNE EXIT_HANDLER
PUSH ES
MOV DX, DATASG
MOV ES, DX
MOV ES:PRESSED_KEY, AL
POP ES
EXIT_HANDLER:
JMP FAR PTR OLD_INT15_VECTOR
RET
INT_15H_HANDLER ENDP
CODESG ENDS
END PRESS_ESC
I'm sure the mistake is something stupid. I'm not an expert, nor do I claim to be one.
;CALL SET_INT15H
Should that be commented out? Your handler won't be called as it is :sad:
Quote from: sinsi on February 13, 2024, 12:19:32 PM ;CALL SET_INT15H
Should that be commented out? Your handler won't be called as it is :sad:
Sorry, it's
NOT commented. I commented it later to pinpoint the error, and forgot to uncomment it when I pasted the code here.
Thanks for posting complete code.
You should save and restore DX (PUSH/POP) in your interrupt handler. My bad, should have put that in the code. That's not what's causing the crash, though, because as sinsi pointed out the interrupt handler never gets installed.
What's with the POP AX in the EXIT_TO_MSDOS routine? Seems like that would unbalance the stack.
Quote from: NoCforMe on February 13, 2024, 12:44:04 PMThanks for posting complete code.
You should save and restore DX (PUSH/POP) in your interrupt handler. My bad, should have put that in the code. That's not what's causing the crash, though, because as sinsi pointed out the interrupt handler never gets installed.
What's with the POP AX in the EXIT_TO_MSDOS routine? Seems like that would unbalance the stack.
1) OK. Added PUSH/POP DX.
2) The extra POP AX shouldn't be there. I was doing something before with AX and I PUSH/POP'd it. However, I changed my mind, and forgot to delete that line. It's gone now.
3) The interrupt handler is installed. I commented the line to try to pinpoint the error, but then I forgot to uncomment it when I pasted the code here.
Aargh, just thought of a problem: in giving you instructions on how to redirect an interrupt vector to your program, which does work, I neglected to think about the consequences of this. And here I'm not sure what they are.
The problem is that if you redirect that vector to your program, everything's OK while your program is running. But when it exits, that interrupt vector still points to the place where your ISR used to be, but is probably now occupied by something else. Probably garbage. Which may be why it's crashing, if it crashes upon exit.
I'm not sure what the protocol here is, exactly. Probably the right thing to do would be to restore the INT 15h interrupt vector to its original value before you exit.
Maybe someone with more DOS expertise can give a better answer here.
Restoring the vector is just the opposite of re-vectoring:
CLI
XOR AX, AX ;Point to "zero segment".
MOV ES, AX
MOV AX, OLD_INT15_OFF
MOV BX, 15h * 4 ;Address of INT 15h vector.
MOV ES:[BX], AX ;Set vector offset.
MOV AX, OLD_INT15_SEG
MOV ES:[BX+2], AX ;Set vector segment.
STI
Quote from: NoCforMe on February 13, 2024, 12:55:20 PMAargh, just thought of a problem: in giving you instructions on how to redirect an interrupt vector to your program, which does work, I neglected to think about the consequences of this. And here I'm not sure what they are.
It doesn't crash now after I fixed the issues you brought up. Anyway, when it crashed, it did when executing the program. Now it just stalls. So there's never a chance for the program to exit and return to MSDOS (and crash because the original interrupt vector is not restored).
To see if your interrupt handler is actually called, try writing a few pixels to the screen
screen_address dw 0
;in your int handler
push bx
push es
mov ax,0a000h
mov es,ax
mov bx,cs:screen_address
mov byte ptr [es:bx],1
add cs:screen_address,1
pop es
pop bx
Be aware that when your handler returns, if you want the BIOS to continue processing the keystroke, you have to set the carry flag. You probably don't want to or else the key buffer will overflow and beep at you.
Quote from: sinsi on February 13, 2024, 01:19:11 PMTo see if your interrupt handler is actually called, try writing a few pixels to the screen
screen_address dw 0
;in your int handler
push bx
push es
mov ax,0a000h
mov es,ax
mov bx,cs:screen_address
mov byte ptr [es:bx],1
add cs:screen_address,1
pop es
pop bx
Be aware that when your handler returns, if you want the BIOS to continue processing the keystroke, you have to set the carry flag. You probably don't want to or else the key buffer will overflow and beep at you.
I get a warning on this line:
mov byte ptr [es:bx],1 Warning: ":" operator ignored
If I execute the program, there's no pixel. I also tried this code I had to print a pixel that 100% works, and it doesn't print a pixel:
MOV AH, 0Ch
MOV AL, COLOR
MOV CX, POS_X
MOV DX, POS_Y
MOV BH, 0 ;PAGE
ES:[BX]
Quote from: NoCforMe on February 13, 2024, 03:04:08 PMES:[BX]
It assembles without warning now, but the linker gives two errors:
Fixup overflow at CODESG:0060, target = DATASG:0000 in module ESC.ASM
Fixup overflow at CODESG:0069, target = DATASG:0000 in module ESC.ASMThese are the lines in CODESG:
005D 2E: 8B 1E 0005r mov bx,cs:screen_address
0062 26: C6 07 01 mov byte ptr es:[bx],1
0066 2E: 83 06 0005r 01 add cs:screen_address,1
And this is DATASG:
0000 DATASG SEGMENT PARA 'Data'
0000 OLD_INT15_VECTOR LABEL DWORD
0000 ???? OLD_INT15_OFF DW ?
0002 ???? OLD_INT15_SEG DW ?
0004 00 PRESSED_KEY DB 0
0005 0000 screen_address dw 0
0007 DATASG ENDS
The program executes, but there's no pixel. I tried something simpler. Right after returning from CALL SET_INT15H, I print a character with this:
MOV AH, 09H
MOV BL, 7 ;COLOR
MOV CX, 1 ;NUMBER OF CHARS
MOV AL, 'A' ;CHARACTER
INT 10H
Then inside the handler I repeat the same code but with character 'B'. The program prints an A, but not a B. So it definitely doesn't execute the handler.
I put screen_address in the code segment, for ease of access. You have it in the data segment.
Quote from: sinsi on February 13, 2024, 04:05:59 PMI put screen_address in the code segment, for ease of access. You have it in the data segment.
Oh! I didn't catch that. I corrected it and it still doesn't work. I tried also printing a character and it won't. It prints it after setting interrupt 15h, but not inside the handler.
You probably need a stack, at the moment there is none
STACKSG SEGMENT STACK
db 4096 dup (?) ;make a 4KB stack
STACKSG ENDS
Move the other vars from data to code as well - the code is written so JMP FAR PTR OLD_INT15_VECTOR is probably using the wrong value in DS (which the BIOS is using)
Quote from: sinsi on February 13, 2024, 04:23:46 PMYou probably need a stack, at the moment there is none
Move the other vars from data to code as well - the code is written so JMP FAR PTR OLD_INT15_VECTOR is probably using the wrong value in DS (which the BIOS is using)
All done. No difference :nie:
Quote from: sinsi on February 13, 2024, 04:23:46 PMMove the other vars from data to code as well - the code is written so JMP FAR PTR OLD_INT15_VECTOR is probably using the wrong value in DS (which the BIOS is using)
Yes; so if it's in the code segment, then you'd use
JMP FAR PTR CS:OLD_INT15_VECTOR, right?
Gawd, it's been a long time since I coded any of this stuff ...
One major problem I finally saw with your code - you don't actually set 320x200 mode 13h
Quote from: NoCforMe on February 13, 2024, 06:59:52 PMYes; so if it's in the code segment, then you'd use JMP FAR PTR CS:OLD_INT15_VECTOR, right?
Gawd, it's been a long time since I coded any of this stuff ...
OMG - what a mess!
Do you guys know how a type cast is supposed to work?
I'm asking vecause that "FAR PTR" behind the JMP instruction actually makes label OLD_INT15_VECTOR a
far code label which surely isn't what you want to achieve. OLD_INT15_VECTOR is a normal variable holding a FAR code address, that's something completely different.
The assembler (masm or tasm) is smart enough to know that a DWORD variable in 16-bit will hold a FAR address, so no type cast is needed, just code JMP CS:[OLD_INT15_VECTOR].
Quote from: sinsi on February 13, 2024, 07:18:05 PMOne major problem I finally saw with your code - you don't actually set 320x200 mode 13h
I did after you suggested printing a pixel to check if the handler executed.
Quote from: _japheth on February 13, 2024, 07:41:12 PMQuote from: NoCforMe on February 13, 2024, 06:59:52 PMYes; so if it's in the code segment, then you'd use JMP FAR PTR CS:OLD_INT15_VECTOR, right?
Gawd, it's been a long time since I coded any of this stuff ...
OMG - what a mess!
Do you guys know how a type cast is supposed to work?
I'm asking vecause that "FAR PTR" behind the JMP instruction actually makes label OLD_INT15_VECTOR a far code label which surely isn't what you want to achieve. OLD_INT15_VECTOR is a normal variable holding a FAR code address, that's something completely different.
The assembler (masm or tasm) is smart enough to know that a DWORD variable in 16-bit will hold a FAR address, so no type cast is needed, just code JMP CS:[OLD_INT15_VECTOR].
That's the problem with looking at someone else's block of code. I rewrote it with FASM and it worked as it should have but haven't got around to converting it to MASM (or is it TASM?)
Quote from: sinsi on February 13, 2024, 08:23:54 PMThat's the problem with looking at someone else's block of code. I rewrote it with FASM and it worked as it should have but haven't got around to converting it to MASM (or is it TASM?)
TASM
It seems to work now with JMP CS:[OLD_INT15_VECTOR]. However, I guess I need to uninstall my handler after exiting the program because if I execute it a second time it crashes.
Quote from: popcalent on February 14, 2024, 05:39:57 AMI guess I need to uninstall my handler after exiting the program because if I execute it a second time it crashes.
Yes, you definitely do. Otherwise that interrupt vector is pointing straight into garbage.
I hope you're not getting too frustated here; this is just beginning to be fun.
Hi,
I am not sure this is of interest, but I replied to a question
about interrupt handling a while back.
"Interrupt Hooking in MS DOS"
https://masm32.com/board/index.php?topic=9494.msg103835#msg103835 (https://masm32.com/board/index.php?topic=9494.msg103835#msg103835)
Regards,
Steve N.
Some useful stuff in there for sure.
Hey, I'd forgotten about those DOS (INT 21h) functions to get and set an interrupt vector (35h & 25h respectively). That's a more kosher way to handle it, rather than my method of just "peeking and poking" down there in the "zero segment", which'll work, but better to do it through the OS.
Thanks.
I'll be working on this later this week and get back to you guys.
I created the following procedure to uninstall my handler. It seems to work, but I thought I would post it here for others to see/use. Also, it's possible the handler has errors that didn't manifest during my tests.
UNSET_INT15H PROC NEAR
PUSH DX
PUSH ES ;Save segment reg.
XOR AX, AX ;Point to "zero segment".
MOV ES, AX
MOV BX, 15h * 4 ;Address of INT 15h vector.
CLI ;Disable interrupts.
MOV AX, OLD_INT15_OFF ; Restore original offset.
MOV ES:[BX], AX
MOV AX, OLD_INT15_SEG ; Restore original segment.
MOV ES:[BX + 2], AX
STI ;Re-enable interrupts.
POP ES ;Restore segment reg.
POP DX
RET
UNSET_INT15H ENDP
Just for convenience, here's the SET_INT15H procedure again:
SET_INT15H PROC NEAR
PUSH DX
PUSH ES ;Save segment reg.
XOR AX, AX ;Point to "zero segment".
MOV ES, AX
MOV BX, 15h * 4 ;Address of INT 15h vector.
MOV AX, ES:[BX] ;Get offset.
MOV OLD_INT15_OFF, AX
MOV AX, ES:[BX + 2] ;Get segment.
MOV OLD_INT15_SEG, AX
CLI ;Disable interrupts.
MOV AX, OFFSET INT_15H_HANDLER
MOV ES:[BX], AX ;Plug in new offset.
MOV AX, CS ;Get our code segment.
MOV ES:[BX + 2], AX ;Plug it in.
STI ;Re-enable interrupts.
POP ES ;Restore segment reg.
POP DX
RET
SET_INT15H ENDP
This is the handler code:
INT_15H_HANDLER PROC NEAR
CMP AH, 4Fh
JNE EXIT_HANDLER
PUSH ES
MOV DX, DATASG
MOV ES, DX
MOV ES:PRESSED_KEY, AL
POP ES
EXIT_HANDLER:
JMP CS:[OLD_INT15_VECTOR]
RET
INT_15H_HANDLER ENDP
The handler puts the scan code of the pressed key in PRESSED_KEY, and then I take care of it in the main loop. The main loop sees the scan code and pushes a direction (R, L, U, D) into the buffer if the corresponding arrow key has been pressed, or pops a direction from the buffer if the corresponding arrow key has been released. The sprite moves in the direction stored in the first position of the buffer, or doesn't move if the buffer is empty.
Now, I know we've mentioned that the handler has to be as short as possible, but it occurred to me that if push/pop stuff into/out of the buffer outside the handler, it's possible that a key is pressed before the current key stroke is taken care of in the main loop. The only way to avoid this that I can think of is to manipulate the buffer inside the handler, but the code is pretty long for a handler... I'm not sure what to do.
Quote from: NoCforMe on February 09, 2024, 10:40:34 AMWhy don't you try my suggestion and see what it does for you? (Including inlining your PUT_PIXEL_IN_BUFFER code.)
I finally got around fixing this issue. Here it is...
PRINT_256SPRITE PROC NEAR
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH ES
PUSH DI
PUSH DS
PUSH SI
MOV CX, MODE13H_X ;XCOORD
MOV DX, CX
MOV BX, MODE13H_Y ;YCOORD
;PREPARE BUFFER AND BUFFER INDEX (SI)
MOV AX, SEG SBUFFER
MOV ES, AX
ASSUME ES:SBUFFER
;MOV SI, OFFSET SCREEN_BUFFER
;FIND ABSOLUTE COORDINATE OF FIRST PIXEL
MOV AX, MODE13H_Y
MOV BX, MODE13H_Y
SHL AX, 8
SHL BX, 6
ADD AX, BX
ADD AX, MODE13H_X
MOV MODE13H_X, AX
MOV MODE13H_X2, AX ;MODE13H_X2 HAS THE ORIGINAL ABSOLUTE COORD
PRINT_256SPRITE_LOOP:
CMP BYTE PTR [DI],'$' ;CHECK END OF SPRITE
JE PRINT_256SPRITE_END
;PREPARE BUFFER AND BUFFER INDEX (SI)
;MOV AX, SEG SBUFFER
;MOV ES, AX
;ASSUME ES:SBUFFER
MOV SI, OFFSET SCREEN_BUFFER
MOV AX, MODE13H_X
ADD SI, AX
MOV AL, BYTE PTR [DI] ;GET PIXEL COLOR
MOV ES:[SI], AL ;PUT PIXEL COLOR
INC DI
INC MODE13H_X
CMP BYTE PTR [DI], '#' ;CHECK END OF LINE
JNE PRINT_256SPRITE_LOOP
MOV AX, MODE13H_X2 ;RECOVER ABSOLUTE COORDINATE
ADD AX, 140h ;ADD 320 TO GO TO NEXT LINE SAME X
MOV MODE13H_X2, AX ;SAVE NEW ABSOLUTE COORDINATE IN X2
MOV MODE13H_X, AX ;SAVE NEW ABSOLUTE COORDINATE IN X
;INC MODE13H_Y
INC DI ;SKIP '#'
JMP PRINT_256SPRITE_LOOP
PRINT_256SPRITE_END:
POP SI
POP DS
POP DI
POP ES
POP DX
POP CX
POP BX
POP AX
RET
PRINT_256SPRITE ENDP
I still need to figure out this part:
MOV AL, BYTE PTR [DI] ;GET PIXEL COLOR
MOV ES:[SI], AL ;PUT PIXEL COLOR
and see if I can do MOVSW because I'm not sure if all sprites have an even number of pixels per row.
Quote from: popcalent on February 18, 2024, 12:57:52 PMI still need to figure out this part:
MOV AL, BYTE PTR [DI] ;GET PIXEL COLOR
MOV ES:[SI], AL ;PUT PIXEL COLOR
and see if I can do MOVSW because I'm not sure if all sprites have an even number of pixels per row.
You could switch SI and DI and use
MOVSB, which will also increment both SI and DI. Should be a little bit faster.
You could use
MOVSW to move
most of your data, then if there's a leftover byte at the end use
MOVSB. Would require a little more calculation code, but not much.
Something like this:
; Move words:
; Set up SI & DI.
MOV CX, <sprite data len>
SHR CX, 1 ;/2.
REP MOVSW
TEST <sprite data len>, 1 ;Even or odd?
JZ isEven ; Even.
; Move extra byte @ end:
MOVSB
isEven: . . .
I haven't had a chance to post because I've been busy, but I'm still working on my keyboard interrupt program. At the moment, I made it so when I press a key, the handler checks if it's interrupt 4Fh, and if it is, it puts AL in PRESSED_KEY. It also prints AL plus 48, but this is for debugging and it will eventually go.
Then the main loop evaluates PRESSED_KEY, and if it's ESC, it exits the program, and, if it isn't, it loops. The problem is that sometimes I press ESC, the handler does but it's supposed to do, but the loop doesn't. I thought that this is because I press ESC after the loop evaluates PRESSED_KEY, then I release it before the loop evaluates PRESSED_KEY again. So the ESC scan code is never evaluated. However, I've tried pressing ESC and not releasing, and it takes a couple loops for the program to exit.
I thought that I could make it so if ESC is released, the handler does nothing, so the loop can evaluate ESC in the next loop. However, this doesn't solve anything... There are instances where I have to press ESC three or four times for the program to exit, and then the DOS prompt has three or four "\", each one resulting from each pressed ESC...
This is the code:
;#################################################
;#
;# PRESS_ESC.ASM
;#
;#################################################
;#################################################
;# DEFINITIONS
;#################################################
KB_ESC EQU 01h
REL_ESC EQU 81h
;#################################################
;# STACK
;#################################################
STACKSG SEGMENT STACK
db 4096 dup (?) ;make a 4KB stack
STACKSG ENDS
;#################################################
;# DATA
;#################################################
DATASG SEGMENT PARA 'Data'
DATASG ENDS
;#################################################
;# PROGRAM STARTS HERE
;#################################################
CODESG SEGMENT PARA 'Code'
PRESS_ESC PROC FAR
screen_address dw 0
OLD_INT15_VECTOR LABEL DWORD
OLD_INT15_OFF DW ?
OLD_INT15_SEG DW ?
PRESSED_KEY DB 0
ASSUME CS:CODESG, DS:DATASG
MOV AX, DATASG
MOV DS, AX
CALL MODE13H_SET
CALL SET_INT15H
MAIN_LOOP:
MOV AL, PRESSED_KEY
CMP AL, KB_ESC
JE CALL_EXIT_TO_MSDOS
MOV PRESSED_KEY, 0 ;For future use
JMP MAIN_LOOP
CALL_EXIT_TO_MSDOS:
CALL EXIT_TO_MSDOS
JMP MAIN_LOOP ;For future use
PRESS_ESC ENDP
;#################################################
;# SUBROUTINE: EXIT_TO_MSDOS
;#################################################
EXIT_TO_MSDOS PROC NEAR
FLUSH_KEYBOARD:
MOV AH, 0 ;READ FROM KB BUFFER
INT 16H
CMP AH, KB_ESC
JNE FLUSH_KEYBOARD
CALL MODE03H_SET
CALL UNSET_INT15H
MOV AH, 4CH ; GO TO MSDOS
INT 21H
RET
EXIT_TO_MSDOS ENDP
;#################################################
;# SUBROUTINE: UNSET_INT15H
;#################################################
UNSET_INT15H PROC NEAR
PUSH DX
PUSH ES ;Save segment reg.
XOR AX, AX ;Point to "zero segment".
MOV ES, AX
MOV BX, 15h * 4 ;Address of INT 15h vector.
CLI ;Disable interrupts.
MOV AX, OLD_INT15_OFF ; Restore original offset.
MOV ES:[BX], AX
MOV AX, OLD_INT15_SEG ; Restore original segment.
MOV ES:[BX + 2], AX
STI ;Re-enable interrupts.
POP ES ;Restore segment reg.
POP DX
RET
UNSET_INT15H ENDP
;#################################################
;# SUBROUTINE: SET_INT15H
;#################################################
SET_INT15H PROC NEAR
PUSH DX
PUSH ES ;Save segment reg.
XOR AX, AX ;Point to "zero segment".
MOV ES, AX
MOV BX, 15h * 4 ;Address of INT 15h vector.
MOV AX, ES:[BX] ;Get offset.
MOV OLD_INT15_OFF, AX
MOV AX, ES:[BX + 2] ;Get segment.
MOV OLD_INT15_SEG, AX
CLI ;Disable interrupts.
MOV AX, OFFSET INT_15H_HANDLER
MOV ES:[BX], AX ;Plug in new offset.
MOV AX, CS ;Get our code segment.
MOV ES:[BX + 2], AX ;Plug it in.
STI ;Re-enable interrupts.
POP ES ;Restore segment reg.
POP DX
RET
SET_INT15H ENDP
;#################################################
;# INTERRUPT HANDLER
;#################################################
INT_15H_HANDLER PROC NEAR
CMP AH, 4Fh
JNE EXIT_HANDLER
CMP AL, REL_ESC
JE EXIT_HANDLER
;PRINT SCAN CODE
MOV AH, 09H
MOV BL, 7 ;COLOR
MOV CX, 1 ;NUMBER OF CHARS
ADD AL, 48
INT 10H
SUB AL, 48
PUSH ES
MOV DX, DATASG
MOV ES, DX
MOV ES:PRESSED_KEY, AL
POP ES
EXIT_HANDLER:
JMP CS:[OLD_INT15_VECTOR]
RET
INT_15H_HANDLER ENDP
;#################################################
;# SUBROUTINE: MODE13H_SET
;#################################################
MODE13H_SET PROC NEAR
PUSH AX
MOV AX, 13H
INT 10H
POP AX
RET
MODE13H_SET ENDP
;#################################################
;# SUBROUTINE: MODE03H_SET
;#################################################
MODE03H_SET PROC NEAR
PUSH AX
MOV AX, 03H
INT 10H
POP AX
RET
MODE03H_SET ENDP
CODESG ENDS
END PRESS_ESC
Problems:- You still seem confused about segment usage here. All your data is in your code segment (including PRESSED_KEY), but you seem to be accessing it wrong: you have ASSUME CS:CODESG, DS:DATASG, which I think means that any data accesses (like PRESSED_KEY) will be done using DS, which is wrong, since you have it pointing to that (empty) data segment.
Just to be sure, I would use explicit segment overrides for that data that's in the code segment:
MOV AL, CS:PRESSED_KEY
MOV CS: PRESSED_KEY, 0 For future use
- Your code segment has data at its entry point. That "data" will be executed as code, with who knows what effect. You should either move the data to the end of that segment, after your return to DOS, or put a jump around it:
PRESS_ESC: JMP realEP ;Program entry point
; Data in code segment:
screen_address dw 0
OLD_INT15_VECTOR LABEL DWORD
OLD_INT15_OFF DW ?
OLD_INT15_SEG DW ?
PRESSED_KEY DB 0
realEP: ;Actual code entry point
- You have no data in your DATA segment, so just get rid of it. (No requirement that a program have a data segment.)
I'm also not sure why you have your entry-point code set up as a PROC, since you will never return from it. Once you exit to DOS w/INT 21/AH=4Ch, that's the end of the show; no more of your code will execute after that, so there's no need of any
RETs.
None of which might explain why you're having problems, but still things that should be fixed.
Thanks for your help.
Quote from: NoCforMe on February 28, 2024, 08:06:35 AMJust to be sure, I would use explicit segment overrides for that data that's in the code segment:
Corrected
Quote from: NoCforMe on February 28, 2024, 08:06:35 AMYour code segment has data at its entry point. That "data" will be executed as code, with who knows what effect. You should either move the data to the end of that segment, after your return to DOS,
- You have no data in your DATA segment, so just get rid of it. (No requirement that a program have a data segment.)
Ok. I put all the data after the JMP MAIN_LOOP, which is a portion of code that is unreachable.
Quote from: NoCforMe on February 28, 2024, 08:06:35 AMI'm also not sure why you have your entry-point code set up as a PROC, since you will never return from it. Once you exit to DOS w/INT 21/AH=4Ch, that's the end of the show; no more of your code will execute after that, so there's no need of any RETs.
I know. It's just that I end every procedure with a RET out of good practice.
Quote from: NoCforMe on February 28, 2024, 08:06:35 AMNone of which might explain why you're having problems
Unfortunately, you're right.
Hmm; OK, I went through your code again. If you didn't change the code at the end of your INT 15h handler, then that needs changed:
PUSH ES
MOV DX, DATASG
MOV ES, DX
MOV ES:PRESSED_KEY, AL
POP ES
should be just:
MOV CS:PRESSED_KEY, AL
Again, confusion over segments. Since that variable is in the code segment, just use the CS: segment override to access it.
Also, your interrupt handler should be as lean and mean as possible. Don't call any OS services from it, like INT 10. If you want to display a character, do it in your code outside of the ISR. You want to spend as little time as possible inside the ISR.
Other than that, I can't see anything wrong ...
Quote from: NoCforMe on February 28, 2024, 12:25:32 PMAgain, confusion over segments. Since that variable is in the code segment, just use the CS: segment override to access it.
Yes, that's a remnant from when PRESSED_KEY was in DS.
Quote from: NoCforMe on February 28, 2024, 12:25:32 PMAlso, your interrupt handler should be as lean and mean as possible. Don't call any OS services from it, like INT 10. If you want to display a character, do it in your code outside of the ISR. You want to spend as little time as possible inside the ISR.
Yeah, that's there just for debugging, but it has to go. I actually just commented it.
Quote from: NoCforMe on February 28, 2024, 12:25:32 PMOther than that, I can't see anything wrong ...
Unfortunately, it doesn't work. Sometimes, I press ESC and the program exits immediately. Sometimes, I have to press ESC multiple times, then there are as many slashes on the DOS prompt as times I pressed ESC... How is this so complicated? All professional games and many homebrews have immediate response to cursor keys.What are they doing that is so obscure that I can't do?
Are you running under an emulator?
Are you building under an emulator?
TASM?
I've tested it with FASM and it works properly, even moves a sprite around (at 18 pixels per second :biggrin: )
I hear your frustration.
Scan code: you're apparently checking for a release code (81h) which may or may not come through INT 15h. Have you tried using the value 1, which is the scan code for ESC (http://www.manmrk.net/tutorials/basic/FBASIC/html/GfxScancodes.html)?
Quote from: sinsi on February 28, 2024, 01:58:50 PMAre you running under an emulator?
Are you building under an emulator?
TASM?
Yes to both.
Quote from: sinsi on February 28, 2024, 01:58:50 PMI've tested it with FASM and it works properly,
The code I posted?
Quote from: NoCforMe on February 28, 2024, 02:00:41 PMI hear your frustration.
Scan code: you're apparently checking for a release code (81h) which may or may not come through INT 15h. Have you tried using the value 1, which is the scan code for ESC (http://www.manmrk.net/tutorials/basic/FBASIC/html/GfxScancodes.html)?
The reason I check for release of ESC (81h) is because I thought my code wasn't working due to the fact that sometimes I would press ESC when the IP was after the "CMP AL, KB_ESC" in the MAIN_LOOP, and then I would release ESC when the IP was before the "CMP AL, KB_ESC" without giving the code in the MAIN_LOOP a chance to evaluate the scan code for ESC. So I thought I would not store the release code for ESC to make sure that, once I press ESC, it gets evaluated in the MAIN_LOOP (as opposed to being overwritten by the release code of ESC before being evaluated). The program doesn't work either way and it shows the same behavior.
And yes, I assume that the loop is so fast that there's no chance I press and release ESC within the same iteration of the loop, but I didn't know what else to try.
OK, some (possibly) bad news, but maybe this'll help eventually:
Looks like using INT 15/AH = 4Fh might not be the way to go. I just looked at a page (https://stanislavs.org/helppc/int_15-4f.html) which says:
QuoteAH = 4F
AL = scan code
CF = set to 1 (via STC instruction)
on return
AH = 80h, CF set (PC, PCjr)
= 86h, CF set (XT BIOS 11/8/82, AT BIOS 1/10/84)
AL = CF set, new scan code
= CF clear, original scancode
- available with XT BIOS after 11/8/82, AT BIOS after 1/10/84
- called by INT 9, makes allowance for keyboard translation
- normally returns the scan code in AL, with CF set
- if function returns with CF clear, INT 9 ignores keystroke
- do not rely on this function being called for each INT 9 since
any user INT 9 handler can exit prematurely and circumvent
this function
So maybe you do need to hook the actual keyboard BIOS interrupt (INT 9) and store keycodes there. I know that's do-able.
Also, question for @sinsi: Are we doing wrong here by
JMPing to the old INT 15h vector? Should we instead be just doing an
IRET inside the ISR? (Assuming we continue using INT 15h.)
Which emulator? Some of them are known for timing issues which can affect keyboard input.
The FASM code I have uses a 128-byte key array, not just a single byte, but when I converted your code it exited as soon as ESC was pressed.
Hello,
it's an interesting "race condition" problem. You're writing to variable PRESSED_KEY in both your main loop and your interrupt procedure. That cannot work without some "synchronization". The best and simplest approach of course is NOT to write to that variable in your main loop.
OK, another approach: instead of hooking any interrupts, just poll the keyboard in a tight loop looking for your keystrokes:
QuoteINT 16h, AH = 01
Returns:
ZF = 0 if a key pressed (even Ctrl-Break)
AX = 0 if no scan code is available
AH = scan code
AL = ASCII character or zero if special function key
Tight loop:
tloop: MOV AH, 1
INT 16H
TEST AX, AX
JZ tloop
; Key code/char. is in AH/AL:
(This one doesn't wait for a keystroke but returns immediately.)
BTW, forget about hooking INT 9, as keycodes are nowhere to be found (the ISR places the latest keystroke in a circular buffer).
Quote from: _japheth on February 28, 2024, 02:53:23 PMHello,
it's an interesting "race condition" problem. You're writing to variable PRESSED_KEY in both your main loop and your interrupt procedure. That cannot work without some "synchronization".
If I remember the BIOS code, the first instruction is STI, then a read from port 60, then an INT 15 call, so an overwrite is possible.
QuoteThe best and simplest approach of course is NOT to write to that variable in your main loop.
Quote from: sinsi on February 28, 2024, 02:41:04 PMWhich emulator? Some of them are known for timing issues which can affect keyboard input.
The FASM code I have uses a 128-byte key array, not just a single byte, but when I converted your code it exited as soon as ESC was pressed.
I'm using dosbox. I have a 386 PC104 board here that I want to assemble with some parts to have an MSDOS working computer, but I need to find the time to do so.
Quote from: _japheth on February 28, 2024, 02:53:23 PMHello,
it's an interesting "race condition" problem. You're writing to variable PRESSED_KEY in both your main loop and your interrupt procedure. That cannot work without some "synchronization". The best and simplest approach of course is NOT to write to that variable in your main loop.
Without the variable write in the main loop, it seems to work (I just tried it a couple of times). However, as of now, the program only checks if ESC has been pressed, and if so, it exits. The aim is to evaluate the cursor keys. The motivation to write the variable with a zero in the main loop is that the next iteration of the loop does not evaluate the same value of the variable, even when no new key has been pressed. Without that write, the loop would evaluate a scan code, a release code, and every subsequent iteration of the loop would evaluate that same release code until a new key is pressed.
Quote from: NoCforMe on February 28, 2024, 03:16:16 PMOK, another approach: instead of hooking any interrupts, just poll the keyboard in a tight loop looking for your keystrokes:
tloop: MOV AH, 1
INT 16H
TEST AX, AX
JZ tloop
; Key code/char. is in AH/AL:
Iirc, this was my first approach. However the problem is that when you press a cursor key, the sprite moves towards the direction of the cursor key, stalls, then it moves without stalling. If you press another cursor key while another one is pressed, then the sprite stalls.
If you don't understand what I mean by "moves, stalls, moves without stalling", try pressing a letter key without releasing anywhere where you can enter text (I just tried here while writing this comment). You'll see the letter appears once, then it stalls, then a bunch of that letter appear without interruption.
Quote from: popcalent on February 28, 2024, 03:56:40 PMWithout the variable write in the main loop, it seems to work (I just tried it a couple of times). However, as of now, the program only checks if ESC has been pressed, and if so, it exits. The aim is to evaluate the cursor keys. The motivation to write the variable with a zero in the main loop is that the next iteration of the loop does not evaluate the same value of the variable, even when no new key has been pressed. Without that write, the loop would evaluate a scan code, a release code, and every subsequent iteration of the loop would evaluate that same release code until a new key is pressed.
For gaming, the best approach - as sinsi already has mentioned - is to maintain an array of 128 bits/bytes that tell you the status of all keys at any time
Here's an example how you could implement that in your int 15h interrupt proc, using 80386 instructions:
push ax
mov ah, 0
btr ax,7 ; pressed or released?
jc released
bts cs:[keybittab], ax
jmp done
released:
btr cs:[keybittab], ax
done:
pop ax
with the bit array defined as:
keybittab dw 8 dup (?)
And then, if you want to know the current status of the ESC key, just code
bt cs:[keybittab], 1
jc is_pressed
I want to back up a bit here and go back over some ground again. Hopefully this won't add to the confusion here.
Thing is, I may have misunderstood what the OP is trying to do here (and others may have as well).
This all concerns keyboard handling for his game program. I'm now wondering if using any of the BIOS functions is the wrong way to go here. Why? Because apparently the OP needs to handle the case where one key is pressed and then another key is pressed without releasing the first key. I believe this falls under the heading of key "rollover", and I don't think the BIOS handles this correctly for his application. (I could be wrong here.)
So let me ask @popcalent: is this the case? Do you need to be able to handle a new keypress with another key already held down?
Another reason not to use the BIOS is that it automatically goes into "typematic" mode when a key is held down, generating multiple keypresses from that key. I wonder if there's some way to disable this behavior when the game program is running.
If it's true that this kind of keyboard handling is required, then do the DOS experts here know whether this case can be handled correctly using BIOS functions (like INT 15h/AH = 4Fh)?
If not, then how about this: go to the lowest-level interface and access the keyboard through port 60 (and 64) to get the immediate status. Here's a page describing that interface (https://www.plantation-productions.com/Webster/www.artofasm.com/DOS/ch20/CH20-2.html).
Quote from: NoCforMe on February 29, 2024, 11:54:02 AMSo let me ask @popcalent: is this the case? Do you need to be able to handle a new keypress with another key already held down?
Yes. This is the case. I was almost able to achieve this using a buffer as described here:
Quote from: popcalent on February 12, 2024, 09:06:00 AM.
, but the problem was that sometimes the program did not register key releases and the sprite kept moving.
QuoteAnother reason not to use the BIOS is that it automatically goes into "typematic" mode when a key is held down, generating multiple keypresses from that key
Nope, it's the controller in hardware. That's one of the many reasons to use low-level BIOS routines.
If th OP wants to handle more than one key, a 128-slot buffer is really the only way.
Well, that might be true.
I'm getting curious enough about this issue to maybe try some coding. Problem is that I don't have any kind of DOS emulator. Is there something I can install on my Windows 7 computah that will properly emulate DOS? DosBox? vDos?
What I'm thinking of doing is coding a key-logging program to just spit out all keystroke events to a file. Should be low enough latency to capture all keyboard events. That might be able to prove a method that will work for the OP.
Hi,
Quote from: NoCforMe on March 01, 2024, 07:00:50 AMWell, that might be true.
I'm getting curious enough about this issue to maybe try some coding. Problem is that I don't have any kind of DOS emulator. Is there something I can install on my Windows 7 computah that will properly emulate DOS? DosBox? vDos?
I am using Oracle's VirtualBox with Windows 8.1. Not exactly DOS but
a version of OS/2 that runs DOS programs. Seems to work here.
Or, if your system allows, just boot up DOS from a bootable media.
That has worked with older computers for me.
Regards,
Steve N.
Quote from: FORTRANS on March 01, 2024, 11:49:44 PMI am using Oracle's VirtualBox with Windows 8.1. Not exactly DOS but
a version of OS/2 that runs DOS programs. Seems to work here.
Or, if your system allows, just boot up DOS from a bootable media.
That has worked with older computers for me.
I am using dosbox an switch to energy setting that stops turbo increase clock freqency,to stop it from change speed
But even when i had my old 300 mhz p2 cpu,dont you return to same old problem old dos games run superfast if you boot dos ?
Now my slowest is 2 ghz cpu
OP intention seem to run it on old 386
OK, here's something to maybe chew on: I finally got a test program working in DOSBox. It's a key-logging program that uses INT 15h/AH=4Fh to capture keycodes to a buffer, then to write them to a log file after ESC is pressed (or the buffer is full).
Interesting, to say the least: first of all, I'm not sure how much of the behavior of this app is due to just the weirdness of running DOSBox on a (fairly) modren computah (Intel Core 2 Duo). Anyhow, here's what I've seen:
You get (at least) 2 keycodes per key, one for pressing, the other for releasing. If you just hit ESC right away you get this:
Key: 009C
Key: 0001
(keycodes in hex, obviously)
OK, fine. But look what happens if I run the logger, hit the right arrow key ONCE, then ESC:
Key: 009C
Key: 00E0
Key: 004D
Key: 00E0
Key: 00CD
Key: 0001
The cursor keys generate 2 keycodes each for press & release. But what's weird is that the ESC press keycode is first in the buffer, even though it was pressed AFTER the arrow key, while the release code is at the end as expected.
Have I created a time machine here? I've been over and over the code, and I'm 98% sure it's correct. (Source & executable attached here.)
Anyhow, play around with this and see what you think. Maybe someone else can figure this out. You can try testing key rollover with this (hold down one key and press another).
About the source: fairly straightforward 16-bit code. The complicated stuff at the end is my sprintf() function 'cause I like to see formatted output; you can just ignore it (or use it if you like).
The controller needs some way of distinguishing between two keys that are the same (e.g. ctrl).
That's what the E0 is, a prefix byte.
In your case the keyboard sent the make code for the keypad right arrow (E0 4D) then the break code (E0 CD). If you are totally bypassing the BIOS, see what byte sequence the pause key sends.
Key: 009C esc released edit: WRONG it's the enter key
Key: 00E0 prefix
Key: 004D right pressed
Key: 00E0 prefix
Key: 00CD right released
Key: 0001 esc presewed
OK, I get the prefix codes; that's what I expected.
What I can't wrap my head around is the sequence of these codes. First of all, you say that the first code (9C) is the ESC release code; are you sure? Shouldn't that be the make code? How could the release code come before the make code?
You looked at my source, yes? I'm buffering the keycodes in the order they occur from INT 15h/AH=4Fh, and I'm writing them to the log file in the same order. So how come they're ... out of order?
Blind as a bat lol.
9C is the release code for enter
Makes sense when you think about it...
... ohhhhh, <Enter> from starting the program at the DOSBox prompt? And 01 is the make code for ESC. (Because that's what I'm looking for to exit the key-logging loop.)
OK, my head doesn't hurt so much now. My only disappointment is that there's no time machine there ...
More proof of concept: I ran my keylogger again. Hit the following keys as quickly as possible:
- Right arrow down
- Left arrow down (r.arrow held down)
- Release right arrow (l.arrow held down)
- Release left arrow
Got this in the log file:
Key: 00E0 - r.arrow down
Key: 004D - " "
Key: 00E0 - l.arrow down
Key: 004B - " "
Key: 00E0 - r.arrow up
Key: 00CD - " "
Key: 00E0 - l.arrow up
Key: 00CB - " "
Key: 0001 - <Esc>So it works. OP, you can use something like this scheme in your game.
Suggestion: set a flag or three, use them to drive your sprite-moving code.
(I think this can be done without a 128-key map, though that is one option. Anything beyond this in game design is far out of my wheelhouse.)
Oh, one more thing: in the INT 15h ISR, add this (the
XOR AL, AL, which also clears the carry flag) to throw the keystrokes away so you don't get a million keys coming up when the program exits:
INT15_ISR LABEL FAR
; We're looking for AH=4Fh:
CMP AH, 4Fh
JNE isr99
; Stash keycode:
MOV CS:Keycode, AL
XOR AL, AL ;Throw keycode away.
isr99: JMP CS:[OldINT15Vector]
in my code I have .inc file with lots of VK_ESCAPE codes for DOS,so you can use it for easy port between windows 32 or 64 bit and DOS 16 bit
Quote from: popcalent on February 28, 2024, 03:52:41 PMI'm using dosbox.
Same here.
Here's something for ya: a working demo that moves a sprite around on the screen. Proves that the INT 15h/AH=4Fh interface works. It's pretty dumbass: uses all 4 cursor keys to move the sprite 1 pixel at a time (the right cursor moves it more just for ha-has). I'm simply erasing the sprite for each move, then redrawing it.
I was thinking about that this am: how in a real game you'd need to implement transparency in the sprite, which would complicate things. Where I'm able to do a block move here (using
REP MOVSW), you'd have to do a pixel-by-pixel move, watching out for your transparency color. It could be done, but would be slower.
Anyhow, lemme know whatcha think.
Here's something else for you, @popcalent: this little utility shows you all 256 colors in color mode 13. Works for me in DOSBox. Hit any key to toggle between showing the color #s and just showing color blocks. ESC exits.
Heh; what d'you think of my nice blocky number font? Created it myself with my font maker program.
Hey!
I've been disappeared for a while. Sorry about that. I've been dealing with other stuff. I'll try to get back to this project as soon as possible. Thank you very much to all who contributed and will continue to help!