Category : C Source Code
Archive   : IMDSRC78.ZIP
Filename : SWAP.ASM

 
Output of file : SWAP.ASM contained in archive : IMDSRC78.ZIP
page 60, 132

; SWAP.ASM Version 3.01 January 4, 1991
; SWAP.ASM Version 3.00 October 4, 1990
;
; Contains code and data needed to swap most of the current program out
; to extended memory, expanded memory, or disk; execute another program;
; and re-load the original program back into memory.
;
; Copyright (C) 1990 by Marty Del Vecchio
; Released to the public domain for free use by all
; Product is supplied as is and author disclaims all warranties,
; explicit or implied, about the functionality of the source code
; or object modules supplied, and shall not be held responsible
; for any damages caused by use of product.
;
; Code to parse default FCB's written and generously donated
; by David E. Jenkins ([email protected] or [email protected]).
;
; Code to correctly handle disk-full errors written by Archie Warnock
; ([email protected])
;
; Contributions not solicited. Just appreciate the fact that somebody
; took the time to write and comment this code. If you have any
; questions, please contact me at:
;
; Marty Del Vecchio Channel 1 BBS
; 99 Marlboro Road Boston, MA
; Southborough, MA 01772 (617) 354-8873
; (508) 485-9718
;
; internet: [email protected]
;
; For information about the contents of this file, see the accompanying
; file SWAP.DOC.
;


; 'DOSSEG' gives us support for Microsoft C and Turbo C segment naming
; and ordering schemes
DOSSEG

; Figure out which memory model we're assembling for. Specified on
; MASM command line with /D followed by either _Small, _Compact, _Medium,
; or _Large. If none specified, _Small is assumed.

; Once the model is defined, MASM provides two definitions, @codesize
; and @datasize, to determine the size of code and data pointers. If
; @codesize is 0 (Small and Compact), there is one code segment, and
; code addresses are 16 bits (offset only). If @codesize is 1 (Medium
; and Large), there are multiple code segments, and code addresses are
; 32 bits (segment and offset). Similarly, @datasize of 0 means one
; data segment (16-bit pointers), and @datasize of 1 means multiple
; data segments (32-bit pointers).

IFDEF _large
.MODEL Large, C
IF1
%out Assembling for C, Large memory model
ENDIF
ELSE
IFDEF _compact
.MODEL Compact, C
IF1
%out Assembling for C, Compact memory model
ENDIF
ELSE
IFDEF _medium
.MODEL Medium, C
IF1
%out Assembling for C, Medium memory model
ENDIF
ELSE
.MODEL Small, C
IF1
%out Assembling for C, Small memory model
ENDIF
ENDIF
ENDIF
ENDIF


ifdef _286
.286
endif

; Report whether multiple DOS memory blocks will be swapped
IF1
IFDEF NOFRAG
%out Multiple DOS memory blocks will NOT be swapped
ELSE
%out Multiple DOS memory blocks will be swapped
ENDIF
ENDIF

; Figure out which save method we are using--EMS, XMS, disk, or a
; combination.

; Specified on MASM command line with /D followed by either "xms", "ems",
; "disk", or "all". For example, to create a swap() that will try using
; XMS and EMS, you would use "masm swap.asm /Dems /Dxms".

; If none specified, it will use all. To change the order in which swap()
; attempts to save the program to different places, see the function
; save_program below.

; First, see if they want all of them...
IFDEF all
USE_DISK EQU 1
USE_XMS EQU 1
USE_EMS EQU 1
ELSE
; /Dall not specified--try each individually...
IFDEF disk
USE_DISK EQU 1
ENDIF

IFDEF xms
USE_XMS EQU 1
ENDIF

IFDEF ems
USE_EMS EQU 1
ENDIF

ENDIF

; Now see if they declared anything--if not, it will use them all
IFNDEF USE_DISK
IFNDEF USE_EMS
IFNDEF USE_XMS
USE_DISK EQU 1
USE_XMS EQU 1
USE_EMS EQU 1
ENDIF
ENDIF
ENDIF

; Constant definitions for easier reading
STDERR equ 2 ; Standard DOS file handle for error output
GET_VECTOR equ 35h ; DOS function to get interrupt vector
EMM_INT equ 67h ; EMS interrupt vector
EMM_NAME_LEN equ 8 ; Length of EMS device driver name
MAX_DOS_CMD equ 127 ; Maximum DOS command-line length

; If we will swap out all DOS memory blocks a program owns, we need a
; place to store information about them
MAX_EXTRA equ 16 ; Maximum number of extra DOS allocation
; blocks to swap

dos_block struc ; Structure for extra DOS memory blocks
block_seg dw 0 ; User's segment address of block
block_size dw 0 ; Size in paragraphs of block
dos_block ends


bptr equ byte ptr ; Means we're loading/storing 8 bits
wptr equ word ptr ; Means we're loading/storing 16 bits
dptr equ dword ptr ; Means we're loading/storing 32 bits


; All code and data must be in the code segment, which is the first segment
; in all Turbo C, Turbo C++, and Microsoft C memory models.

; If we are in the Medium or Large models, there are multiple code segments.
; If this is the case, our default code segment name will be "SWAP_TEXT".
; This is acceptable in most cases, except when using the Turbo C integrated
; development environment. See SWAP.DOC for details.

; If you are using Turbo C's Integrated Development Environment, the line
; right after "IF @codesize" MUST say ".CODE _TEXT"!!!!!!!!!!!!!!!!!!!!
IF @codesize
;.CODE _TEXT
.CODE SWAP_TEXT
ELSE
.CODE
ENDIF

; *****************************************************************************
; Our resident data declarations--this data will be needed after the swap
; has occurred, and thus must be above the resident line
; *****************************************************************************

; *****************************************************************************
; First, all variables that will be used by all versions assembled from
; this source file, regardless of what save options are selected
; *****************************************************************************
ret_code dw 0 ; Return code (to C caller) of the swap routine
; 0 = success
; 1 = unable to shrink DOS memory allocation
; 2 = unable to save program to EMS
; 3 = unable to execute requested program
; These values must be the same as those listed
; in SWAP.H!!!!!!!!!

; *****************************************************************************
; Variables that deal with DOS' memory allocation blocks
old_size dw 0 ; The old size (in paragraphs) of this program
new_size dw 0 ; The new "resident" size, doesn't include
; code/data swapped
prog_size dw 0 ; Size in paragraphs of saved part of program
; block (old_size - new_size)
total_paras dw 0 ; Size (in paragraphs) of all blocks combined
my_psp dw 0 ; This program's Program Segment Prefix (PSP)
mcb_psp dw 0 ; The PSP address in this program's memory block
start_seg dw 0 ; Segment address of released memory

; If we are swapping all DOS memory blocks a program owns, we store
; them in this array of structures
IFNDEF NOFRAG
extra_count dw 0 ; # of extra blocks to save (not including
; program block)
dos_blocks dos_block MAX_EXTRA dup (<>) ; Array for extra blocks
ENDIF
; *****************************************************************************

; *****************************************************************************
; Variable used during the save/restore process
handle dw 0 ; EMS/XMS/disk file handle
; *****************************************************************************

; *****************************************************************************
; A temporary stack in our code segment, and associated variables
old_sp dw 0 ; Place to save this program's stack
old_ss dw 0 ; information while executing new program

; XMS driver needs a large stack (at least 256 bytes free when called)
IFDEF USE_XMS
new_stack db 320 dup ('?') ; Temporary stack we can address after swap
ELSE
new_stack db 128 dup ('?') ; Temporary stack we can address after swap
ENDIF
new_sp label word ; Point SP to "top" of stack
; *****************************************************************************

