Episode 18: A couple quick fixes

We have some hard work coming in the next few episodes. Before we get there, let's get a couple incidentals out of the way.
Reclaiming space
Recall in the last few episodes we shrank our scene by 32 pixels vertically to give us room for text interaction at the bottom. Since our picture is smaller we can reduce the sizes of our background and compositing buffers, giving us more stack space.
Before, each buffer took up 32,000 bytes (320*200 pixels / 2 pixels per byte). Now, each buffer only needs 26,880 bytes. Here are our new segment locations:
- Framebuffer: Always at segment
1800h
(address 18000h) - Compositor: Segment
1170h
(18000h - 26,880 = 11700h) - Background: Segment
ae0h
(11700h - 26,880 = ae00h)
The background buffer previously started at segment 860h, so we've reclaimed over 10k bytes!
Stack Management
Recall our "stack management" code at the beginning of the program:
; Stack management - Move stack pointer down out of the way
; so we have three 32KB buffer regions free
mov bx, ss
mov ax, [BACKGROUND_SEG]
sub ax, bx
mov cl, 4
shl ax, cl
mov sp, ax
xor ax, ax
push ax
Do you remember why we did this? Recall how DOS loads COM programs. All the segment pointers are set to the same value, the start of the program, and the stack pointer is positioned at the top of the segment:

On my IBM PCjr with DOS 2.1, my program usually gets loaded at 6070h in memory. That would put the top of the stack right in the middle of the compositor buffer! We can't have that, so we move our stack pointer down to just below the start of the background buffer.
But in newer versions of DOSBox (I'm using DOSBox Staging 0.83), the behavior of older versions of DOS and the memory layout of the PCjr is not very well emulated. DOSBox usually loads my program at 0x24540, in memory that doesn't exist on my PCjr. This is way above the top of the framebuffer, so no stack manipulation is needed.
The following routine checks the location of SS and SS+SP, shrinking SP down if necessary to keep it below our buffers:
; Stack management - The stack pointer will likely be in the middle of our framebuffers. If so, move
; it to just before the start of the first buffer. If not (DOSBOX will probably load us above the
; video memory), skip.
stack_fix:
mov ax, ss
cmp ax, [FRAMEBUFFER_SEG]
jg .end ; If past the end of the framebuffer (SS > FRAMEBUFFER_SEG), all good
; Else, if past the beginning of our buffers (SS:SP > BACKGROUND_SEG), move SP down appropriately
mov cl, 4
shl ax, cl
add ax, sp ; Absolute SP
cmp ax, [BACKGROUND_SEG]
jl .end
mov ax, [BACKGROUND_SEG]
mov bx, ss
sub ax, bx
mov cl, 4
shl ax, cl
pop cx ; Grab the IP so we can move it with the stack
mov sp, ax ; SP = (BACKGROUND_SEG - SS) << 4
xor ax, ax
push ax ; Push our zero word that DOS expects
push cx
.end:
ret
Notice that before we change the stack pointer, we pull off the top value (this will be the return address). Then we recreate the stack by pushing on the zero word and the IP to return to. We don't have to worry about anything else being on the stack since this is the first thing our program will do.
Memory Allocation
But there's an issue with all this new space we've freed up. Recent versions of DOSBox won't actually let us use it. This is not a problem for us right now, but we'll go ahead and take care of it for future episodes.
From what I understand, when DOS loads a COM program, that program is supposed to have access free reign over all memory from the program segment on up, which means we should be able to put anything in the space between the end of our program code and the stack. That gives us several thousand bytes of memory at least. But when trying to read a whole file of several KB into that memory using DOS function INT 21, 3f, DOSBOX just... froze. It seemed to do fine on the PCjr. I struggled with this for the longest time before finding a solution.
DOS uses Memory Control Blocks (MCBs) to track memory usage. Each loaded DOS module (such as COMMAND.COM) has one or more MCBs, and when our program is loaded it gets its own MCB. With the special debug version of DOSBox you can list them:

Our program is loaded at segment 2454h (memory location 24540h). Notice the MCB segment is 2453h – 16 bytes below 2454h – this 16 bytes is contains the MCB's description (start, length, label). But instead of getting all the remaining memory, DOS only wants to give us 1712 bytes?
You might think this doesn't matter; after all we've been writing to our screen buffers and the stack, which are above this area, with no problems. But it seems that if you use a DOS interrupt routine to write to these areas, and they're not under the purview of the MCB we've been given, DOSBox will just freeze.
I'm sure DOSBox is at fault here, after all there are no problems when run on my actual PCjr. But to make DOSBox happy, we use DOS interrupt INT 21h, 4a to expand our allocated memory block to the entire segment:
; dosbox_fix(): If we're running in DOSBox, expand our tiny memory allocation to the whole segment.
dosbox_fix:
push ds ; If DOSBox, f000:e061 says 'DOSBox'. Just check the first byte for simplicity.
mov si, 0f000h
mov ds, si
mov si, 0e061h
cmp byte [ds:si], 'D'
pop ds
je .is_dosbox
ret
.is_dosbox:
mov bx, 1000h
mov ax, 4a00h
int 21h ; Reallocate segment at ES (our PSP) to 64k
mov cl, 4
shl bx, cl
pop cx ; Grab the IP so we can move it with the stack
mov sp, bx ; Set the stack to the top of whatever was allocated
sub ax, ax
push ax ; Push our zero word that DOS expects
push cx
ret
We only want to do this if we're running under DOSBox, so we'll check a known location in DOSBox's fake BIOS to verify. The BIOS ROM, readable from segment f00h, has the word "DOSBox" at e061h. We'll check only the first letter for simplicity.
After the reallocation, we move our stack pointer to the top of the newly-allocated memory. (We will call stack_fix
after this, which will probably move the stack pointer again).
And the result – we now have the whole segment allocated:

Plonk these two functions in a new file and call them at the beginning:
call dosbox_fix
call stack_fix
Now we're well-prepared for the big task to come next: Vector drawing.
As always, full source code for this episode is available on GitHub: https://github.com/josh2112/pcjr-asm-game/tree/episode-18