; *****************************************************************************
; Variables that deal with the execution of the new program
prog_name db 128 dup (0) ; Storage for name of program to execute
cmd_pad db 0 ; Maintain word-alignment for variables
cmd_len db 0 ; Storage for length of command line
; parameters
cmd_line db 128 dup (0) ; Storage for command line parameters

param_blk label byte ; Program Parameter Block--pass to DOS on
; exec call
env_seg dw 0 ; Environment segment address, 0 means a
; COPY of ours
cmd_ofs dw offset @code:cmd_len ; Offset address of command line
cmd_seg dw seg cmd_line ; Segment address of command line
fcb_5C_ofs dw offset fcb5C ; Far pointers to default FCB's. Some
fcb_5C_seg dw seg fcb5C ; programs (such as DOS' CHKDSK.COM)
fcb_6C_ofs dw offset fcb6C ; depend on these being parsed from
fcb_6C_seg dw seg fcb6C ; the command line before the EXEC call
; *****************************************************************************

; *****************************************************************************
; Variables needed to parse the command line into the default FCB's
c_l_length dw 0 ; Command line length
si_5C dw 0 ; Save area for pointer to cmd line arg 1
si_6C dw 0 ; Save area for pointer to cmd line arg 2

; Default FCB to be passed to PSP offset 5C (hex)
fcb5C label byte
fcb5C_drive db 0 ; drive
fcb5C_fname db 8 dup (?) ; file name
fcb5C_ext db 3 dup (?) ; extension
fcb5C_pad db 4 dup (?) ; unused

; Default FCB to be passed to PSP offset 6C (hex)
fcb6C label byte
fcb6C_drive db 0 ; drive
fcb6C_fname db 8 dup (?) ; file name
fcb6C_ext db 3 dup (?) ; extension
fcb6C_pad db 4 dup (?) ; unused
; *****************************************************************************

exec_ret db 0 ; Return code from executed program
exec_pad db 0 ; Maintain word-alignment for variables
restore_proc dw 0 ; Address of appropriate restore routine

; *****************************************************************************
; Message to display to screen when we can't reload program
abort_msg db 0dh, 0ah, 'SWAP: Unable to reload program.', 0dh, 0ah
abort_len dw $ - offset @code:abort_msg
; *****************************************************************************

; *****************************************************************************
; Next, the variables needed only for certain versions of the routine,
; depending on which save/restore options are chosen
; *****************************************************************************

; *****************************************************************************
; Variables needed only when swapping to XMS
IFDEF USE_XMS
XMS_proc dd 0 ; Address of XMS entry point

XMS_struc label byte ; Structure needed to move memory with XMS
XMS_size dd 0 ; # of bytes to move (must be even)
XMS_from dw 0 ; Handle of source, 0=conventional memory
XMS_from_addr dd 0 ; Address of source memory
XMS_to dw 0 ; Handle of destination, 0=conventional
; memory
XMS_to_addr dd 0 ; Address of destination memory
ENDIF
; *****************************************************************************

; *****************************************************************************
; Variables needed only when swapping to EMS
IFDEF USE_EMS
pages_used db 0 ; # of pages of EMS used
emm_name db 'EMMXXXX0' ; Name of EMS device driver

EMS_struc label byte ; Structure needed to move memory with EMS 4.0+
EMS_size dd 0 ; # of bytes to move
EMS_from db 0 ; Type of source memory (0 = conventional,
; 1 = expanded)
EMS_from_h dw 0 ; Source memory handle (0 = conventional)
EMS_from_o dw 0 ; Offset of source memory (expanded = 0-16K,
; conventional = 0-64K)
EMS_from_s dw 0 ; Segment/page of source (expanded = logical
; page, conventional = segment)
EMS_to db 0 ; Type of desination memory (0 = conventional,
; 1 = expanded)
EMS_to_h dw 0 ; Destination memory handle (0 = conventional)
EMS_to_o dw 0 ; Offset of destination memory (expanded =
; 0-16K, conventional = 0-64K)
EMS_to_s dw 0 ; Segment/page of destination (expanded =
; logical page, conventional = segment)

ems_offset dd 0 ; Destination pointer--absolute byte offset
; into handle
ENDIF
; *****************************************************************************

; *****************************************************************************
; Variables needed only when swapping to disk
IFDEF USE_DISK
fname db 80 dup (0) ; Name of the file data is saved to/read from
paras_left dw 0 ; temporary counter
ENDIF
; *****************************************************************************



; *****************************************************************************
; Version-dependent code--only assemble the routine to restore the program
; from each media (XMS, EMS, disk) if it was specified on the command line
; *****************************************************************************


; *****************************************************************************
; restore_xms Attempts to restore program from XMS extended memory
;
; Entry: DS points to our variables
; Program was saved to XMS extended memory (block referred to by
; handle)
;
; Return: Carry set on error, carry clear on success
; *****************************************************************************
IFDEF USE_XMS
restore_xms proc near
push es

assume ds:@code ; Tell MASM that DS points
; to our variables

; First, attempt to restore the portion of the program block that was saved
xms_prog_rest: mov ax, wptr start_seg ; Released segment address
mov es, ax
mov ax, wptr prog_size ; Size (in paragraphs)

xor bx, bx
mov wptr XMS_from_addr, bx ; Initialize XMS source
mov wptr XMS_from_addr + 2, bx ; address (offset into
; extended memory block)

call rest_xms_seg ; Attempt to restore it

IFNDEF NOFRAG
jc xms_dealloc ; Carry set = error, exit

; Next, restore the extra DOS segments
xms_extra_rest: mov cx, wptr extra_count ; Number of extra blocks to save
jcxz xms_dealloc ; If CX = 0, we exit routine

mov di, offset dos_blocks ; DI -> array of segment/size
; pairs

xms_extra_rest_loop:
mov ax, wptr [di].block_seg
mov es, ax ; ES = segment to restore
mov ax, wptr [di].block_size; AX = size in paragraphs
push cx
push di
call rest_xms_seg ; Attempt to restore this block
pop di
pop cx
jc xms_dealloc ; Carry flag set == error, exit
add di, size dos_block
loop xms_extra_rest_loop ; Keep going through all blocks

ENDIF

xms_dealloc: rcl bl, 1 ; Save carry flag in low bit of
; bl

mov dx, wptr handle ; First, free XMS handle
mov ah, 0Ah
push bx
call dptr XMS_proc
pop bx

rcr bl, 1 ; Restore carry flag from bl
; low bit

restore_xms_ret:pop es
ret
restore_xms endp


; *****************************************************************************
; rest_xms_seg Attempts to restore a chunk of RAM from XMS memory
;
; Entry: ES points to the segment to restore
; AX contains its length (in paragraphs)
; handle holds the XMS handle to read from
; XMS_from_addr contains offset into extended memory for read
;
; Return: Carry set on error, carry clear on success
; Updates XMS_from_addr for next read
; *****************************************************************************
rest_xms_seg proc near
push ds
push es

; Call the XMS copy memory function to do this; fill in request block
xms_read_size: mov bx, 10h ; AX = # of paragraphs,
; convert to bytes
mul bx ; DX:AX = AX * 10h, # of
; bytes to read
mov wptr XMS_size, ax ; Store # of bytes to read
mov wptr XMS_size + 2, dx

xms_read_from: mov ax, wptr handle ; Source XMS handle
mov wptr XMS_from, ax ; XMS_from_addr already
; filled in

xms_read_to: xor bx, bx
mov wptr XMS_to, bx ; Read into conventional
; memory
mov wptr XMS_to_addr, bx ; Offset of dest address
mov ax, es ; Segment of destination
; address
mov wptr XMS_to_addr + 2, ax

do_xms_read: mov si, offset @code:XMS_struc ; DS:SI -> XMS structure
mov ah, 0Bh
call dptr XMS_proc ; Do the move
cmp ax, 1
jnz rest_xms_seg_er

rest_xms_seg_ok:mov ax, wptr XMS_size ; Retrieve length
mov dx, wptr XMS_size + 2 ; (32 bits)
add wptr XMS_from_addr, ax ; Add two 32-bit values
adc wptr XMS_from_addr + 2, dx ; Update XMS read pointer
clc ; Signal success
jmp short rest_xms_seg_ret

rest_xms_seg_er:stc

rest_xms_seg_ret:
pop es
pop ds
ret
rest_xms_seg endp

ENDIF
; *****************************************************************************


; *****************************************************************************
; restore_ems Attempts to restore program from EMS expanded memory
;
; Entry: DS points to our variables
; Program was saved to EMS expanded memory (block referred to by
; handle)
;
; Return: Carry set on error, carry clear on success
; *****************************************************************************
IFDEF USE_EMS
restore_ems proc near
push es

assume ds:@code ; Tell MASM that DS points
; to our variables

; First, attempt to restore the portion of the program block that was saved
ems_prog_rest: mov ax, wptr start_seg ; Released segment address
mov es, ax
mov ax, wptr prog_size ; Size (in paragraphs)

xor bx, bx
mov wptr ems_offset, bx ; Maintain absolute by
mov wptr ems_offset + 2, bx ; offset pointer relative
; to handle

call rest_ems_seg ; Attempt to restore it

IFNDEF NOFRAG
jc ems_dealloc ; Carry set = error, exit

; Next, restore the extra DOS segments
ems_extra_rest: mov cx, wptr extra_count ; Number of extra blocks to save
jcxz ems_dealloc ; If CX = 0, we exit routine

mov di, offset dos_blocks ; DI -> array of segment/size
; pairs

ems_extra_rest_loop:
mov ax, wptr [di].block_seg
mov es, ax ; ES = segment to restore
mov ax, wptr [di].block_size; AX = size in paragraphs
push cx
push di
call rest_ems_seg ; Attempt to restore this block
pop di
pop cx
jc ems_dealloc ; Carry flag set == error, exit
add di, size dos_block
loop ems_extra_rest_loop ; Keep going through all blocks

ENDIF

ems_dealloc: rcl bl, 1 ; Save carry flag in low bit of
; bl

mov ah, 45h ; Deallocate EMS memory
mov dx, wptr handle ; Specify which handle
push bx
int 67h
pop bx

rcr bl, 1 ; Restore carry flag from bl
; low bit

restore_ems_ret:pop es
ret
restore_ems endp

; *****************************************************************************
; rest_ems_seg Attempts to restore a chunk of RAM from EMS memory
;
; Entry: ES points to the segment to restore
; AX contains its length (in paragraphs)
; handle holds the EMS handle to write to
; ems_offset holds the 32-bit absolute offset in expanded
; memory to read this block from
;
; Return: Carry set on error, carry clear on success
; Updates ems_offset with proper offset for next read
; *****************************************************************************
rest_ems_seg proc near
push ds
push es

assume ds:@code ; Tell MASM DS points to our
; variables

; Call the EMS copy memory function to do this; fill in the EMS request block
ems_read_size: mov bx, 10h ; AX = # of paragraphs
mul bx ; DX:AX = AX * 10h, convert
; paragraphs to bytes
mov wptr EMS_size, ax ; Store # of bytes to write
mov wptr EMS_size + 2, dx

ems_read_to: xor bx, bx
mov bptr EMS_to, bl ; Copying to conventional
; memory (0)
mov wptr EMS_to_h, bx ; Destination handle is 0
; (conventional memory)
mov wptr EMS_to_o, bx ; Destination offset is 0
mov ax, es ; Segment of destination
; address is ES
mov wptr EMS_to_s, ax

ems_read_from: mov bptr EMS_from, 1 ; Copying to expanded memory
mov ax, wptr handle
mov wptr EMS_from_h, ax ; Specify EMS handle

; 32-bit absolute offset for copy is in ems_offset
; convert to EMS page:offset (16K pages) values
mov ax, wptr ems_offset ; Load 32-byte offset
mov dx, wptr ems_offset + 2
mov bx, ax ; Save a copy of ax (low 16
; bits)
and ax, 0011111111111111b ; Get (ax & (16K - 1)),
; this is the offset (14
; bits)
mov wptr EMS_from_o, ax ; Save page offset
mov cl, 14
shr bx, cl ; Move low 2 bits of page
; into low 2 bits of bx
mov cl, 2
shl dx, cl ; Move hi ? bits of page
; into dx shl 2
or dx, bx ; DX = page number (combine
; two values)
mov wptr EMS_from_s, dx ; Save

mov ax, wptr EMS_size ; Retrieve size of copy
mov dx, wptr EMS_size + 2
add wptr ems_offset, ax ; Update EMS copy pointer
adc wptr ems_offset + 2, dx ; for next EMS write

do_ems_read: mov si, offset @code:EMS_struc ; DS:SI -> EMS request
; structure
mov ax, 5700h ; Function 57 (copy/exchange
; memory), sub 0, copy
; memory
int 67h ; Call EMS manager
or ah, ah ; AH = 0 means success
jnz rest_ems_seg_er ; Not 0 means error

rest_ems_seg_ok:clc ; Signal success
jmp short rest_ems_seg_ret

rest_ems_seg_er:stc

rest_ems_seg_ret:
pop es
pop ds
ret
rest_ems_seg endp

ENDIF
; *****************************************************************************


; *****************************************************************************
; restore_disk Attempts to restore program from DOS disk file
;
; Entry: DS points to our code segment
; Program was saved to DOS disk file (full path stored in fname)
;
; Return: Carry set on error, carry clear on success
; *****************************************************************************
IFDEF USE_DISK
restore_disk proc near

push ds

assume ds:@code ; Tell MASM that DS points to
; our variables

open_file: mov dx, offset @code:fname ; DS:DX -> file name
mov ax, 3D42h ; DOS function 3Dh, open file
; al = open for read only,
; deny none
int 21h ; Call DOS
jnc open_ok ; Carry clear = all OK
jmp short restore_disk_ret ; Carry set, just exit with
; error

open_ok: mov wptr handle, ax ; File handle returned from DOS

; First, restore the program block contents saved to disk
disk_prog_rest: mov ax, wptr start_seg ; Get segment of program block
; saved
mov es, ax
mov ax, wptr prog_size ; Get size of program block
; saved
call rest_disk_seg ; Try to restore it
jc restore_disk_er ; Carry set == error

IFNDEF NOFRAG
; Next, restore the contents of the extra blocks saved to disk
disk_extra_rest:
mov cx, wptr extra_count ; Number of extra blocks to
; restore
jcxz close_read ; IF CX = 0, we're done
; restoring

mov di, offset dos_blocks ; DI -> array of segment/size
; pairs

disk_extra_rest_loop:
mov ax, wptr [di].block_seg
mov es, ax ; ES = segment to restore to
mov ax, wptr [di].block_size; AX = size in paragraphs
push cx
push di
call rest_disk_seg ; Attempt to restore this block
pop di
pop cx
jc restore_disk_er ; Error--exit routine
add di, size dos_block
loop disk_extra_rest_loop ; Look for next DOS block

ENDIF

close_read: mov ah, 3Eh ; Close file
int 21h ; Call DOS

restore_disk_ok:clc ; Signal success
jmp short restore_disk_ret ; and Exit

restore_disk_er:
mov ah, 3Eh ; Error, close file first
int 21h ; Call DOS
stc ; Signal failure

restore_disk_ret:
pop ds ; Restore our DS! (error in
; revs 2.11 and before)

rcl bl, 1 ; Save carry flag in low bit of
; bl

mov dx, offset @code:fname ; DS:DX -> file name
mov ah, 41h ; DOS function 41h, delete file
push bx
int 21h ; Call DOS
pop bx

rcr bl, 1 ; Restore carry flag from low
; bit of bl

ret
restore_disk endp

; *****************************************************************************
; rest_disk_seg Attempts to restore a chunk of RAM from the DOS disk file
;
; Entry: ES points to the segment to restore
; AX contains its length (in paragraphs)
; handle contains the file handle to read from
; Program was saved to DOS disk file (fname)
;
; Return: Carry set on error, carry clear on success
; *****************************************************************************
rest_disk_seg proc near
push es
push ds

mov bx, es
mov ds, bx ; DS -> segment to restore to

assume ds:nothing

mov wptr cs:paras_left, ax ; Keep count in this variable

disk_read_32k: cmp ax, 0800h ; Less than 32K left?
jb last_disk_read ; Yes, do last read
sub wptr cs:paras_left, 0800h ; 32K left to read
mov ah, 3Fh ; DOS function 3Fh, read file
mov bx, wptr cs:handle ; BX = handle to read from
mov cx, 8000h ; Read 32K bytes
xor dx, dx ; DS:DX -> buffer to read to
int 21h ; Call DOS
jc rest_disk_seg_er ; Carry set = error

disk_read_ok: mov ax, ds ; Address next read location
add ax, 0800h ; It's 800h paragraphs ahead
mov ds, ax ; DS -> new restore location
mov ax, wptr cs:paras_left ; Expecting this above
jmp short disk_read_32k ; Read next 32K

last_disk_read: mov cx, 4 ; Convert paragraphs to bytes
shl ax, cl
mov cx, ax ; # of bytes left in cx
mov ah, 3Fh ; Read last bytes
mov bx, wptr cs:handle ; BX = handle to read from
xor dx, dx ; DS:DX -> buffer to restore to
int 21h ; Call DOS
jc rest_disk_seg_er ; Error reading! Close file
; first

rest_disk_seg_ok:
clc
jmp short rest_disk_seg_ret

rest_disk_seg_er:
stc

rest_disk_seg_ret:
pop ds
pop es
ret
rest_disk_seg endp

ENDIF
; *****************************************************************************



; *****************************************************************************
; execute_program Execute the program specified
;
; Entry: param_blk has been initialized
; DS points to our data
; Return: puts return code in cs:exec_ret
; *****************************************************************************
execute_program proc near ; Called only from inside our
; segment

push ds ; These are destroyed by the
push es ; DOS EXEC call

assume ds:@code ; Tell MASM that DS points to
; our variables

exec_program: mov ax, ds ; Our path name is in CS (point
; DS to our segment)
mov es, ax ; Our parameter block is in CS
; (point ES to our segment)
mov ax, 4B00h ; Load and execute program
mov bx, offset @code:param_blk
mov dx, offset @code:prog_name
int 21h ; Sets carry flag if error
; All registers destroyed
; except CS:IP!

assume ds:nothing ; Tell MASM that DS doesn't
; point to our variables

mov bptr cs:exec_ret, al ; Store EXEC code
jc exec_err ; Ooops

get_return: mov ah, 4Dh ; DOS function to get ret code
int 21h ; All registers destroyed
mov bptr cs:exec_ret, al ; Store EXEC code
jmp short exec_exit

exec_err: mov wptr cs:ret_code, 3 ; Signal error on executing

exec_exit: pop es
pop ds

ret

execute_program endp


; *****************************************************************************
; err_exit Prints error message and terminates program
;
; Entry: Nothing.
; Returns: Doesn't return--calls DOS terminate function.
; Naturally, we can't use the C runtime routines,
; since they are swapped out.
; *****************************************************************************
err_exit proc near ; Called only from inside our
; segment

mov ax, cs
mov ds, ax ; Point DS to our data

assume ds:@code ; Tell MASM that DS points to
; our data

mov ah, 40h ; DOS function to write to file
mov bx, STDERR ; Write to standard error handle
mov cx, wptr abort_len ; CX = length of message
mov dx, offset @code:abort_msg ; DS:DX = message
int 21h

mov ax, 4CFFh ; Exit, return code 255 decimal
; (FF hex)
int 21h ; Exit to DOS, no return

err_exit endp


; *****************************************************************************
; do_exec Calls the execute routine, then restores program
;
; Entry: Nothing
; Returns: Since it is called from the non-resident area, it
; can only return if the program is restored completely.
; *****************************************************************************
do_exec proc
call near ptr execute_program ; Execute the specified
; program
jnc re_size ; No carry, OK

exec_er: mov wptr ret_code, 3 ; Signal error

re_size: mov es, wptr my_psp ; Get our PSP address
mov bx, wptr old_size ; Increase back to old size
mov ah, 4Ah ; DOS function 4Ah = resize
int 21h
jc resize_err ; Carry clear = all OK

IFNDEF NOFRAG
; If necessary, allocate all extra DOS memory blocks our program owned

mov cx, wptr extra_count ; CX=number of extra DOS blocks
jcxz restore_prog ; If zero, don't bother
mov di, offset dos_blocks ; DI -> array of addresses/sizes

push es

alloc_extra_loop:
mov bx, wptr [di].block_size; BX = old size
mov ah, 48h ; DOS function to allocate
; memory block
push cx
push di
int 21h
pop di
pop cx
jc resize_err ; Unlikely error

check_alloc: cmp ax, wptr [di].block_seg ; Is it the same as the original
; segment address?
jnz resize_err ; Nope. We could do some fancy
; tricks here, but for the most
; part it's not necessary.

add di, size dos_block ; Point to next entry
loop alloc_extra_loop ; Keep going through extra
; blocks

pop es
ENDIF
jmp short restore_prog

resize_err: call near ptr err_exit ; Can't return, exit to DOS

restore_prog: call wptr restore_proc ; Restore program from disk
jc resize_err ; Carry set if error
; If no error, it returns
; down to restored code
ret
do_exec endp

; *****************************************************************************
; *****************************************************************************
ALIGN 10h ; Aligns next code item on paragraph boundary
; para_align is a proc instead of just a data
; item because the ALIGN directive in MASM only
; applies to code items, not data items!
para_align proc near
new_mcb db 16 dup (0) ; DOS will put MCB of released
; memory here
para_align endp
; *****************************************************************************
; *****************************************************************************

; *****************************************************************************
; Everything after here is only needed BEFORE we change our allocation size.
; Everything below this line will be (temporarily) swapped out of memory,
; and thus cannot be used once we shrink our memory allocation.
; *****************************************************************************

; *****************************************************************************
; swap The routine that does it all
;
; Callable by a C program, takes these parameters (regardless
; of which swap options chosen at assembly time, because
; C calling conventions let us ignore parameters to the
; right if we want to):
;
; swap_both:
; prog Full path name of program to execute
; cmdline Command-line parameters for program to execute
; return Pointer to byte for return code of exec'd program
; save_file Full path name of file in which to save program image (if
; disk is to be used)
;
; Depending on the memory model used, the pointers to the
; parameters each occupy 2 bytes or 4 bytes on the stack.
; If there is only one data segment (Small and Medium), each
; value is a 2-byte near pointer, with DS assumed as the segment
; register. If there are multiple data segments (Compact and
; Large), each value is a 4-byte far pointer, with segment and
; offset values each pushed on the stack.
;
; The function is declared with 4 parameters, regardless of whether
; disk swapping is being included. This is because the file name
; parameter is the last on the parameter list, which C lets us
; ignore if we want.
;
; The swap() routine does not check the program name or command
; line to verify that a legal command has been requested--that's
; the caller's responsibility!
;
; *****************************************************************************

public swap

swap proc prog:PTR, cmdline:PTR, return:PTR, save_file:PTR

push si ; Save registers needed
push di ; by the caller
push es
push ds

point_segs: mov ax, cs ; Point ES to our segment
mov es, ax ; for copying of parameters

; *****************************************************************************
get_name: ; Copy program name to our variable, all versions

; If multiple data segments, load DS:SI from stack. Else, just load SI
IF @datasize
push ds ; Save segment register
lds si, dptr prog ; Load 32-bit far pointer
ELSE
mov si, wptr prog ; Load 16-bit near pointer
ENDIF ; DS:SI -> program name from
; caller

mov di, offset @code:prog_name ; ES:DI -> our storage area

name_loop: lodsb ; Fetch next byte
stosb ; Save next byte
or al, al ; Was it 0 (end of string)?
jnz name_loop ; No, get next one

IF @datasize
pop ds ; Pop DS if it was pushed above
ENDIF
; *****************************************************************************

; *****************************************************************************
get_cmd: ; Copy command line to our variable, all versions

; If multiple data segments, load DS:SI from stack. Else, just load SI
IF @datasize
push ds ; Save segment register
lds si, dptr cmdline ; Load 32-bit far pointer
ELSE
mov si, wptr cmdline ; Load 16-bit near pointer
ENDIF ; DS:SI -> command line from
; caller

mov di, offset @code:cmd_line ; ES:DI -> our storage area
xor cl, cl ; Keep track of length in cl

cmd_loop: lodsb ; Fetch next byte from DS:SI
or al, al ; Was it 0 (end of string)?
jz cmd_end ; Yes, we're done
stosb ; No, store byte
inc cl ; Increment length
cmp cl, MAX_DOS_CMD ; Are we at maximum cmd length?
jnz cmd_loop ; Nope, keep going

cmd_end: mov bptr es:[di], 0dh ; Put CR at end of cmd line
mov bptr cs:cmd_len, cl ; Store command-line length

IF @datasize
pop ds ; Pop DS if it was pushed above
ENDIF
; *****************************************************************************
; Set up the default FCBs at 5Ch and 6Ch in the PSP
; Code provided by David E. Jenkins
push ds ; Save caller's DS

mov ax, cs ; Point DS to our
mov ds, ax ; variables

assume ds:@code ; Tell MASM that DS points to
; our variables
;
; Locate the first two command line arguments
;
push ds ; Copy ds into es
pop es ; " " " "
mov di, offset @code:cmd_line ; Point to command line in
; CS
mov al, bptr cmd_len ; load the command line length
xor ah, ah
inc ax ; Include the CR in the length
mov wptr c_l_length, ax ; Save the command line length
add ax, di ; Point to end of command line
mov wptr si_5c, ax ; default to just after command
; line
mov wptr si_6c, ax ; " " " " "
cmp bptr cmd_len, 0 ; Is there anything to parse?
jz args_located ; if not then args have been
; located

mov cx, wptr c_l_length ; Load the command line length
mov al, ' ' ; We must find the first non-
; blank
repe scasb ; Go until we find it or run out
or cx, cx ; Did we run out (CX = 0)?
jz args_located ; Yes--then args have been
; located

dec di ; Move back to the right one
inc cx ; " " " " " "
mov wptr si_5c, di ; Save the location of arg 1
repne scasb ; Find the next space (between
; arg1,2)
or cx, cx ; Did we run out
jz args_located ; If so then args have been
; located

dec di ; Move back to the left one
inc cx ; " " " " " "
repe scasb ; Now find next non-blank
; (arg 2)
or cx, cx ; Did we run out
jz args_located ; If so then args have been
; located

dec di ; Move back to the right one
inc cx ; " " " " " "
mov wptr si_6c,di ; Save location of arg 2

args_located:
; parse the first argument into the first FCB

mov si, wptr si_5c ; Point to the first
; argument
mov di, offset @code:fcb5C_drive ; Point to the unopened
; FCB
mov ah, 29h ; Parse file name function
mov al, 00h ; Do it like COMMAND.COM does
int 21h ; go for it

; parse the second argument into the second FCB
mov si, wptr si_6c ; Point to the second argument
mov di, offset @code:fcb6c_drive ; point to the unopened
; FCB
mov ah, 29h ; Parse file name function
mov al, 00h ; Do it like COMMAND.COM does
int 21h ; go for it

pop ds ; Restore caller's DS

; *****************************************************************************
; Get the file name from the command line, if this version needs it
IFDEF USE_DISK
get_file:

; If multiple data segments, load DS:SI, else just load SI
IF @datasize
push ds ; Save segment register
lds si, dptr save_file ; Load 32-bit pointer
ELSE
mov si, save_file ; Load 16-bit pointer
ENDIF ; DS:SI -> swap file name from
; caller

mov di, offset @code:fname ; ES:DI -> our storage area

resolve: mov ah, 60h ; DOS INTERNAL function to
; resolve file name to full
; path name
int 21h ; Stores complete path at ES:DI
; we need it after EXEC in case
; current drive or directory
; have changed
; Ignore file name error here--
; it will be caught in
; save_disk if need be

IF @datasize
pop ds ; Pop DS if it was pushed above
ENDIF

ENDIF ; IFDEF disk
; *****************************************************************************
; We have the parameters--let's go
; *****************************************************************************

mov wptr cs:ret_code, 0 ; Initialize swap's return code
mov cs:exec_ret, 0 ; Initialize exec's return code

save_stack: mov ax, ss
mov wptr cs:old_ss, ax ; Save current SS
mov ax, sp
mov wptr cs:old_sp, ax ; Save current SP

our_stack: mov ax, cs ; Our stack is in our CS
cli ; Disable interrupts
mov ss, ax
mov sp, offset @code:new_sp ; Set new stack
sti ; Re-enable interrupts

save_regs: push es ; Save needed registers
push ds ; This is the caller's DS!
push bp

mov ax, cs
mov ds, ax ; Point DS to our data

assume ds:@code ; Tell MASM that DS points to
; our variables

save_info: mov ah, 51h ; DOS function 51h, get PSP
int 21h ; Call DOS
mov ax, bx ; ax = PSP
mov wptr my_psp, ax ; Save in cs: addressable
; location
dec ax ; PSP-1 = MCB for this mem block
mov es, ax
mov ax, es:[0001h] ; Get PSP address--should be
; same!
cmp ax, wptr my_psp ; All kosher?
jz psp_ok ; Yes

psp_error: mov wptr ret_code, 1 ; No, pass return code
jmp short exit_swap ; Exit

psp_ok: call near ptr calc_size ; Calc size to keep, save

try_save: call near ptr save_program ; Write program to disk
jnc shrink_mem ; Carry flag set on error

no_save: mov wptr ret_code, 2 ; Error--set return code
jmp short exit_swap ; Exit routine on error

shrink_mem: mov ah, 4Ah ; DOS 4Ah--modify memory
; allocation
mov es, wptr my_psp ; Point to PSP again
mov bx, wptr new_size ; new_size was figured in
; calc_size
int 21h ; Call DOS to shrink size
jc no_shrink ; Carry set = error

IFNDEF NOFRAG
; If necessary, free all extra DOS memory blocks our program owns

mov cx, wptr extra_count ; CX=number of extra DOS blocks
jcxz exec_prog ; If zero, don't bother
mov di, offset dos_blocks ; DI -> array of addresses/sizes

push es

free_extra_loop:
mov ax, wptr [di].block_seg
mov es, ax ; ES=DOS memory segment to free
mov ah, 49h ; DOS function to free memory
; block
push cx
push di
int 21h
pop di
pop cx
jc no_shrink ; Unlikely error
add di, size dos_block ; Point to next entry
loop free_extra_loop ; Keep going through extra
; blocks

pop es
ENDIF

jmp short exec_prog

; *****************************************************************************
; Any routine called or data referred to after this point MUST be located
; in this source file BEFORE the variable new_mcb below!
; *****************************************************************************

no_shrink: mov wptr ret_code, 1 ; Carry = couldn't shrink block
jmp short exit_swap ; Should delete file here!

exec_prog: call do_exec ; This code is resident, and
; can be found above the
; resident line

; do_exec execute the routine AND restores the program!

exit_swap: pop bp ; Restore saved registers
pop ds ; This is the caller's DS!
pop es

assume ds:nothing ; Tell MASM DS doesn't point to
; our variables

prev_stack: mov ax, wptr cs:old_ss ; Restore original stack
cli
mov ss, ax
mov sp, wptr cs:old_sp
sti

; Giving user exec's return code. It could be a 16- or 32-bit pointer
IF @datasize
push ds
lds si, dptr return ; Load 32-bit pointer
ELSE
mov si, wptr return ; Load 16-bit pointer
ENDIF ; DS:SI -> return code variable

mov al, bptr cs:exec_ret ; Store exec's return code
mov bptr [si], al ; at address specified by
; caller

IF @datasize
pop ds ; Pop DS if pushed above
ENDIF

pop ds
pop es
pop di
pop si
mov ax, wptr cs:ret_code ; Give return code
ret
swap endp

; *****************************************************************************
; *****************************************************************************
; calc_size Calculates the total size (in paragraphs) of all DOS blocks
; owned by this program plus the amount of the initial program
; allocation block we can swap out.
;
; Entry: DS points to our variables
; ES points to DOS Memory Control Block for our program
;
; Return: old_size, start_seg, new_size, total_paras, extra_count
; initialized
; *****************************************************************************
calc_size proc near ; Called only from inside our
; segment

push es

assume ds:@code ; Tell MASM that DS points to
; our variables

mov ax, es:[0003h] ; Get # paragraphs allocated
; in this memory block
mov wptr old_size, ax ; Save old size of program
mov bx, cs ; BX = segment of our code
mov ax, offset @code:new_mcb; Last address to keep
mov cl, 4 ; new_mcb is para aligned
shr ax, cl ; AX = ofs new_mcb / 16
inc ax
add bx, ax
mov wptr start_seg, bx ; Segment of released memory
sub bx, wptr my_psp ; BX=size to keep in paragraphs
mov wptr new_size, bx ; Save new, smaller size
mov ax, wptr old_size
sub ax, bx
mov wptr prog_size, ax ; ax = size of program block to
; swap out
mov wptr total_paras, ax ; ax = total paragraphs

IFNDEF NOFRAG
; Now loop through all subsequent MCBs looking for blocks that we own (if
; the MCB's "owner" (PSP) matches us (our PSP). Right now ES points to
; our MCB. The MCB has three fields of interest:
;
; Offset Size Description
; -------------------------------------------------------------------------
; 0000h Byte Chain flag: 'M' (4Dh) if not last, 'Z' (5Ah) if last block
; in chain
; 0001h Word PSP segment of owner, 0000h if free memory
; 0003h Word Size of memory block in paragraphs, NOT including this MCB!

find_extras: mov wptr extra_count, 0 ; Initialize count
mov bx, wptr my_psp ; Use bx to hold PSP for easy
; comparisons
mov di, offset dos_blocks ; di = pointer to storage area

check_next_mcb: cmp bptr es:[0000h], 'Z' ; Is this the last block?
jz calc_size_ret ; Yup

next_mcb2: mov ax, es ; ax = this MCB
mov cx, wptr es:[0003h] ; cx = size of this mcb
add ax, cx
inc ax ; ax = addres of next MCB
mov es, ax ; ES -> next MCB

my_block: cmp wptr es:[0001h], bx ; Does it match my PSP?
jnz check_next_mcb ; Nope, move along

is_my_block: inc wptr extra_count ; One more extra block
cmp wptr extra_count, MAX_EXTRA
ja calc_size_ret ; Too many blocks--just exit

is_my_block2: inc ax ; Was MCB, now is address of
; segment
mov wptr [di].block_seg, ax ; Store segment address
mov cx, wptr es:[0003h] ; Get size in paragraphs
mov wptr [di].block_size, cx; Store size
add wptr total_paras, cx ; Increment total
add di, size dos_block ; Next index (move pointer)
jmp short check_next_mcb
ENDIF

calc_size_ret: pop es
ret

calc_size endp
; *****************************************************************************

; *****************************************************************************
; xms_installed Checks to see if XMS driver (himem.sys) is loaded
;
; Entry: No assumptions--can be called by user
; Return: 1 if XMS driver is load, 0 if not
; *****************************************************************************
IFDEF USE_XMS
public xms_installed
xms_installed proc ; Called by user also!

push ds ; Save all "important" registers
push si
push es
push di

mov ax, 4300h ; Multiplex code for XMS driver,
; load check function
int 2Fh ; Call multiplex interrupt
cmp al, 80h ; al=80h means XMS driver IS loaded
jnz no_xms ; Nope, not there

yes_xms: mov ax, 4310h ; Get address of entry point
int 2Fh ; Returns address in ES:BX
mov wptr cs:XMS_proc, bx
mov wptr cs:XMS_proc + 2, es
mov ax, 1 ; Return 1, XMS installed
jmp short xms_ret

no_xms: xor ax, ax ; Return 0, XMS not installed

xms_ret: pop di
pop es

pop si
pop ds
ret

xms_installed endp
ENDIF
; *****************************************************************************

; *****************************************************************************
; ems4_installed Checks to see if EMS 4.0 or above driver is loaded
;
; Entry: No assumptions--can be called by user
; Return: 1 if EMS 4.0 driver is load, 0 if not
; *****************************************************************************
IFDEF USE_EMS
public ems4_installed
ems4_installed proc ; Called by user also!

push ds ; Save "important" registers
push si
push es
push di


get_emm_vector: mov ah, GET_VECTOR ; Get EMM interrupt vector
mov al, 67h ; EMM accessed through Int 67h
int 21h ; Call DOS to get vector
mov di, 0ah ; vector + di = name
mov ax, cs
mov ds, ax ; DS:SI-> EMM device driver name
mov si, offset @code:emm_name ; Compare with EMM device
; name
mov cx, EMM_NAME_LEN
cld
repe cmpsb ; Compare bytes
jnz ems_no ; Same? If not, EMS installed

ems_yes: mov ah, 46h ; Get EMM version number
int 67h ; Returns BCD in al
cmp al, 40h ; Look only at high 4 bits
jb ems_no ; Version not high enough --
; return 0

ems4_yes: mov ax, 1 ; EMS installed, return 1
jmp short ems_ret

ems_no: xor ax, ax ; EMS not installed, return 0

ems_ret: pop di
pop es
pop si
pop ds
ret

ems4_installed endp
ENDIF
; *****************************************************************************


; *****************************************************************************
; save_program Try to save in XMS/EMS/disk.
;
; Entry: DS points to our variables
;
; Returns: Success: carry flag clear
; Failure: carry flag set
; *****************************************************************************
save_program proc near ; Called only from inside our segment

push si ; Save registers
push di
push ds
push es

; Now figure out which routines to call, based on command-line definitions

; To change the order in which swap() attempts to swap, change the order
; of these three conditional blocks.
IF1
%out swap() will attempt to save the program in the following order:
ENDIF


; *****************************************************************************
IFDEF USE_XMS
IF1
%out -- XMS extended memory
ENDIF
call save_xms ; Try saving to XMS extended memory
jnc save_ok ; Carry clear == success, all done
ENDIF
; *****************************************************************************


; *****************************************************************************
IFDEF USE_EMS
IF1
%out -- EMS expanded memory
ENDIF
call save_ems ; Try saving to EMS expanded memory
jnc save_ok ; Carry clear == success, all done
ENDIF
; *****************************************************************************


; *****************************************************************************
IFDEF USE_DISK
IF1
%out -- DOS disk file
ENDIF
call save_disk ; Try saving to DOS disk file
jnc save_ok ; Carry clear == success, all done
ENDIF
; *****************************************************************************

save_er: stc ; Couldn't save anywhere, return error
jmp short save_ret

save_ok: clc ; Saved successfully, return OK

save_ret: pop es ; Restore registers
pop ds
pop di
pop si

ret
save_program endp
; *****************************************************************************


; *****************************************************************************
; Version-dependent code--only assemble the routine to save the program
; to each place if it was requested on the command line
; *****************************************************************************


; *****************************************************************************
; save_xms Attempts to save program to XMS extended memory
;
; Entry: DS points to our variables
;
; Return: Carry set on error, carry clear on success
; If successful, updates restore_proc with the address of
; the XMS restore routine
; *****************************************************************************
IFDEF USE_XMS
save_xms proc near

assume ds:@code ; Tell MASM DS points to our
; variables

call xms_installed ; Check if XMS installed
or ax, ax ; Returns 0 if not installed
jnz xms_inst ; AX != 0, XMS installed
jmp short save_xms_er ; AX == 0, XMS not installed

xms_inst: mov dx, wptr total_paras ; dx = total # of paragraphs to
; write
mov cl, 6 ; Convert Paragraphs to
; kilobytes
shr dx, cl ; dx = dx / 64
inc dx ; dx = kilobytes needed (plus 1
; for safety)

xms_alloc: mov ah, 09h ; XMS function 09, allocate
; extended memory block
call dptr XMS_proc ; Call XMS entry point directly
cmp ax, 1 ; AX = 1 on success
jnz save_xms_er ; Allocation unsuccessful, error

xms_alloc_ok: mov wptr handle, dx ; Save returned handle in DX

; First, attempt to save the portion of the program block
xms_prog_save: mov ax, wptr start_seg ; Released segment address
mov es, ax
mov ax, wptr prog_size ; Size (in paragraphs) of
; program block to save
xor bx, bx
mov wptr XMS_to_addr, bx ; Initialize XMS destination
mov wptr XMS_to_addr+2, bx ; address (offset into extended
; memory block)

call save_xms_seg ; Attempt to save the program
; block
jc write_error ; Carry set = failure, return

IFNDEF NOFRAG
; Next, save the extra DOS segments
xms_extra_save: mov cx, wptr extra_count ; Number of extra blocks to save
jcxz save_xms_ok ; If CX = 0, we exit routine

mov di, offset dos_blocks ; DI -> array of segment/size
; pairs

xms_extra_save_loop:
mov ax, wptr [di].block_seg
mov es, ax ; ES = segment to save
mov ax, wptr [di].block_size; AX = size in paragraphs
push cx
push di
call save_xms_seg ; Attempt to save this block
pop di
pop cx
jc write_error ; Carry flag set == error
add di, size dos_block
loop xms_extra_save_loop ; Keep going through all blocks

ENDIF
jmp short save_xms_ok

write_error: mov dx, wptr handle ; Free allocated handle
mov ah, 0Ah
call dptr XMS_proc ; Falls through to failure
; code

save_xms_er: stc
jmp short save_xms_ret

; Initialize pointer
; to restore routine
save_xms_ok: mov wptr restore_proc, offset @code:restore_xms
clc

save_xms_ret: ret
save_xms endp


; *****************************************************************************
; save_xms_seg Attempts to save a chunk of RAM to XMS memory
;
; Entry: ES points to the segment to save
; AX contains its length (in paragraphs)
; handle holds the XMS handle to write to
; XMS_to_addr contains offset into extended memory for write
;
; Return: Carry set on error, carry clear on success
; Updates XMS_to_addr for next write
; *****************************************************************************
save_xms_seg proc near
push ds
push es

; Call the XMS copy memory function to do this; fill in the XMS request block
xms_write_size: mov bx, 10h ; AX = # of paragraphs
mul bx ; DX:AX = AX * 10h, convert
; paragraphs to bytes
mov wptr XMS_size, ax ; Store # of bytes to write
mov wptr XMS_size + 2, dx

xms_write_from: xor bx, bx
mov wptr XMS_from, bx ; 0 means from conventional
; memory
mov wptr XMS_from_addr, bx ; Offset of source address
; is 0
mov ax, es ; Segment of source address
; is ES
mov wptr XMS_from_addr + 2, ax

xms_write_to: mov ax, wptr handle ; Destination XMS handle
mov wptr XMS_to, ax ; XMS_to_addr already
; filled in

do_xms_write: mov si, offset @code:XMS_struc ; DS:SI -> XMS request
; structure
mov ah, 0Bh ; Function B, copy memory
call dptr XMS_proc ; Do the memory copy move
cmp ax, 1 ; AX = 1 means success
jnz save_xms_seg_er ; Success, all done!

save_xms_seg_ok:mov ax, wptr XMS_size ; Retrieve length
mov dx, wptr XMS_size + 2 ; (32 bits)
add wptr XMS_to_addr, ax ; Add two 32-bit values
adc wptr XMS_to_addr + 2, dx ; Update XMS write pointer
clc ; Signal success
jmp short save_xms_seg_ret

save_xms_seg_er:stc

save_xms_seg_ret:
pop es
pop ds
ret
save_xms_seg endp

ENDIF
; *****************************************************************************


; *****************************************************************************
; save_ems Attempts to save program to EMS 4.0 expanded memory
;
; Entry: DS points to our variables
;
; Return: Carry set on error, carry clear on success
; If successful, updates restore_proc with the address of
; the EMS restore routine
; *****************************************************************************
IFDEF USE_EMS
save_ems proc near

assume ds:@code ; Tell MASM DS points to our
; variables

call ems4_installed ; Check if EMS 4.0 installed
or ax, ax ; AX = 0 if not installed
jnz ems_inst ; AX != 0, ems installed
jmp short save_ems_er ; AX = 0, no EMS, error!

ems_inst: mov bx, wptr total_paras ; Total # of paragraphs we need
mov cl, 10 ; Convert Paragraphs to 16K
; pages
shr bx, cl
inc bx ; BX = pages needed
mov bptr pages_used, bl ; Save for later use

mov ah, 43h ; EMM function 43h, allocate
int 67h
or ah, ah ; OK return code?
jz ems_alloc_ok ; Yes, skip ahead
jmp short save_ems_er ; No, not enough EMS

ems_alloc_ok: mov wptr handle, dx ; Returned handle in DX

; First, attempt to save the portion of the program block
ems_prog_save: mov ax, wptr start_seg ; Released segment address
mov es, ax
mov ax, wptr prog_size ; Size (in paragraphs) of
; program block to save

xor bx, bx
mov wptr ems_offset, bx ; Maintain absolute byte offset
mov wptr ems_offset + 2, bx ; pointer into handle

call save_ems_seg ; Attempt to save the program
; block

jc save_ems_fail ; Carry set = failure, return

IFNDEF NOFRAG
; Next, save the extra DOS segments
ems_extra_save: mov cx, wptr extra_count ; Number of extra blocks to save
jcxz save_ems_ok ; If CX = 0, we exit routine

mov di, offset dos_blocks ; DI -> array of segment/size
; pairs

ems_extra_save_loop:
mov ax, wptr [di].block_seg
mov es, ax ; ES = segment to save
mov ax, wptr [di].block_size; AX = size in paragraphs
push cx
push di
call save_ems_seg ; Attempt to save this block
pop di
pop cx
jc save_ems_fail ; Carry flag set == error
add di, size dos_block
loop ems_extra_save_loop ; Keep going through all blocks
ENDIF
jmp short save_ems_ok

save_ems_fail: mov dx, wptr handle ; Failure--free handle
mov ah, 45h
int 67h ; Falls through to failure code

; Initialize pointer
; to restore routine
save_ems_ok: mov wptr restore_proc, offset @code:restore_ems
clc
jmp short save_ems_ret

save_ems_er: stc

save_ems_ret: ret
save_ems endp

; *****************************************************************************
; save_ems_seg Attempts to save a chunk of RAM to EMS memory
;
; Entry: ES points to the segment to save
; AX contains its length (in paragraphs)
; handle holds the EMS handle to write to
; ems_offset holds the 32-bit absolute offset in expanded
; memory to write this block to
;
; Return: Carry set on error, carry clear on success
; Updates ems_offset with proper offset for next write
; *****************************************************************************
save_ems_seg proc near
push ds
push es

assume ds:@code ; Tell MASM DS points to our
; variables

; Call the EMS copy memory function to do this; fill in the eMS request block
ems_write_size: mov bx, 10h ; AX = # of paragraphs
mul bx ; DX:AX = AX * 10h, convert
; paragraphs to bytes
mov wptr EMS_size, ax ; Store # of bytes to write
mov wptr EMS_size + 2, dx

ems_write_from: xor bx, bx
mov bptr EMS_from, bl ; Copying from conventional
; memory (0)
mov wptr EMS_from_h, bx ; Source handle is 0
; (conventional memory)
mov wptr EMS_from_o, bx ; Source offset is 0
mov ax, es ; Segment of source address
; is ES
mov wptr EMS_from_s, ax

ems_write_to: mov bptr EMS_to, 1 ; Copying to expanded memory
mov ax, wptr handle
mov wptr EMS_to_h, ax ; Specify EMS handle

; 32-bit absolute offset for copy is in ems_offset
; convert to EMS page:offset (16K pages) values
mov ax, wptr ems_offset ; Load 32-byte offset
mov dx, wptr ems_offset + 2
mov bx, ax ; Save a copy of ax (low 16
; bits)
and ax, 0011111111111111b ; Get (ax & (16K - 1)), this
; is the offset (14 bits)
mov wptr EMS_to_o, ax ; Save page offset
mov cl, 14
shr bx, cl ; Move low 2 bits of page
; into low 2 bits of bx
mov cl, 2
shl dx, cl ; Move hi ? bits of page
; into dx shl 2
or dx, bx ; DX = page number (combine
; two values)
mov wptr EMS_to_s, dx ; Save

mov ax, wptr EMS_size ; Retrieve size of copy
mov dx, wptr EMS_size + 2
add wptr ems_offset, ax ; Update EMS copy pointer
adc wptr ems_offset + 2, dx ; for next EMS write

do_ems_write: mov si, offset @code:EMS_struc ; DS:SI -> EMS request
; structure
mov ax, 5700h ; Function 57 (copy/exchange
; memory), sub 0, copy
; memory
int 67h ; Call EMS manager
or ah, ah ; AH = 0 means success
jnz save_ems_seg_er ; Not 0 means error

save_ems_seg_ok:clc ; Signal success
jmp short save_ems_seg_ret

save_ems_seg_er:stc

save_ems_seg_ret:
pop es
pop ds
ret
save_ems_seg endp
ENDIF
; *****************************************************************************


; *****************************************************************************
; save_disk Attempts to save program to DOS disk file
;
; Entry: DS points to our variables
;
; Return: Carry set on error, carry clear on success
; If successful, updates restore_proc with the address of
; the disk restore routine
; *****************************************************************************
IFDEF USE_DISK
save_disk proc near
push es

assume ds:@code ; Tell MASM DS points to our
; variables

creat_file: mov dx, offset @code:fname ; DS:DX -> file name
mov ah, 3Ch ; Create/truncate file
mov cx, 02h ; Create a hidden file
int 21h ; Call DOS
jc save_disk_er ; Carry set, couldn't create
; file

creat_ok: mov wptr handle, ax ; Save handle returned by DOS

; First, attempt to save the portion of the program block
disk_prog_save: mov ax, wptr start_seg ; Released segment address
mov es, ax
mov ax, wptr prog_size ; Size (in paragraphs) of
; program block
call save_disk_seg ; Attempt to save the program
; block
jc disk_write_er ; Carry flag set == error

IFNDEF NOFRAG
; Next, save the extra DOS segments
disk_extra_save:
mov cx, wptr extra_count ; Number of extra blocks to save
jcxz save_disk_ok ; If CX = 0, we exit routine

mov di, offset dos_blocks ; DI -> array of segment/size
; pairs

disk_extra_save_loop:
mov ax, wptr [di].block_seg
mov es, ax ; ES = segment to save
mov ax, wptr [di].block_size; AX = size in paragraphs
push cx
push di
call save_disk_seg ; Attempt to save this block
pop di
pop cx
jc disk_write_er ; Carry flag set == error
add di, size dos_block
loop disk_extra_save_loop ; Keep going through all blocks

ENDIF
jmp short save_disk_ok


disk_write_er: mov ah, 3Eh ; Close file first
mov bx, wptr handle
int 21h
stc
jmp short save_disk_ret


save_disk_ok: mov ah, 3Eh ; 3eh = close file
mov bx, wptr handle
int 21h

; Initialize pointer
; to restore routine
mov wptr restore_proc, offset @code:restore_disk
clc
jmp short save_disk_ret

save_disk_er: stc

save_disk_ret: pop es
ret
save_disk endp


; *****************************************************************************
; save_disk_seg Attempts to save a chunk of RAM to DOS disk file
;

; Entry: ES points to the segment to save
; AX contains its length (in paragraphs)
; handle holds the file handle to write to
;
;
; Return: Carry set on error, carry clear on success
; *****************************************************************************
save_disk_seg proc near
push ds
push es
push di

assume ds:@code

mov wptr paras_left, ax ; Used to count paras written
mov bx, es
mov ds, bx ; DS -> segment to write

assume ds:nothing

disk_write_32k: cmp ax, 0800h ; paras_left less than 32K?
jb finish_disk_write ; Yes, exit
sub wptr cs:paras_left, 800h; We will write 32K bytes now

mov ah, 40h ; DOS function to write to file
mov bx, wptr cs:handle ; BX = file handle to write to
mov cx, 8000h ; Write 32K bytes
xor dx, dx ; DS:DX is buffer to write
int 21h ; Write data to file
jc save_disk_seg_er ; This write failed--escape

disk_write_ok: mov ax, ds ; Move write pointer in memory
add ax, 800h ; We just wrote 1K paragraphs
mov ds, ax
mov ax, wptr cs:paras_left ; AX checked above
jmp short disk_write_32k ; Loop on next 32K

finish_disk_write:
mov cl, 4 ; AX= # paragraphs left to write
shl ax, cl ; Paragraphs to bytes
mov cx, ax
mov ah, 40h ; 40h = write to file
mov bx, wptr cs:handle ; BX = file handle to write to
xor dx, dx ; DS:DX = buffer
int 21h ; Call DOS
jc save_disk_seg_er ; Carry set, error (close file
; first)
;
; The next two lines added to trap disk full error
; A. Warnock, ST Systems Corp.
; Goddard Space Flight Center
; Greenbelt, MD 21044
; Jan. 4, 1991
;
cmp ax,cx ; Was write complete?
jne save_disk_seg_er ; No - disk must be full

save_disk_seg_ok:

clc
jmp short save_disk_seg_ret

save_disk_seg_er:
stc

save_disk_seg_ret:
pop di
pop es
pop ds

ret
save_disk_seg endp



ENDIF
; *****************************************************************************

END





  3 Responses to “Category : C Source Code
Archive   : IMDSRC78.ZIP
Filename : SWAP.ASM

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/