home *** CD-ROM | disk | FTP | other *** search
/ Zodiac Super OZ / MEDIADEPOT.ISO / FILES / 16 / FREEDOS.ZIP / FD_A4PRE.ZIP / SOURCE / MC-SWAP.ZIP / MC-SWAP.ASM next >
Assembly Source File  |  1995-06-23  |  83KB  |  1,807 lines

  1. page 60, 132
  2.  
  3. ;
  4. ; Original SWAP300 code by Marty Del Vecchio was modified by Yury Semenov
  5. ; (yury@petre.odessa.ua ; Fidonet#2:467/2)  on June 6, 1995
  6. ; for using in DDS MICRO-C PC86 v. 3.13 and Arrowsoft Assembler environment.
  7. ; This modifications was done in the frames of Free-DOS project
  8. ; (coordinator: M. "Hannibal" Toal <hannibal@iastate.edu>).
  9. ;
  10.  
  11. ;   SWAP.ASM        Version 3.00    October 4, 1990
  12. ;
  13. ;   Contains code and data needed to swap most of the current program out
  14. ;   to extended memory, expanded memory, or disk; execute another program;
  15. ;   and re-load the original program back into memory.
  16. ;
  17. ;   Copyright (C) 1990 by Marty Del Vecchio
  18. ;   Released to the public domain for free use by all
  19. ;   Product is supplied as is and author disclaims all warranties,
  20. ;   explicit or implied, about the functionality of the source code
  21. ;   or object modules supplied, and shall not be held responsible
  22. ;   for any damages caused by use of product.
  23. ;
  24. ;   Code to parse default FCB's written and generously donated
  25. ;   by David E. Jenkins (jenkins@wang.com or dave.jenkins@office.wang.com).
  26. ;
  27. ;   Contributions not solicited.  Just appreciate the fact that somebody
  28. ;   took the time to write and comment this code.  If you have any
  29. ;   questions, please contact me at:
  30. ;
  31. ;   Marty Del Vecchio                   Channel 1 BBS
  32. ;   99 Marlboro Road                    Boston, MA
  33. ;   Southborough, MA  01772             (617) 354-8873
  34. ;   (508) 485-9718
  35. ;
  36. ;   internet:  marty@bsn.mceo.dg.com
  37. ;
  38. ;   For information about the contents of this file, see the accompanying
  39. ;   file SWAP.DOC.
  40. ;
  41.  
  42.  
  43. ; Report whether multiple DOS memory blocks will be swapped
  44. IF1
  45.    IFDEF NOFRAG
  46.       %out Multiple DOS memory blocks will NOT be swapped
  47.    ELSE
  48.       %out Multiple DOS memory blocks will be swapped
  49.    ENDIF
  50. ENDIF
  51.  
  52. ; Figure out which save method we are using--EMS, XMS, disk, or a
  53. ;  combination.
  54.  
  55. ; Specified on MASM command line with /D followed by either "xms", "ems",
  56. ;  "disk", or "all".  For example, to create a swap() that will try using
  57. ;  XMS and EMS, you would use "masm swap.asm /Dems /Dxms".
  58.  
  59. ; If none specified, it will use all.  To change the order in which swap()
  60. ;  attempts to save the program to different places, see the function
  61. ;  save_program below.
  62.  
  63. ; First, see if they want all of them...
  64. IFDEF all
  65.    USE_DISK  EQU 1
  66.    USE_XMS   EQU 1
  67.    USE_EMS   EQU 1
  68. ELSE
  69.    ; /Dall not specified--try each individually...
  70.    IFDEF disk
  71.       USE_DISK  EQU 1
  72.    ENDIF
  73.  
  74.    IFDEF xms
  75.       USE_XMS   EQU 1
  76.     ENDIF
  77.  
  78.    IFDEF ems
  79.       USE_EMS   EQU 1
  80.    ENDIF
  81.  
  82. ENDIF
  83.  
  84. ; Now see if they declared anything--if not, it will use them all
  85. IFNDEF USE_DISK
  86.    IFNDEF USE_EMS
  87.       IFNDEF USE_XMS
  88.          USE_DISK  EQU 1
  89.          USE_XMS   EQU 1
  90.          USE_EMS   EQU 1
  91.       ENDIF
  92.    ENDIF
  93. ENDIF
  94.  
  95. ; Constant definitions for easier reading
  96. STDERR          equ     2           ; Standard DOS file handle for error output
  97. GET_VECTOR      equ     35h         ; DOS function to get interrupt vector
  98. EMM_INT         equ     67h         ; EMS interrupt vector
  99. EMM_NAME_LEN    equ     8           ; Length of EMS device driver name
  100. MAX_DOS_CMD     equ     127         ; Maximum DOS command-line length
  101.  
  102. ; If we will swap out all DOS memory blocks a program owns, we need a
  103. ;   place to store information about them
  104. MAX_EXTRA       equ     16          ; Maximum number of extra DOS allocation blocks to swap
  105.  
  106. dos_block       struc               ; Structure for extra DOS memory blocks
  107. block_seg       dw      0           ; User's segment address of block
  108. block_size      dw      0           ; Size in paragraphs of block
  109. dos_block       ends
  110.  
  111.  
  112. bptr            equ     byte ptr    ; Means we're loading/storing 8 bits
  113. wptr            equ     word ptr    ; Means we're loading/storing 16 bits
  114. dptr            equ     dword ptr   ; Means we're loading/storing 32 bits
  115.  
  116.  
  117. ; All code and data must be in the code segment, which is the first segment
  118. ;  in all Turbo C, Turbo C++, and Microsoft C memory models.
  119.  
  120. ; If we are in the Medium or Large models, there are multiple code segments.
  121. ;  If this is the case, our default code segment name will be "SWAP_TEXT".
  122. ;  This is acceptable in most cases, except when using the Turbo C integrated
  123. ;  development environment.  See SWAP.DOC for details.
  124.  
  125.   CSEG SEGMENT BYTE PUBLIC 'CODE'
  126.   ASSUME CS:CSEG, DS:CSEG, ES:NOTHING
  127.  
  128. ; *****************************************************************************
  129. ; Our resident data declarations--this data will be needed after the swap
  130. ;  has occurred, and thus must be above the resident line
  131. ; *****************************************************************************
  132.  
  133. ; *****************************************************************************
  134. ; First, all variables that will be used by all versions assembled from
  135. ; this source file, regardless of what save options are selected
  136. ; *****************************************************************************
  137. ret_code    dw      0           ; Return code (to C caller) of this swap routine
  138.                                 ;   0 = success
  139.                                 ;   1 = unable to shrink DOS memory allocation
  140.                                 ;   2 = unable to save program to EMS
  141.                                 ;   3 = unable to execute requested program
  142.                                 ; These values must be the same as those listed
  143.                                 ;  in SWAP.H!!!!!!!!!
  144.  
  145. ; *****************************************************************************
  146. ; Variables that deal with DOS' memory allocation blocks
  147. old_size    dw      0           ; The old size (in paragraphs) of this program
  148. new_size    dw      0           ; The new "resident" size, doesn't include code/data swapped
  149. prog_size   dw      0           ; Size in paragraphs of saved part of program block (old_size - new_size)
  150. total_paras dw      0           ; Size (in paragraphs) of all blocks combined
  151. my_psp      dw      0           ; This program's Program Segment Prefix (PSP)
  152. mcb_psp     dw      0           ; The PSP address in this program's memory block
  153. start_seg   dw      0           ; Segment address of released memory
  154.  
  155. ; If we are swapping all DOS memory blocks a program owns, we store
  156. ;  them in this array of structures
  157. IFNDEF NOFRAG
  158. extra_count dw      0           ; # of extra blocks to save (not including program block)
  159. dos_blocks  dos_block MAX_EXTRA dup (<>)    ; Array for extra blocks
  160. ENDIF
  161. ; *****************************************************************************
  162.  
  163. ; *****************************************************************************
  164. ; Variable used during the save/restore process
  165. handle      dw      0           ; EMS/XMS/disk file handle
  166. ; *****************************************************************************
  167.  
  168. ; *****************************************************************************
  169. ; A temporary stack in our code segment, and associated variables
  170. old_sp      dw      0               ; Place to save this program's stack
  171. old_ss      dw      0               ;  information while executing new program
  172.  
  173. ; XMS driver needs a large stack (at least 256 bytes free when called)
  174. IFDEF USE_XMS
  175. new_stack   db      320 dup ('?')   ; Temporary stack we can address after swap
  176. ELSE
  177. new_stack   db      128 dup ('?')   ; Temporary stack we can address after swap
  178. ENDIF
  179. new_sp      label   word            ; Point SP to "top" of stack
  180. ; *****************************************************************************
  181.  
  182. ; *****************************************************************************
  183. ; Variables that deal with the execution of the new program
  184. prog_name   db      128 dup (0)     ; Storage for name of program to execute
  185. cmd_pad     db      0               ; Maintain word-alignment for variables
  186. cmd_len     db      0               ; Storage for length of command line parameters
  187. cmd_line    db      128 dup (0)     ; Storage for command line parameters
  188.  
  189. param_blk   label   byte            ; Program Parameter Block--pass to DOS on exec call
  190. env_seg     dw      0               ; Environment segment address, 0 means a COPY of ours
  191. cmd_ofs     dw      offset CSEG:cmd_len    ; Offset address of command line
  192. ;cmd_ofs     dw      offset cmd_len    ; Offset address of command line
  193. cmd_seg     dw      seg cmd_line    ; Segment address of command line
  194. fcb_5C_ofs  dw      offset fcb5C    ; Far pointers to default FCB's.  Some
  195. fcb_5C_seg  dw      seg fcb5C       ;  programs (such as DOS' CHKDSK.COM)
  196. fcb_6C_ofs  dw      offset fcb6C    ;  depend on these being parsed from
  197. fcb_6C_seg  dw      seg fcb6C       ;  the command line before the EXEC call
  198. ; *****************************************************************************
  199.  
  200. ; *****************************************************************************
  201. ; Variables needed to parse the command line into the default FCB's
  202. c_l_length  dw      0               ; Command line length
  203. si_5C       dw      0               ; Save area for pointer to cmd line arg 1
  204. si_6C       dw      0               ; Save area for pointer to cmd line arg 2
  205.  
  206. ; Default FCB to be passed to PSP offset 5C (hex)
  207. fcb5C       label   byte
  208. fcb5C_drive db      0               ; drive
  209. fcb5C_fname db      8 dup (?)       ; file name
  210. fcb5C_ext   db      3 dup (?)       ; extension
  211. fcb5C_pad   db      4 dup (?)       ; unused
  212.  
  213. ; Default FCB to be passed to PSP offset 6C (hex)
  214. fcb6C       label   byte
  215. fcb6C_drive db      0               ; drive
  216. fcb6C_fname db      8 dup (?)       ; file name
  217. fcb6C_ext   db      3 dup (?)       ; extension
  218. fcb6C_pad   db      4 dup (?)       ; unused
  219. ; *****************************************************************************
  220.  
  221. exec_ret    db      0               ; Return code from executed program
  222. exec_pad    db      0               ; Maintain word-alignment for variables
  223. restore_proc dw     0               ; Address of appropriate restore routine
  224.  
  225. ; *****************************************************************************
  226. ; Message to display to screen when we can't reload program
  227. abort_msg   db      0dh, 0ah, 'SWAP: Unable to reload program.', 0dh, 0ah
  228. abort_len   dw      $ - offset CSEG:abort_msg
  229. ; *****************************************************************************
  230.  
  231. ; *****************************************************************************
  232. ; Next, the variables needed only for certain versions of the routine,
  233. ;  depending on which save/restore options are chosen
  234. ; *****************************************************************************
  235.  
  236. ; *****************************************************************************
  237. ; Variables needed only when swapping to XMS
  238. IFDEF USE_XMS
  239. XMS_proc    dd      0               ; Address of XMS entry point
  240.  
  241. XMS_struc       label   byte        ; Structure needed to move memory with XMS
  242. XMS_size        dd      0           ; # of bytes to move (must be even)
  243. XMS_from        dw      0           ; Handle of source, 0=conventional memory
  244. XMS_from_addr   dd      0           ; Address of source memory
  245. XMS_to          dw      0           ; Handle of destionation, 0=conventional memory
  246. XMS_to_addr     dd      0           ; Address of destination memory
  247. ENDIF
  248. ; *****************************************************************************
  249.  
  250. ; *****************************************************************************
  251. ; Variables needed only when swapping to EMS
  252. IFDEF USE_EMS
  253. pages_used  db      0           ; # of pages of EMS used
  254. emm_name    db      'EMMXXXX0'  ; Name of EMS device driver
  255.  
  256. EMS_struc   label   byte        ; Structure needed to move memory with EMS 4.0+
  257. EMS_size    dd      0           ; # of bytes to move
  258. EMS_from    db      0           ; Type of source memory (0 = conventional, 1 = expanded)
  259. EMS_from_h  dw      0           ; Source memory handle (0 = conventional)
  260. EMS_from_o  dw      0           ; Offset of source memory (expanded = 0-16K, conventional = 0-64K)
  261. EMS_from_s  dw      0           ; Segment/page of source (expanded = logical page, conventional = segment)
  262. EMS_to      db      0           ; Type of desination memory (0 = conventional, 1 = expanded)
  263. EMS_to_h    dw      0           ; Destination memory handle (0 = conventional)
  264. EMS_to_o    dw      0           ; Offset of destination memory (expanded = 0-16K, conventional = 0-64K)
  265. EMS_to_s    dw      0           ; Segment/page of destination (expanded = logical page, conventional = segment)
  266.  
  267. ems_offset  dd      0           ; Destination pointer--absolute byte offset into handle
  268. ENDIF
  269. ; *****************************************************************************
  270.  
  271. ; *****************************************************************************
  272. ; Variables needed only when swapping to disk
  273. IFDEF USE_DISK
  274. fname       db      80 dup (0)  ; Name of the file data is saved to/read from
  275. paras_left  dw      0           ; temporary counter
  276. ENDIF
  277. ; *****************************************************************************
  278.  
  279.  
  280.  
  281. ; *****************************************************************************
  282. ; Version-dependent code--only assemble the routine to restore the program
  283. ; from each media (XMS, EMS, disk) if it was specified on the command line
  284. ; *****************************************************************************
  285.  
  286.  
  287. ; *****************************************************************************
  288. ; restore_xms   Attempts to restore program from XMS extended memory
  289. ;
  290. ; Entry:        DS points to our variables
  291. ;               Program was saved to XMS extended memory (block referred to by handle)
  292. ;
  293. ; Return:       Carry set on error, carry clear on success
  294. ; *****************************************************************************
  295. IFDEF USE_XMS
  296. restore_xms     proc    near
  297.                 push    es
  298.  
  299.                 assume  ds:CSEG                    ; Tell MASM that DS points to our variables
  300.  
  301. ; First, attempt to restore the portion of the program block that was saved
  302. xms_prog_rest:  mov     ax, wptr start_seg          ; Released segment address
  303.                 mov     es, ax
  304.                 mov     ax, wptr prog_size          ; Size (in paragraphs)
  305.  
  306.                 xor     bx, bx
  307.                 mov     wptr XMS_from_addr, bx      ; Initialize XMS source
  308.                 mov     wptr XMS_from_addr + 2, bx  ;  address (offset into extended memory block)
  309.  
  310.                 call    rest_xms_seg                ; Attempt to restore it
  311.  
  312. IFNDEF NOFRAG
  313.                 jc      xms_dealloc                 ; Carry set = error, exit
  314.  
  315. ; Next, restore the extra DOS segments
  316. xms_extra_rest: mov     cx, wptr extra_count    ; Number of extra blocks to save
  317.                 jcxz    xms_dealloc             ; If CX = 0, we exit routine
  318.  
  319.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  320.  
  321. xms_extra_rest_loop:
  322.                 mov     ax, wptr [di].block_seg
  323.                 mov     es, ax                  ; ES = segment to restore
  324.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  325.                 push    cx
  326.                 push    di
  327.                 call    rest_xms_seg            ; Attempt to restore this block
  328.                 pop     di
  329.                 pop     cx
  330.                 jc      xms_dealloc             ; Carry flag set == error, exit
  331.                 add     di, size dos_block
  332.                 loop    xms_extra_rest_loop     ; Keep going through all blocks
  333.  
  334. ENDIF
  335.  
  336. xms_dealloc:    rcl     bl, 1                   ; Save carry flag in low bit of bl
  337.  
  338.                 mov     dx, wptr handle         ; First, free XMS handle
  339.                 mov     ah, 0Ah
  340.                 push    bx
  341.                 call    dptr XMS_proc
  342.                 pop     bx
  343.  
  344.                 rcr     bl, 1                   ; Restore carry flag from bl low bit
  345.  
  346. restore_xms_ret:pop     es
  347.                 ret
  348. restore_xms     endp
  349.  
  350.  
  351. ; *****************************************************************************
  352. ; rest_xms_seg  Attempts to restore a chunk of RAM from XMS memory
  353. ;
  354. ; Entry:        ES points to the segment to restore
  355. ;               AX contains its length (in paragraphs)
  356. ;               handle holds the XMS handle to read from
  357. ;               XMS_from_addr contains offset into extended memory for read
  358. ;
  359. ; Return:       Carry set on error, carry clear on success
  360. ;               Updates XMS_from_addr for next read
  361. ; *****************************************************************************
  362. rest_xms_seg    proc    near
  363.                 push    ds
  364.                 push    es
  365.  
  366. ; Call the XMS copy memory function to do this; fill in request block
  367. xms_read_size:  mov     bx, 10h                     ; AX = # of paragraphs, convert to bytes
  368.                 mul     bx                          ; DX:AX = AX * 10h, # of bytes to read
  369.                 mov     wptr XMS_size, ax           ; Store # of bytes to read
  370.                 mov     wptr XMS_size + 2, dx
  371.  
  372. xms_read_from:  mov     ax, wptr handle             ; Source XMS handle
  373.                 mov     wptr XMS_from, ax           ;  XMS_from_addr already filled in
  374.  
  375. xms_read_to:    xor     bx, bx
  376.                 mov     wptr XMS_to, bx             ; Read into conventional memory
  377.                 mov     wptr XMS_to_addr, bx        ; Offset of dest address
  378.                 mov     ax, es                      ; Segment of destination address
  379.                 mov     wptr XMS_to_addr + 2, ax
  380.  
  381. do_xms_read:    mov     si, offset CSEG:XMS_struc  ; DS:SI -> XMS structure
  382.                 mov     ah, 0Bh
  383.                 call    dptr XMS_proc               ; Do the move
  384.                 cmp     ax, 1
  385.                 jnz     rest_xms_seg_er
  386.  
  387. rest_xms_seg_ok:mov     ax, wptr XMS_size           ; Retrieve length
  388.                 mov     dx, wptr XMS_size + 2       ;  (32 bits)
  389.                 add     wptr XMS_from_addr, ax      ; Add two 32-bit values
  390.                 adc     wptr XMS_from_addr + 2, dx  ; Update XMS read pointer
  391.                 clc                                 ; Signal success
  392.                 jmp     short rest_xms_seg_ret
  393.  
  394. rest_xms_seg_er:stc
  395.  
  396. rest_xms_seg_ret:
  397.                 pop     es
  398.                 pop     ds
  399.                 ret
  400. rest_xms_seg    endp
  401.  
  402. ENDIF
  403. ; *****************************************************************************
  404.  
  405.  
  406. ; *****************************************************************************
  407. ; restore_ems   Attempts to restore program from EMS expanded memory
  408. ;
  409. ; Entry:        DS points to our variables
  410. ;               Program was saved to EMS expanded memory (block referred to by handle)
  411. ;
  412. ; Return:       Carry set on error, carry clear on success
  413. ; *****************************************************************************
  414. IFDEF USE_EMS
  415. restore_ems     proc    near
  416.                 push    es
  417.  
  418.                 assume  ds:CSEG                    ; Tell MASM that DS points to our variables
  419.  
  420. ; First, attempt to restore the portion of the program block that was saved
  421. ems_prog_rest:  mov     ax, wptr start_seg          ; Released segment address
  422.                 mov     es, ax
  423.                 mov     ax, wptr prog_size          ; Size (in paragraphs)
  424.  
  425.                 xor     bx, bx
  426.                 mov     wptr ems_offset, bx         ; Maintain absolute by offset
  427.                 mov     wptr ems_offset + 2, bx     ;  pointer relative to handle
  428.  
  429.                 call    rest_ems_seg                ; Attempt to restore it
  430.  
  431. IFNDEF NOFRAG
  432.                 jc      ems_dealloc                 ; Carry set = error, exit
  433.  
  434. ; Next, restore the extra DOS segments
  435. ems_extra_rest: mov     cx, wptr extra_count    ; Number of extra blocks to save
  436.                 jcxz    ems_dealloc             ; If CX = 0, we exit routine
  437.  
  438.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  439.  
  440. ems_extra_rest_loop:
  441.                 mov     ax, wptr [di].block_seg
  442.                 mov     es, ax                  ; ES = segment to restore
  443.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  444.                 push    cx
  445.                 push    di
  446.                 call    rest_ems_seg            ; Attempt to restore this block
  447.                 pop     di
  448.                 pop     cx
  449.                 jc      ems_dealloc             ; Carry flag set == error, exit
  450.                 add     di, size dos_block
  451.                 loop    ems_extra_rest_loop     ; Keep going through all blocks
  452.  
  453. ENDIF
  454.  
  455. ems_dealloc:    rcl     bl, 1                   ; Save carry flag in low bit of bl
  456.  
  457.                 mov     ah, 45h                 ; Deallocate EMS memory
  458.                 mov     dx, wptr handle         ; Specify which handle
  459.                 push    bx
  460.                 int     67h
  461.                 pop     bx
  462.  
  463.                 rcr     bl, 1                   ; Restore carry flag from bl low bit
  464.  
  465. restore_ems_ret:pop     es
  466.                 ret
  467. restore_ems     endp
  468.  
  469. ; *****************************************************************************
  470. ; rest_ems_seg  Attempts to restore a chunk of RAM from EMS memory
  471. ;
  472. ; Entry:        ES points to the segment to restore
  473. ;               AX contains its length (in paragraphs)
  474. ;               handle holds the EMS handle to write to
  475. ;               ems_offset holds the 32-bit absolute offset in expanded
  476. ;                memory to read this block from
  477. ;
  478. ; Return:       Carry set on error, carry clear on success
  479. ;               Updates ems_offset with proper offset for next read
  480. ; *****************************************************************************
  481. rest_ems_seg    proc    near
  482.                 push    ds
  483.                 push    es
  484.  
  485.                 assume  ds:CSEG                ; Tell MASM DS points to our variables
  486.  
  487. ; Call the EMS copy memory function to do this; fill in the EMS request block
  488. ems_read_size:  mov     bx, 10h                     ; AX = # of paragraphs
  489.                 mul     bx                          ; DX:AX = AX * 10h, convert paragraphs to bytes
  490.                 mov     wptr EMS_size, ax           ; Store # of bytes to write
  491.                 mov     wptr EMS_size + 2, dx
  492.  
  493. ems_read_to:    xor     bx, bx
  494.                 mov     bptr EMS_to, bl             ; Copying to conventional memory (0)
  495.                 mov     wptr EMS_to_h, bx           ; Destination handle is 0 (conventional memory)
  496.                 mov     wptr EMS_to_o, bx           ; Destination offset is 0
  497.                 mov     ax, es                      ; Segment of destination address is ES
  498.                 mov     wptr EMS_to_s, ax
  499.  
  500. ems_read_from:  mov     bptr EMS_from, 1            ; Copying to expanded memory
  501.                 mov     ax, wptr handle
  502.                 mov     wptr EMS_from_h, ax         ; Specify EMS handle
  503.  
  504.                 ; 32-bit absolute offset for copy is in ems_offset
  505.                 ;  convert to EMS page:offset (16K pages) values
  506.                 mov     ax, wptr ems_offset         ; Load 32-byte offset
  507.                 mov     dx, wptr ems_offset + 2
  508.                 mov     bx, ax                      ; Save a copy of ax (low 16 bits)
  509.                 and     ax, 0011111111111111b       ; Get (ax & (16K - 1)), this is the offset (14 bits)
  510.                 mov     wptr EMS_from_o, ax         ; Save page offset
  511.                 mov     cl, 14
  512.                 shr     bx, cl                      ; Move low 2 bits of page into low 2 bits of bx
  513.                 mov     cl, 2
  514.                 shl     dx, cl                      ; Move hi ? bits of page into dx shl 2
  515.                 or      dx, bx                      ; DX = page number (combine two values)
  516.                 mov     wptr EMS_from_s, dx         ; Save
  517.  
  518.                 mov     ax, wptr EMS_size           ; Retrieve size of copy
  519.                 mov     dx, wptr EMS_size + 2
  520.                 add     wptr ems_offset, ax         ; Update EMS copy pointer
  521.                 adc     wptr ems_offset + 2, dx     ;  for next EMS write
  522.  
  523. do_ems_read:    mov     si, offset CSEG:EMS_struc  ; DS:SI -> EMS request structure
  524.                 mov     ax, 5700h                   ; Function 57 (copy/exchange memory), sub 0, copy memory
  525.                 int     67h                         ; Call EMS manager
  526.                 or      ah, ah                      ; AH = 0 means success
  527.                 jnz     rest_ems_seg_er             ; Not 0 means error
  528.  
  529. rest_ems_seg_ok:clc                                 ; Signal success
  530.                 jmp     short rest_ems_seg_ret
  531.  
  532. rest_ems_seg_er:stc
  533.  
  534. rest_ems_seg_ret:
  535.                 pop     es
  536.                 pop     ds
  537.                 ret
  538. rest_ems_seg    endp
  539.  
  540. ENDIF
  541. ; *****************************************************************************
  542.  
  543.  
  544. ; *****************************************************************************
  545. ; restore_disk  Attempts to restore program from DOS disk file
  546. ;
  547. ; Entry:        DS points to our code segment
  548. ;               Program was saved to DOS disk file (full path stored in fname)
  549. ;
  550. ; Return:       Carry set on error, carry clear on success
  551. ; *****************************************************************************
  552. IFDEF USE_DISK
  553. restore_disk    proc    near
  554.  
  555.                 push    ds
  556.  
  557.                 assume  ds:CSEG                ; Tell MASM that DS points to our variables
  558.  
  559. open_file:      mov     dx, offset CSEG:fname  ; DS:DX -> file name
  560.                 mov     ax, 3D42h               ; DOS function 3Dh, open file
  561.                                                 ;  al = open for read only, deny none
  562.                 int     21h                     ; Call DOS
  563.                 jnc     open_ok                 ; Carry clear = all OK
  564.                 jmp     short restore_disk_ret  ; Carry set, just exit with error
  565.  
  566. open_ok:        mov     wptr handle, ax         ; File handle returned from DOS
  567.  
  568. ; First, restore the program block contents saved to disk
  569. disk_prog_rest: mov     ax, wptr start_seg      ; Get segment of program block saved
  570.                 mov     es, ax
  571.                 mov     ax, wptr prog_size      ; Get size of program block saved
  572.                 call    rest_disk_seg           ; Try to restore it
  573.                 jc      restore_disk_er         ; Carry set == error
  574.  
  575. IFNDEF NOFRAG
  576. ; Next, restore the contents of the extra blocks saved to disk
  577. disk_extra_rest:
  578.                 mov     cx, wptr extra_count    ; Number of extra blocks to restore
  579.                 jcxz    close_read              ; IF CX = 0, we're done restoring
  580.  
  581.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  582.  
  583. disk_extra_rest_loop:
  584.                 mov     ax, wptr [di].block_seg
  585.                 mov     es, ax                  ; ES = segment to restore to
  586.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  587.                 push    cx
  588.                 push    di
  589.                 call    rest_disk_seg           ; Attempt to restore this block
  590.                 pop     di
  591.                 pop     cx
  592.                 jc      restore_disk_er         ; Error--exit routine
  593.                 add     di, size dos_block
  594.                 loop    disk_extra_rest_loop    ; Look for next DOS block
  595.  
  596. ENDIF
  597.  
  598. close_read:     mov     ah, 3Eh                 ; Close file
  599.                 int     21h                     ; Call DOS
  600.  
  601. restore_disk_ok:clc                             ; Signal success
  602.                 jmp     short restore_disk_ret  ;  and Exit
  603.  
  604. restore_disk_er:
  605.                 mov     ah, 3Eh                 ; Error, close file first
  606.                 int     21h                     ; Call DOS
  607.                 stc                             ; Signal failure
  608.  
  609. restore_disk_ret:
  610.                 pop     ds                      ; Restore our DS! (error in revs 2.11 and before)
  611.  
  612.                 rcl     bl, 1                   ; Save carry flag in low bit of bl
  613.  
  614.                 mov     dx, offset CSEG:fname  ; DS:DX -> file name
  615.                 mov     ah, 41h                 ; DOS function 41h, delete file
  616.                 push    bx
  617.                 int     21h                     ; Call DOS
  618.                 pop     bx
  619.  
  620.                 rcr     bl, 1                   ; Restore carry flag from low bit of bl
  621.  
  622.                 ret
  623. restore_disk    endp
  624.  
  625. ; *****************************************************************************
  626. ; rest_disk_seg Attempts to restore a chunk of RAM from the DOS disk file
  627. ;
  628. ; Entry:        ES points to the segment to restore
  629. ;               AX contains its length (in paragraphs)
  630. ;               handle contains the file handle to read from
  631. ;               Program was saved to DOS disk file (fname)
  632. ;
  633. ; Return:       Carry set on error, carry clear on success
  634. ; *****************************************************************************
  635. rest_disk_seg   proc    near
  636.                 push    es
  637.                 push    ds
  638.  
  639.                 mov     bx, es
  640.                 mov     ds, bx                  ; DS -> segment to restore to
  641.  
  642.                 assume  ds:nothing
  643.  
  644.                 mov     wptr cs:paras_left, ax  ; Keep count in this variable
  645.  
  646. disk_read_32k:  cmp     ax, 0800h                   ; Less than 32K left?
  647.                 jb      last_disk_read              ; Yes, do last read
  648.                 sub     wptr cs:paras_left, 0800h   ; 32K left to read
  649.                 mov     ah, 3Fh                 ; DOS function 3Fh, read file
  650.                 mov     bx, wptr cs:handle      ; BX = handle to read from
  651.                 mov     cx, 8000h               ; Read 32K bytes
  652.                 xor     dx, dx                  ; DS:DX -> buffer to read to
  653.                 int     21h                     ; Call DOS
  654.                 jc      rest_disk_seg_er        ; Carry set = error
  655.  
  656. disk_read_ok:   mov     ax, ds                  ; Address next read location
  657.                 add     ax, 0800h               ; It's 800h paragraphs ahead
  658.                 mov     ds, ax                  ; DS -> new restore location
  659.                 mov     ax, wptr cs:paras_left  ; Expecting this above
  660.                 jmp     short disk_read_32k     ; Read next 32K
  661.  
  662. last_disk_read: mov     cx, 4                   ; Convert paragraphs to bytes
  663.                 shl     ax, cl
  664.                 mov     cx, ax                  ; # of bytes left in cx
  665.                 mov     ah, 3Fh                 ; Read last bytes
  666.                 mov     bx, wptr cs:handle      ; BX = handle to read from
  667.                 xor     dx, dx                  ; DS:DX -> buffer to restore to
  668.                 int     21h                     ; Call DOS
  669.                 jc      rest_disk_seg_er        ; Error reading!  Close file first
  670.  
  671. rest_disk_seg_ok:
  672.                 clc
  673.                 jmp     short rest_disk_seg_ret
  674.  
  675. rest_disk_seg_er:
  676.                 stc
  677.  
  678. rest_disk_seg_ret:
  679.                 pop     ds
  680.                 pop     es
  681.                 ret
  682. rest_disk_seg   endp
  683.  
  684. ENDIF
  685. ; *****************************************************************************
  686.  
  687.  
  688.                 
  689. ; *****************************************************************************
  690. ; execute_program   Execute the program specified
  691. ;
  692. ; Entry:            param_blk has been initialized
  693. ;                   DS points to our data
  694. ; Return:           puts return code in cs:exec_ret
  695. ; *****************************************************************************
  696. execute_program proc    near                    ; Called only from inside our segment
  697.  
  698.                 push    ds                      ; These are destroyed by the
  699.                 push    es                      ;  DOS EXEC call
  700.  
  701.                 assume  ds:CSEG                ; Tell MASM that DS points to our variables
  702.  
  703. exec_program:   mov     ax, ds                  ; Our path name is in CS (point DS to our segment)
  704.                             ;?YS
  705.                               int 3
  706.                 mov     es, ax                  ; Our parameter block is in CS (point ES to our segment)
  707.                 mov    CSEG:cmd_seg,ax
  708.                 mov    CSEG:fcb_5C_seg,ax
  709.                 mov    CSEG:fcb_6C_seg,ax
  710.                 mov     ax, 4B00h               ; Load and execute program
  711.                 mov     bx, offset CSEG:param_blk
  712.                 mov     dx, offset CSEG:prog_name
  713.                 int     21h                     ; Sets carry flag if error
  714.                                                 ; All registers destroyed
  715.                                                 ;  except CS:IP!
  716.  
  717.                 assume  ds:nothing              ; Tell MASM that DS doesn't point to our variables
  718.  
  719.                 mov     bptr cs:exec_ret, al    ; Store EXEC code
  720.                 jc      exec_err                ; Ooops
  721.  
  722. get_return:     mov     ah, 4Dh                 ; DOS function to get ret code
  723.                 int     21h                     ; All registers destroyed
  724.                 mov     bptr cs:exec_ret, al    ; Store EXEC code
  725.                 jmp     short exec_exit
  726.  
  727. exec_err:       mov     wptr cs:ret_code, 3     ; Signal error on executing
  728.  
  729. exec_exit:      pop     es
  730.                 pop     ds
  731.  
  732.                 ret
  733.  
  734. execute_program endp
  735.  
  736.  
  737. ; *****************************************************************************
  738. ; err_exit          Prints error message and terminates program
  739. ;
  740. ; Entry:            Nothing.
  741. ; Returns:          Doesn't return--calls DOS terminate function.
  742. ;                   Naturally, we can't use the C runtime routines,
  743. ;                   since they are swapped out.
  744. ; *****************************************************************************
  745. err_exit        proc    near                    ; Called only from inside our segment
  746.  
  747.                 mov     ax, cs
  748.                 mov     ds, ax                  ; Point DS to our data
  749.  
  750.                 assume  ds:CSEG                ; Tell MASM that DS points to our data
  751.  
  752.                 mov     ah, 40h                 ; DOS function to write to file
  753.                 mov     bx, STDERR              ; Write to standard error handle
  754.                 mov     cx, wptr abort_len      ; CX = length of message
  755.                 mov     dx, offset CSEG:abort_msg  ; DS:DX = message
  756.                 int     21h
  757.  
  758.                 mov     ax, 4CFFh           ; Exit, return code 255 decimal (FF hex)
  759.                 int     21h                 ; Exit to DOS, no return
  760.  
  761. err_exit        endp
  762.  
  763.  
  764. ; *****************************************************************************
  765. ; do_exec           Calls the execute routine, then restores program
  766. ;
  767. ; Entry:            Nothing
  768. ; Returns:          Since it is called from the non-resident area, it
  769. ;                   can only return if the program is restored completely.
  770. ; *****************************************************************************
  771. do_exec         proc
  772.                 call    near ptr execute_program    ; Execute the specified program
  773.                 jnc     re_size                     ; No carry, OK
  774.  
  775. exec_er:        mov     wptr ret_code, 3        ; Signal error
  776.  
  777. re_size:        mov     es, wptr my_psp         ; Get our PSP address
  778.                 mov     bx, wptr old_size       ; Increase back to old size
  779.                 mov     ah, 4Ah                 ; DOS function 4Ah = resize
  780.                 int     21h
  781.                 jc      resize_err              ; Carry clear = all OK
  782.  
  783. IFNDEF NOFRAG
  784. ; If necessary, allocate all extra DOS memory blocks our program owned
  785.  
  786.                 mov     cx, wptr extra_count    ; CX = number of extra DOS blocks
  787.                 jcxz    restore_prog            ; If zero, don't bother
  788.                 mov     di, offset dos_blocks   ; DI -> array of addresses/sizes
  789.  
  790.                 push    es
  791.  
  792. alloc_extra_loop:
  793.                 mov     bx, wptr [di].block_size; BX = old size
  794.                 mov     ah, 48h                 ; DOS function to allocate memory block
  795.                 push    cx
  796.                 push    di
  797.                 int     21h
  798.                 pop     di
  799.                 pop     cx
  800.                 jc      resize_err              ; Unlikely error
  801.  
  802. check_alloc:    cmp     ax, wptr [di].block_seg ; Is it the same as the origignal segment address?
  803.                 jnz     resize_err              ; Nope.  We could do some fancy tricks here,
  804.                                                 ;  but for the most part it's not necessary.
  805.  
  806.                 add     di, size dos_block      ; Point to next entry
  807.                 loop    alloc_extra_loop        ; Keep going through extra blocks
  808.  
  809.                 pop     es
  810. ENDIF
  811.                 jmp     short restore_prog
  812.  
  813. resize_err:     call    near ptr err_exit       ; Can't return, exit to DOS
  814.  
  815. restore_prog:   call    wptr restore_proc       ; Restore program from disk
  816.                 jc      resize_err              ; Carry set if error
  817.                                                 ; If no error, it returns
  818.                                                 ;  down to restored code
  819.                 ret
  820. do_exec         endp
  821.  
  822. ; *****************************************************************************
  823. ; *****************************************************************************
  824. para_align      proc    near
  825. new_mcb         db      16 dup (0)          ; DOS will put MCB of released memory here
  826. para_align      endp
  827. ; *****************************************************************************
  828. ; *****************************************************************************
  829.  
  830. ; *****************************************************************************
  831. ; Everything after here is only needed BEFORE we change our allocation size.
  832. ;  Everything below this line will be (temporarily) swapped out of memory,
  833. ;  and thus cannot be used once we shrink our memory allocation.
  834. ; *****************************************************************************
  835.  
  836. ; *****************************************************************************
  837. ;   swap        The routine that does it all
  838. ;
  839. ;   Callable by a C program, takes these parameters (regardless
  840. ;     of which swap options chosen at assembly time, because
  841. ;     C calling conventions let us ignore parameters to the
  842. ;     right if we want to):
  843. ;
  844. ;   swap_both:
  845. ;       prog        Full path name of program to execute
  846. ;       cmdline     Command-line parameters for program to execute
  847. ;       return      Pointer to byte for return code of exec'd program
  848. ;       save_file   Full path name of file in which to save program image (if disk is to be used)
  849. ;
  850. ;   Depending on the memory model used, the pointers to the
  851. ;   parameters each occupy 2 bytes or 4 bytes on the stack.
  852. ;   If there is only one data segment (Small and Medium), each
  853. ;   value is a 2-byte near pointer, with DS assumed as the segment
  854. ;   register.  If there are multiple data segments (Compact and
  855. ;   Large), each value is a 4-byte far pointer, with segment and
  856. ;   offset values each pushed on the stack.
  857. ;
  858. ;   The function is declared with 4 parameters, regardless of whether
  859. ;   disk swapping is being included.  This is because the file name
  860. ;   parameter is the last on the parameter list, which C lets us
  861. ;   ignore if we want.
  862. ;
  863. ;   The swap() routine does not check the program name or command
  864. ;   line to verify that a legal command has been requested--that's
  865. ;   the caller's responsibility!
  866. ;
  867. ; *****************************************************************************
  868.  
  869.                public _swap
  870.  _swap         proc near
  871.                 push    bp
  872.                 mov    bp,sp
  873.   prog        EQU    word ptr [bp+10]
  874.   cmdline    EQU    word ptr [bp+8]
  875.   return    EQU    word ptr [bp+6]
  876.   save_file    EQU    word ptr [bp+4]
  877.  
  878.                 push    si                      ; Save registers needed
  879.                 push    di                      ;  by the caller
  880.                 push    es
  881.                 push    ds
  882.  
  883. point_segs:     mov     ax, cs                  ; Point ES to our segment
  884.                 mov     es, ax                  ;  for copying of parameters
  885.  
  886. ; *****************************************************************************
  887. get_name:       ; Copy program name to our variable, all versions
  888.  
  889. ; If multiple data segments, load DS:SI from stack.  Else, just load SI
  890.                 mov     si, wptr prog           ; Load 16-bit near pointer
  891.  
  892.                 mov     di, offset CSEG:prog_name  ; ES:DI -> our storage area
  893.  
  894. name_loop:      lodsb                           ; Fetch next byte
  895.                 stosb                           ; Save next byte
  896.                 or      al, al                  ; Was it 0 (end of string)?
  897.                 jnz     name_loop               ; No, get next one
  898.  
  899. ; *****************************************************************************
  900.  
  901. ; *****************************************************************************
  902. get_cmd:        ; Copy command line to our variable, all versions
  903.  
  904. ; If multiple data segments, load DS:SI from stack.  Else, just load SI
  905.                 mov     si, wptr cmdline        ; Load 16-bit near pointer
  906.                 
  907.                 mov     di, offset CSEG:cmd_line   ; ES:DI -> our storage area
  908.                 xor     cl, cl                  ; Keep track of length in cl
  909.  
  910. cmd_loop:       lodsb                           ; Fetch next byte from DS:SI
  911.                 or      al, al                  ; Was it 0 (end of string)?
  912.                 jz      cmd_end                 ; Yes, we're done
  913.                 stosb                           ; No, store byte
  914.                 inc     cl                      ; Increment length
  915.                 cmp     cl, MAX_DOS_CMD         ; Are we at maximum cmd length?
  916.                 jnz     cmd_loop                ; Nope, keep going
  917.  
  918. cmd_end:        mov     bptr es:[di], 0dh       ; Put CR at end of cmd line
  919.                 mov     bptr cs:cmd_len, cl     ; Store command-line length
  920.  
  921. ; *****************************************************************************
  922. ; Set up the default FCBs at 5Ch and 6Ch in the PSP
  923. ;  Code provided by David E. Jenkins
  924.                 push    ds                      ; Save caller's DS
  925.  
  926.                 mov     ax, cs                  ; Point DS to our
  927.                 mov     ds, ax                  ;  variables
  928.  
  929.                 assume  ds:CSEG                ; Tell MASM that DS points to our variables
  930. ;
  931. ;   Locate the first two command line arguments
  932. ;
  933.                 push    ds                      ; Copy ds into es
  934.                 pop     es                      ;  "   "   "   "
  935.                 mov     di, offset CSEG:cmd_line   ; Point to command line in CS
  936.                 mov     al, bptr cmd_len            ; load the command line length
  937.                 xor     ah, ah
  938.                 inc     ax                      ; Include the CR in the length
  939.                 mov     wptr c_l_length, ax     ; Save the command line length
  940.                 add     ax, di                  ; Point to end of command line
  941.                 mov     wptr si_5C, ax          ; default to just after command line
  942.                 mov     wptr si_6C, ax          ;    "    "   "     "      "     "
  943.                 cmp     bptr cmd_len, 0         ; Is there anything to parse?
  944.                 jz      args_located            ; if not then args have been located
  945.  
  946.                 mov     cx, wptr c_l_length     ; Load the command line length
  947.                 mov     al, ' '                 ; We must find the first non-blank
  948.                 repe    scasb                   ; Go until we find it or run out
  949.                 or      cx, cx                  ; Did we run out (CX = 0)?
  950.                 jz      args_located            ; Yes--then args have been located
  951.  
  952.                 dec     di                      ; Move back to the right one
  953.                 inc     cx                      ;  "    "   "   "    "    "
  954.                 mov     wptr si_5C, di          ; Save the location of arg 1
  955.                 repne   scasb                   ; Find the next space (between arg1,2)
  956.                 or      cx, cx                  ; Did we run out
  957.                 jz      args_located            ; If so then args have been located
  958.  
  959.                 dec     di                      ; Move back to the left one
  960.                 inc     cx                      ;  "    "   "   "    "   "
  961.                 repe    scasb                   ; Now find next non-blank (arg 2)
  962.                 or      cx, cx                  ; Did we run out
  963.                 jz      args_located            ; If so then args have been located
  964.  
  965.                 dec     di                      ; Move back to the right one
  966.                 inc     cx                      ;  "    "   "   "    "    "
  967.                 mov     wptr si_6C,di           ; Save location of arg 2
  968.  
  969. args_located:
  970. ; parse the first argument into the first FCB
  971.  
  972.                 mov     si, wptr si_5C                  ; Point to the first argument
  973.                 mov     di, offset CSEG:fcb5C_drive    ; Point to the unopened FCB
  974.                 mov     ah, 29h                 ; Parse file name function
  975.                 mov     al, 00h                 ; Do it like COMMAND.COM does it
  976.                 int     21h                     ; go for it
  977.  
  978. ; parse the second argument into the second FCB
  979.                 mov     si, wptr si_6C                  ; Point to the second argument
  980.                 mov     di, offset CSEG:fcb6C_drive    ; point to the unopened FCB
  981.                 mov     ah, 29h                 ; Parse file name function
  982.                 mov     al, 00h                 ; Do it like COMMAND.COM does it
  983.                 int     21h                     ; go for it
  984.  
  985.                 pop     ds                      ; Restore caller's DS
  986.  
  987. ; *****************************************************************************
  988. ; Get the file name from the command line, if this version needs it
  989. IFDEF USE_DISK
  990. get_file:
  991.  
  992. ; If multiple data segments, load DS:SI, else just load SI
  993.                 mov     si, save_file           ; Load 16-bit pointer
  994.  
  995.                 mov     di, offset CSEG:fname  ; ES:DI -> our storage area
  996.  
  997. resolve:        mov     ah, 60h                 ; DOS INTERNAL function to resolve file name to full path name
  998.                 int     21h                     ; Stores complete path at ES:DI--we need it after EXEC in case
  999.                                                 ;  current drive or directory have changed
  1000.                                                 ; Ignore file name error here--it
  1001.                                                 ;  will be caught in save_disk if need be
  1002.  
  1003. ENDIF           ; IFDEF disk
  1004. ; *****************************************************************************
  1005. ; We have the parameters--let's go
  1006. ; *****************************************************************************
  1007.  
  1008.                 mov     wptr cs:ret_code, 0     ; Initialize swap's return code
  1009.                 mov     cs:exec_ret, 0          ; Initialize exec's return code
  1010.  
  1011. save_stack:     mov     ax, ss
  1012.                 mov     wptr cs:old_ss, ax      ; Save current SS
  1013.                 mov     ax, sp
  1014.                 mov     wptr cs:old_sp, ax      ; Save current SP
  1015.  
  1016. our_stack:      mov     ax, cs                  ; Our stack is in our CS
  1017.                 cli                             ; Disable interrupts
  1018.                 mov     ss, ax
  1019.                 mov     sp, offset CSEG:new_sp ; Set new stack
  1020.                 sti                             ; Re-enable interrupts
  1021.  
  1022. save_regs:      push    es                      ; Save needed registers
  1023.                 push    ds                      ; This is the caller's DS!
  1024.                 push    bp
  1025.  
  1026.                 mov     ax, cs
  1027.                 mov     ds, ax                  ; Point DS to our data
  1028.  
  1029.                 assume  ds:CSEG                ; Tell MASM that DS points to our variables
  1030.  
  1031. save_info:      mov     ah, 51h                 ; DOS function 51h, get PSP
  1032.                 int     21h                     ; Call DOS
  1033.                 mov     ax, bx                  ; ax = PSP
  1034.                 mov     wptr my_psp, ax         ; Save in cs: addressable location
  1035.                 dec     ax                      ; PSP-1 = MCB for this mem block
  1036.                 mov     es, ax
  1037.                 mov     ax, es:[0001h]          ; Get PSP address--should be same!
  1038.                 cmp     ax, wptr my_psp         ; All kosher?
  1039.                 jz      psp_ok                  ; Yes
  1040.  
  1041. psp_error:      mov     wptr ret_code, 1        ; No, pass return code
  1042.                 jmp     short exit_swap         ; Exit
  1043.  
  1044. psp_ok:         call    near ptr calc_size      ; Calc size to keep, save
  1045.  
  1046. try_save:       call    near ptr save_program   ; Write program to disk
  1047.                 jnc     shrink_mem              ; Carry flag set on error
  1048.  
  1049. no_save:        mov     wptr ret_code, 2        ; Error--set return code
  1050.                 jmp     short exit_swap         ; Exit routine on error
  1051.  
  1052. shrink_mem:     mov     ah, 4Ah                 ; DOS 4Ah--modify memory allocation
  1053.                 mov     es, wptr my_psp         ; Point to PSP again
  1054.                 mov     bx, wptr new_size       ; new_size was figured in calc_size
  1055.                 int     21h                     ; Call DOS to shrink size
  1056.                 jc      no_shrink               ; Carry set = error
  1057.  
  1058. IFNDEF NOFRAG
  1059. ; If necessary, free all extra DOS memory blocks our program owns
  1060.  
  1061.                 mov     cx, wptr extra_count    ; CX = number of extra DOS blocks
  1062.                 jcxz    exec_prog               ; If zero, don't bother
  1063.                 mov     di, offset dos_blocks   ; DI -> array of addresses/sizes
  1064.  
  1065.                 push    es
  1066.  
  1067. free_extra_loop:
  1068.                 mov     ax, wptr [di].block_seg
  1069.                 mov     es, ax                  ; ES = DOS memory segment to free
  1070.                 mov     ah, 49h                 ; DOS function to free memory block
  1071.                 push    cx
  1072.                 push    di
  1073.                 int     21h
  1074.                 pop     di
  1075.                 pop     cx
  1076.                 jc      no_shrink               ; Unlikely error
  1077.                 add     di, size dos_block      ; Point to next entry
  1078.                 loop    free_extra_loop         ; Keep going through extra blocks
  1079.  
  1080.                 pop     es
  1081. ENDIF
  1082.  
  1083.                 jmp     short exec_prog
  1084.  
  1085. ; *****************************************************************************
  1086. ; Any routine called or data referred to after this point MUST be located
  1087. ;  in this source file BEFORE the variable new_mcb below!
  1088. ; *****************************************************************************
  1089.  
  1090. no_shrink:      mov     wptr ret_code, 1        ; Carry = couldn't shrink block
  1091.                 jmp     short exit_swap         ; Should delete file here!
  1092.  
  1093. exec_prog:      call    do_exec                 ; This code is resident, and can
  1094.                                                 ;  be found above the resident line
  1095.  
  1096. ; do_exec execute the routine AND restores the program!
  1097.  
  1098. exit_swap:      pop     bp                      ; Restore saved registers
  1099.                 pop     ds                      ; This is the caller's DS!
  1100.                 pop     es
  1101.  
  1102.                 assume  ds:nothing              ; Tell MASM DS doesn't point to our variables
  1103.  
  1104. prev_stack:     mov     ax, wptr cs:old_ss      ; Restore original stack
  1105.                 cli
  1106.                 mov     ss, ax
  1107.                 mov     sp, wptr cs:old_sp
  1108.                 sti
  1109.  
  1110. ; Giving user exec's return code.  It could be a 16- or 32-bit pointer
  1111.                 mov     si, wptr return         ; Load 16-bit pointer
  1112.                 
  1113.                 mov     al, bptr cs:exec_ret    ; Store exec's return code
  1114.                 mov     bptr [si], al           ;  at address specified by caller
  1115.  
  1116.                 pop     ds
  1117.                 pop     es
  1118.                 pop     di
  1119.                 pop     si
  1120.                 pop    bp
  1121.                 mov     ax, wptr cs:ret_code    ; Give return code
  1122.                 ret
  1123. _swap           endp
  1124.  
  1125. ; *****************************************************************************
  1126. ; *****************************************************************************
  1127. ; calc_size     Calculates the total size (in paragraphs) of all DOS blocks
  1128. ;               owned by this program plus the amount of the initial program
  1129. ;               allocation block we can swap out.
  1130. ;
  1131. ; Entry:        DS points to our variables
  1132. ;               ES points to DOS Memory Control Block for our program
  1133. ;
  1134. ; Return:       old_size, start_seg, new_size, total_paras, extra_count initialized
  1135. ; *****************************************************************************
  1136. calc_size       proc    near                    ; Called only from inside our segment
  1137.  
  1138.                 push    es
  1139.  
  1140.                 assume  ds:CSEG                ; Tell MASM that DS points to our variables
  1141.  
  1142.                 mov     ax, es:[0003h]          ; Get # paragraphs allocated
  1143.                                                 ;  in this memory block
  1144.                 mov     wptr old_size, ax       ; Save old size of program
  1145.                 mov     bx, cs                  ; BX = segment of our code
  1146.                 mov     ax, offset CSEG:new_mcb; Last address to keep
  1147.                 mov     cl, 4                   ; new_mcb is para aligned
  1148.                 shr     ax, cl                  ; AX = ofs new_mcb / 16
  1149.                 inc     ax
  1150.                 add     bx, ax
  1151.                 mov     wptr start_seg, bx      ; Segment of released memory
  1152.                 sub     bx, wptr my_psp         ; BX = size to keep in paragraphs
  1153.                 mov     wptr new_size, bx       ; Save new, smaller size
  1154.                 mov     ax, wptr old_size
  1155.                 sub     ax, bx
  1156.                 mov     wptr prog_size, ax      ; ax = size of program block to swap out
  1157.                 mov     wptr total_paras, ax    ; ax = total paragraphs
  1158.  
  1159. IFNDEF NOFRAG
  1160. ; Now loop through all subsequent MCBs looking for blocks that we own (if
  1161. ;  the MCB's "owner" (PSP) matches us (our PSP).  Right now ES points to
  1162. ;  our MCB.  The MCB has three fields of interest:
  1163. ;
  1164. ;   Offset  Size    Description
  1165. ;   -------------------------------------------------------------------------
  1166. ;   0000h   Byte    Chain flag: 'M' (4Dh) if not last, 'Z' (5Ah) if last block in chain
  1167. ;   0001h   Word    PSP segment of owner, 0000h if free memory
  1168. ;   0003h   Word    Size of memory block in paragraphs, NOT including this MCB!
  1169.  
  1170. find_extras:    mov     wptr extra_count, 0     ; Initialize count
  1171.                 mov     bx, wptr my_psp         ; Use bx to hold PSP for easy comparisons
  1172.                 mov     di, offset dos_blocks   ; di = pointer to storage area
  1173.  
  1174. check_next_mcb: cmp     bptr es:[0000h], 'Z'    ; Is this the last block?
  1175.                 jz      calc_size_ret           ; Yup
  1176.  
  1177. next_mcb2:      mov     ax, es                  ; ax = this MCB
  1178.                 mov     cx, wptr es:[0003h]     ; cx = size of this mcb
  1179.                 add     ax, cx
  1180.                 inc     ax                      ; ax = addres of next MCB
  1181.                 mov     es, ax                  ; ES -> next MCB
  1182.  
  1183. my_block:       cmp     wptr es:[0001h], bx     ; Does it match my PSP?
  1184.                 jnz     check_next_mcb          ; Nope, move along
  1185.  
  1186. is_my_block:    inc     wptr extra_count        ; One more extra block
  1187.                 cmp     wptr extra_count, MAX_EXTRA
  1188.                 ja      calc_size_ret           ; Too many blocks--just exit
  1189.  
  1190. is_my_block2:   inc     ax                      ; Was MCB, now is address of segment
  1191.                 mov     wptr [di].block_seg, ax ; Store segment address
  1192.                 mov     cx, wptr es:[0003h]     ; Get size in paragraphs
  1193.                 mov     wptr [di].block_size, cx; Store size
  1194.                 add     wptr total_paras, cx    ; Increment total
  1195.                 add     di, size dos_block      ; Next index (move pointer)
  1196.                 jmp     short check_next_mcb
  1197. ENDIF
  1198.  
  1199. calc_size_ret:  pop     es
  1200.                 ret
  1201.  
  1202. calc_size       endp
  1203. ; *****************************************************************************
  1204.  
  1205. ; *****************************************************************************
  1206. ; xms_installed     Checks to see if XMS driver (himem.sys) is loaded
  1207. ;
  1208. ; Entry:            No assumptions--can be called by user
  1209. ; Return:           1 if XMS driver is load, 0 if not
  1210. ; *****************************************************************************
  1211. IFDEF USE_XMS
  1212.                 public  _xms_installed
  1213. _xms_installed   proc                            ; Called by user also!
  1214.  
  1215.                 push    ds                  ; Save all "important" registers
  1216.                 push    si
  1217.                 push    es
  1218.                 push    di
  1219.  
  1220.                 mov     ax, 4300h           ; Multiplex code for XMS driver, load check function
  1221.                 int     2Fh                 ; Call multiplex interrupt
  1222.                 cmp     al, 80h             ; al = 80h means XMS driver IS loaded
  1223.                 jnz     no_xms              ; Nope, not there
  1224.  
  1225. yes_xms:        mov     ax, 4310h               ; Get address of entry point
  1226.                 int     2Fh                     ; Returns address in ES:BX
  1227.                 mov     wptr cs:XMS_proc, bx
  1228.                 mov     wptr cs:XMS_proc + 2, es
  1229.                 mov     ax, 1                   ; Return 1, XMS installed
  1230.                 jmp     short xms_ret
  1231.  
  1232. no_xms:         xor     ax, ax              ; Return 0, XMS not installed
  1233.  
  1234. xms_ret:        pop     di
  1235.                 pop     es
  1236.                 pop     si
  1237.                 pop     ds
  1238.                 ret
  1239.  
  1240. _xms_installed   endp
  1241. ENDIF
  1242. ; *****************************************************************************
  1243.  
  1244. ; *****************************************************************************
  1245. ; ems4_installed    Checks to see if EMS 4.0 or above driver is loaded
  1246. ;
  1247. ; Entry:            No assumptions--can be called by user
  1248. ; Return:           1 if EMS 4.0 driver is load, 0 if not
  1249. ; *****************************************************************************
  1250. IFDEF USE_EMS
  1251.                 public  _ems4_installed
  1252. _ems4_installed  proc                            ; Called by user also!
  1253.  
  1254.                 push    ds                      ; Save "important" registers
  1255.                 push    si
  1256.                 push    es
  1257.                 push    di
  1258.  
  1259.  
  1260. get_emm_vector: mov     ah, GET_VECTOR          ; Get EMM interrupt vector
  1261.                 mov     al, 67h                 ; EMM accessed through Int 67h
  1262.                 int     21h                     ; Call DOS to get vector
  1263.                 mov     di, 0ah                 ; vector + di = name
  1264.                 mov     ax, cs
  1265.                 mov     ds, ax                  ; DS:SI -> EMM device driver name
  1266.                 mov     si, offset CSEG:emm_name   ; Compare with EMM device name
  1267.                 mov     cx, EMM_NAME_LEN
  1268.                 cld
  1269.                 repe    cmpsb                   ; Compare bytes
  1270.                 jnz     ems_no                  ; Same?  If not, EMS installed
  1271.  
  1272. ems_yes:        mov     ah, 46h                 ; Get EMM version number
  1273.                 int     67h                     ; Returns BCD in al
  1274.                 cmp     al, 40h                 ; Look only at high 4 bits
  1275.                 jb      ems_no                  ; Version not high enough--return 0
  1276.  
  1277. ems4_yes:       mov     ax, 1                   ; EMS installed, return 1
  1278.                 jmp     short ems_ret
  1279.  
  1280. ems_no:         xor     ax, ax                  ; EMS not installed, return 0
  1281.  
  1282. ems_ret:        pop     di
  1283.                 pop     es
  1284.                 pop     si
  1285.                 pop     ds
  1286.                 ret
  1287.  
  1288. _ems4_installed  endp
  1289. ENDIF
  1290. ; *****************************************************************************
  1291.  
  1292.  
  1293. ; *****************************************************************************
  1294. ; save_program      Try to save in XMS/EMS/disk.
  1295. ;
  1296. ; Entry:            DS points to our variables
  1297. ;
  1298. ; Returns:          Success:  carry flag clear
  1299. ;                   Failure:  carry flag set
  1300. ; *****************************************************************************
  1301. save_program    proc    near            ; Called only from inside our segment
  1302.  
  1303.                 push    si              ; Save registers
  1304.                 push    di
  1305.                 push    ds
  1306.                 push    es
  1307.  
  1308. ; Now figure out which routines to call, based on command-line definitions
  1309. ; To change the order in which swap() attempts to swap, change the order
  1310. ;  of these three conditional blocks.
  1311. IF1
  1312.    %out swap() will attempt to save the program in the following order:
  1313. ENDIF
  1314.    
  1315.  
  1316. ; *****************************************************************************
  1317. IFDEF USE_XMS
  1318. IF1
  1319.    %out -- XMS extended memory
  1320. ENDIF
  1321.                 call    save_xms        ; Try saving to XMS extended memory
  1322.                 jnc     save_ok         ; Carry clear == success, all done
  1323. ENDIF
  1324. ; *****************************************************************************
  1325.  
  1326.  
  1327. ; *****************************************************************************
  1328. IFDEF USE_EMS
  1329. IF1
  1330.    %out -- EMS expanded memory
  1331. ENDIF
  1332.                 call    save_ems        ; Try saving to EMS expanded memory
  1333.                 jnc     save_ok       ; Carry clear == success, all done
  1334. ENDIF
  1335. ; *****************************************************************************
  1336.  
  1337.  
  1338. ; *****************************************************************************
  1339. IFDEF USE_DISK
  1340. IF1
  1341.    %out -- DOS disk file
  1342. ENDIF
  1343.                 call    save_disk       ; Try saving to DOS disk file
  1344.                 jnc     save_ok         ; Carry clear == success, all done
  1345. ENDIF
  1346. ; *****************************************************************************
  1347.  
  1348. save_er:        stc                     ; Couldn't save anywhere, return error
  1349.                 jmp     short save_ret
  1350.  
  1351. save_ok:        clc                     ; Saved successfully, return OK
  1352.  
  1353. save_ret:       pop     es              ; Restore registers
  1354.                 pop     ds
  1355.                 pop     di
  1356.                 pop     si
  1357.  
  1358.                 ret
  1359. save_program    endp
  1360. ; *****************************************************************************
  1361.  
  1362.  
  1363. ; *****************************************************************************
  1364. ; Version-dependent code--only assemble the routine to save the program
  1365. ; to each place if it was requested on the command line
  1366. ; *****************************************************************************
  1367.  
  1368.  
  1369. ; *****************************************************************************
  1370. ; save_xms      Attempts to save program to XMS extended memory
  1371. ;
  1372. ; Entry:        DS points to our variables
  1373. ;
  1374. ; Return:       Carry set on error, carry clear on success
  1375. ;               If successful, updates restore_proc with the address of
  1376. ;               the XMS restore routine
  1377. ; *****************************************************************************
  1378. IFDEF USE_XMS
  1379. save_xms        proc    near
  1380.  
  1381.                 assume  ds:CSEG                ; Tell MASM DS points to our variables
  1382.  
  1383.                 call    _xms_installed           ; Check if XMS installed
  1384.                 or      ax, ax                  ; Returns 0 if not installed
  1385.                 jnz     xms_inst                ; AX != 0, XMS installed
  1386.                 jmp     short save_xms_er       ; AX == 0, XMS not installed
  1387.  
  1388. xms_inst:       mov     dx, wptr total_paras    ; dx = total # of paragraphs to write
  1389.                 mov     cl, 6                   ; Convert Paragraphs to kilobytes
  1390.                 shr     dx, cl                  ; dx = dx / 64
  1391.                 inc     dx                      ; dx = kilobytes needed (plus 1 for safety)
  1392.  
  1393. xms_alloc:      mov     ah, 09h                 ; XMS function 09, allocate extended memory block
  1394.                 call    dptr XMS_proc           ; Call XMS entry point directly
  1395.                 cmp     ax, 1                   ; AX = 1 on success
  1396.                 jnz     save_xms_er             ; Allocation unsuccessful, error
  1397.  
  1398. xms_alloc_ok:   mov     wptr handle, dx         ; Save returned handle in DX
  1399.  
  1400. ; First, attempt to save the portion of the program block
  1401. xms_prog_save:  mov     ax, wptr start_seg      ; Released segment address
  1402.                 mov     es, ax
  1403.                 mov     ax, wptr prog_size      ; Size (in paragraphs) of program block to save
  1404.                 xor     bx, bx
  1405.                 mov     wptr XMS_to_addr, bx    ; Initialize XMS destination
  1406.                 mov     wptr XMS_to_addr + 2, bx;  address (offset into extended memory block)
  1407.  
  1408.                 call    save_xms_seg            ; Attempt to save the program block
  1409.                 jc      write_error             ; Carry set = failure, return
  1410.  
  1411. IFNDEF NOFRAG
  1412. ; Next, save the extra DOS segments
  1413. xms_extra_save: mov     cx, wptr extra_count    ; Number of extra blocks to save
  1414.                 jcxz    save_xms_ok             ; If CX = 0, we exit routine
  1415.  
  1416.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  1417.  
  1418. xms_extra_save_loop:
  1419.                 mov     ax, wptr [di].block_seg
  1420.                 mov     es, ax                  ; ES = segment to save
  1421.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  1422.                 push    cx
  1423.                 push    di
  1424.                 call    save_xms_seg            ; Attempt to save this block
  1425.                 pop     di
  1426.                 pop     cx
  1427.                 jc      write_error             ; Carry flag set == error
  1428.                 add     di, size dos_block
  1429.                 loop    xms_extra_save_loop     ; Keep going through all blocks
  1430.  
  1431. ENDIF
  1432.                 jmp     short save_xms_ok
  1433.  
  1434. write_error:    mov     dx, wptr handle             ; Free allocated handle
  1435.                 mov     ah, 0Ah
  1436.                 call    dptr XMS_proc               ; Falls through to failure code
  1437.  
  1438. save_xms_er:    stc
  1439.                 jmp     short save_xms_ret
  1440.  
  1441. save_xms_ok:    mov     wptr restore_proc, offset CSEG:restore_xms     ; Initialize pointer
  1442.                 clc                                                     ;  to restore routine
  1443.  
  1444. save_xms_ret:   ret
  1445. save_xms        endp
  1446.  
  1447.  
  1448. ; *****************************************************************************
  1449. ; save_xms_seg  Attempts to save a chunk of RAM to XMS memory
  1450. ;
  1451. ; Entry:        ES points to the segment to save
  1452. ;               AX contains its length (in paragraphs)
  1453. ;               handle holds the XMS handle to write to
  1454. ;               XMS_to_addr contains offset into extended memory for write
  1455. ;
  1456. ; Return:       Carry set on error, carry clear on success
  1457. ;               Updates XMS_to_addr for next write
  1458. ; *****************************************************************************
  1459. save_xms_seg    proc    near
  1460.                 push    ds
  1461.                 push    es
  1462.  
  1463. ; Call the XMS copy memory function to do this; fill in the XMS request block
  1464. xms_write_size: mov     bx, 10h                     ; AX = # of paragraphs
  1465.                 mul     bx                          ; DX:AX = AX * 10h, convert paragraphs to bytes
  1466.                 mov     wptr XMS_size, ax           ; Store # of bytes to write
  1467.                 mov     wptr XMS_size + 2, dx
  1468.  
  1469. xms_write_from: xor     bx, bx
  1470.                 mov     wptr XMS_from, bx           ; 0 means from conventional memory
  1471.                 mov     wptr XMS_from_addr, bx      ; Offset of source address is 0
  1472.                 mov     ax, es                      ; Segment of source address is ES
  1473.                 mov     wptr XMS_from_addr + 2, ax
  1474.  
  1475. xms_write_to:   mov     ax, wptr handle             ; Destination XMS handle
  1476.                 mov     wptr XMS_to, ax             ;  XMS_to_addr already filled in
  1477.  
  1478. do_xms_write:   mov     si, offset CSEG:XMS_struc  ; DS:SI -> XMS request structure
  1479.                 mov     ah, 0Bh                     ; Function B, copy memory
  1480.                 call    dptr XMS_proc               ; Do the memory copy move
  1481.                 cmp     ax, 1                       ; AX = 1 means success
  1482.                 jnz     save_xms_seg_er             ; Success, all done!
  1483.  
  1484. save_xms_seg_ok:mov     ax, wptr XMS_size           ; Retrieve length
  1485.                 mov     dx, wptr XMS_size + 2       ;  (32 bits)
  1486.                 add     wptr XMS_to_addr, ax        ; Add two 32-bit values
  1487.                 adc     wptr XMS_to_addr + 2, dx    ; Update XMS write pointer
  1488.                 clc                                 ; Signal success
  1489.                 jmp     short save_xms_seg_ret
  1490.  
  1491. save_xms_seg_er:stc
  1492.  
  1493. save_xms_seg_ret:
  1494.                 pop     es
  1495.                 pop     ds
  1496.                 ret
  1497. save_xms_seg    endp
  1498.  
  1499. ENDIF
  1500. ; *****************************************************************************
  1501.  
  1502.  
  1503. ; *****************************************************************************
  1504. ; save_ems      Attempts to save program to EMS 4.0 expanded memory
  1505. ;
  1506. ; Entry:        DS points to our variables
  1507. ;
  1508. ; Return:       Carry set on error, carry clear on success
  1509. ;               If successful, updates restore_proc with the address of
  1510. ;               the EMS restore routine
  1511. ; *****************************************************************************
  1512. IFDEF USE_EMS
  1513. save_ems        proc    near
  1514.  
  1515.                 assume  ds:CSEG                ; Tell MASM DS points to our variables
  1516.  
  1517.                 call    _ems4_installed          ; Check if EMS 4.0 installed
  1518.                 or      ax, ax                  ; AX = 0 if not installed
  1519.                 jnz     ems_inst                ; AX != 0, ems installed
  1520.                 jmp     short save_ems_er       ; AX = 0, no EMS, error!
  1521.  
  1522. ems_inst:       mov     bx, wptr total_paras    ; Total # of paragraphs we need
  1523.                 mov     cl, 10                  ; Convert Paragraphs to 16K pages
  1524.                 shr     bx, cl
  1525.                 inc     bx                      ; BX = pages needed
  1526.                 mov     bptr pages_used, bl     ; Save for later use
  1527.  
  1528.                 mov     ah, 43h                 ; EMM function 43h, allocate
  1529.                 int     67h
  1530.                 or      ah, ah                  ; OK return code?
  1531.                 jz      ems_alloc_ok            ; Yes, skip ahead
  1532.                 jmp     short save_ems_er       ; No, not enough EMS
  1533.  
  1534. ems_alloc_ok:   mov     wptr handle, dx         ; Returned handle in DX
  1535.  
  1536. ; First, attempt to save the portion of the program block
  1537. ems_prog_save:  mov     ax, wptr start_seg      ; Released segment address
  1538.                 mov     es, ax
  1539.                 mov     ax, wptr prog_size      ; Size (in paragraphs) of program block to save
  1540.  
  1541.                 xor     bx, bx
  1542.                 mov     wptr ems_offset, bx     ; Maintain absolute byte offset
  1543.                 mov     wptr ems_offset + 2, bx ;  pointer into handle
  1544.  
  1545.                 call    save_ems_seg            ; Attempt to save the program block
  1546.  
  1547.                 jc      save_ems_fail           ; Carry set = failure, return
  1548.  
  1549. IFNDEF NOFRAG
  1550. ; Next, save the extra DOS segments
  1551. ems_extra_save: mov     cx, wptr extra_count    ; Number of extra blocks to save
  1552.                 jcxz    save_ems_ok             ; If CX = 0, we exit routine
  1553.  
  1554.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  1555.  
  1556. ems_extra_save_loop:
  1557.                 mov     ax, wptr [di].block_seg
  1558.                 mov     es, ax                  ; ES = segment to save
  1559.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  1560.                 push    cx
  1561.                 push    di
  1562.                 call    save_ems_seg            ; Attempt to save this block
  1563.                 pop     di
  1564.                 pop     cx
  1565.                 jc      save_ems_fail           ; Carry flag set == error
  1566.                 add     di, size dos_block
  1567.                 loop    ems_extra_save_loop     ; Keep going through all blocks
  1568. ENDIF
  1569.                 jmp     short save_ems_ok
  1570.  
  1571. save_ems_fail:  mov     dx, wptr handle         ; Failure--free handle
  1572.                 mov     ah, 45h
  1573.                 int     67h                     ; Falls through to failure code
  1574.  
  1575. save_ems_ok:    mov     wptr restore_proc, offset CSEG:restore_ems     ; Initialize pointer
  1576.                 clc                                                     ;  to restore routine
  1577.                 jmp     short save_ems_ret
  1578.  
  1579. save_ems_er:    stc
  1580.  
  1581. save_ems_ret:   ret
  1582. save_ems        endp
  1583.  
  1584. ; *****************************************************************************
  1585. ; save_ems_seg  Attempts to save a chunk of RAM to EMS memory
  1586. ;
  1587. ; Entry:        ES points to the segment to save
  1588. ;               AX contains its length (in paragraphs)
  1589. ;               handle holds the EMS handle to write to
  1590. ;               ems_offset holds the 32-bit absolute offset in expanded
  1591. ;                memory to write this block to
  1592. ;
  1593. ; Return:       Carry set on error, carry clear on success
  1594. ;               Updates ems_offset with proper offset for next write
  1595. ; *****************************************************************************
  1596. save_ems_seg    proc    near
  1597.                 push    ds
  1598.                 push    es
  1599.  
  1600.                 assume  ds:CSEG                ; Tell MASM DS points to our variables
  1601.  
  1602. ; Call the EMS copy memory function to do this; fill in the eMS request block
  1603. ems_write_size: mov     bx, 10h                     ; AX = # of paragraphs
  1604.                 mul     bx                          ; DX:AX = AX * 10h, convert paragraphs to bytes
  1605.                 mov     wptr EMS_size, ax           ; Store # of bytes to write
  1606.                 mov     wptr EMS_size + 2, dx
  1607.  
  1608. ems_write_from: xor     bx, bx
  1609.                 mov     bptr EMS_from, bl           ; Copying from conventional memory (0)
  1610.                 mov     wptr EMS_from_h, bx         ; Source handle is 0 (conventional memory)
  1611.                 mov     wptr EMS_from_o, bx         ; Source offset is 0
  1612.                 mov     ax, es                      ; Segment of source address is ES
  1613.                 mov     wptr EMS_from_s, ax
  1614.  
  1615. ems_write_to:   mov     bptr EMS_to, 1              ; Copying to expanded memory
  1616.                 mov     ax, wptr handle
  1617.                 mov     wptr EMS_to_h, ax           ; Specify EMS handle
  1618.  
  1619.                 ; 32-bit absolute offset for copy is in ems_offset
  1620.                 ;  convert to EMS page:offset (16K pages) values
  1621.                 mov     ax, wptr ems_offset         ; Load 32-byte offset
  1622.                 mov     dx, wptr ems_offset + 2
  1623.                 mov     bx, ax                      ; Save a copy of ax (low 16 bits)
  1624.                 and     ax, 0011111111111111b       ; Get (ax & (16K - 1)), this is the offset (14 bits)
  1625.                 mov     wptr EMS_to_o, ax           ; Save page offset
  1626.                 mov     cl, 14
  1627.                 shr     bx, cl                      ; Move low 2 bits of page into low 2 bits of bx
  1628.                 mov     cl, 2
  1629.                 shl     dx, cl                      ; Move hi ? bits of page into dx shl 2
  1630.                 or      dx, bx                      ; DX = page number (combine two values)
  1631.                 mov     wptr EMS_to_s, dx           ; Save
  1632.  
  1633.                 mov     ax, wptr EMS_size           ; Retrieve size of copy
  1634.                 mov     dx, wptr EMS_size + 2
  1635.                 add     wptr ems_offset, ax         ; Update EMS copy pointer
  1636.                 adc     wptr ems_offset + 2, dx     ;  for next EMS write
  1637.  
  1638. do_ems_write:   mov     si, offset CSEG:EMS_struc  ; DS:SI -> EMS request structure
  1639.                 mov     ax, 5700h                   ; Function 57 (copy/exchange memory), sub 0, copy memory
  1640.                 int     67h                         ; Call EMS manager
  1641.                 or      ah, ah                      ; AH = 0 means success
  1642.                 jnz     save_ems_seg_er             ; Not 0 means error
  1643.  
  1644. save_ems_seg_ok:clc                                 ; Signal success
  1645.                 jmp     short save_ems_seg_ret
  1646.  
  1647. save_ems_seg_er:stc
  1648.  
  1649. save_ems_seg_ret:
  1650.                 pop     es
  1651.                 pop     ds
  1652.                 ret
  1653. save_ems_seg    endp
  1654. ENDIF
  1655. ; *****************************************************************************
  1656.  
  1657.  
  1658. ; *****************************************************************************
  1659. ; save_disk     Attempts to save program to DOS disk file
  1660. ;
  1661. ; Entry:        DS points to our variables
  1662. ;
  1663. ; Return:       Carry set on error, carry clear on success
  1664. ;               If successful, updates restore_proc with the address of
  1665. ;               the disk restore routine
  1666. ; *****************************************************************************
  1667. IFDEF USE_DISK
  1668. save_disk       proc    near
  1669.                 push    es
  1670.  
  1671.                 assume  ds:CSEG                ; Tell MASM DS points to our variables
  1672.  
  1673. creat_file:     mov     dx, offset CSEG:fname  ; DS:DX -> file name
  1674.                 mov     ah, 3Ch                 ; Create/truncate file
  1675.                 mov     cx, 02h                 ; Create a hidden file
  1676.                 int     21h                     ; Call DOS
  1677.                 jc      save_disk_er            ; Carry set, couldn't create file
  1678.  
  1679. creat_ok:       mov     wptr handle, ax         ; Save handle returned by DOS
  1680.  
  1681. ; First, attempt to save the portion of the program block
  1682. disk_prog_save: mov     ax, wptr start_seg      ; Released segment address
  1683.                 mov     es, ax
  1684.                 mov     ax, wptr prog_size      ; Size (in paragraphs) of program block
  1685.                 call    save_disk_seg           ; Attempt to save the program block
  1686.                 jc      disk_write_er           ; Carry flag set == error
  1687.  
  1688. IFNDEF NOFRAG
  1689. ; Next, save the extra DOS segments
  1690. disk_extra_save:
  1691.                 mov     cx, wptr extra_count    ; Number of extra blocks to save
  1692.                 jcxz    save_disk_ok            ; If CX = 0, we exit routine
  1693.  
  1694.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  1695.  
  1696. disk_extra_save_loop:
  1697.                 mov     ax, wptr [di].block_seg
  1698.                 mov     es, ax                  ; ES = segment to save
  1699.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  1700.                 push    cx
  1701.                 push    di
  1702.                 call    save_disk_seg           ; Attempt to save this block
  1703.                 pop     di
  1704.                 pop     cx
  1705.                 jc      disk_write_er           ; Carry flag set == error
  1706.                 add     di, size dos_block
  1707.                 loop    disk_extra_save_loop    ; Keep going through all blocks
  1708.  
  1709. ENDIF
  1710.                 jmp     short save_disk_ok
  1711.  
  1712.  
  1713. disk_write_er:  mov     ah, 3Eh                 ; Close file first
  1714.                 mov     bx, wptr handle
  1715.                 int     21h
  1716.                 stc
  1717.                 jmp     short save_disk_ret
  1718.  
  1719.  
  1720. save_disk_ok:   mov     ah, 3Eh                 ; 3eh = close file
  1721.                 mov     bx, wptr handle
  1722.                 int     21h
  1723.                 mov     wptr restore_proc, offset CSEG:restore_disk    ; Initialize pointer
  1724.                 clc                                                     ;  to restore routine
  1725.                 jmp     short save_disk_ret
  1726.  
  1727. save_disk_er:   stc
  1728.  
  1729. save_disk_ret:  pop     es
  1730.                 ret
  1731. save_disk       endp
  1732.  
  1733.  
  1734. ; *****************************************************************************
  1735. ; save_disk_seg Attempts to save a chunk of RAM to DOS disk file
  1736. ;
  1737. ; Entry:        ES points to the segment to save
  1738. ;               AX contains its length (in paragraphs)
  1739. ;               handle holds the file handle to write to
  1740. ;
  1741. ;
  1742. ; Return:       Carry set on error, carry clear on success
  1743. ; *****************************************************************************
  1744. save_disk_seg   proc    near
  1745.                 push    ds
  1746.                 push    es
  1747.                 push    di
  1748.  
  1749.                 assume  ds:CSEG
  1750.  
  1751.                 mov     wptr paras_left, ax     ; Used to count paras written
  1752.                 mov     bx, es
  1753.                 mov     ds, bx                  ; DS -> segment to write
  1754.  
  1755.                 assume  ds:nothing
  1756.  
  1757. disk_write_32k: cmp     ax, 0800h               ; paras_left less than 32K?
  1758.                 jb      finish_disk_write       ; Yes, exit
  1759.                 sub     wptr cs:paras_left, 800h; We will write 32K bytes now
  1760.  
  1761.                 mov     ah, 40h                 ; DOS function to write to file
  1762.                 mov     bx, wptr cs:handle      ; BX = file handle to write to
  1763.                 mov     cx, 8000h               ; Write 32K bytes
  1764.                 xor     dx, dx                  ; DS:DX is buffer to write
  1765.                 int     21h                     ; Write data to file
  1766.                 jc      save_disk_seg_er        ; This write failed--escape
  1767.  
  1768. disk_write_ok:  mov     ax, ds                  ; Move write pointer in memory
  1769.                 add     ax, 800h                ; We just wrote 1K paragraphs
  1770.                 mov     ds, ax
  1771.                 mov     ax, wptr cs:paras_left  ; AX checked above
  1772.                 jmp     short disk_write_32k    ; Loop on next 32K
  1773.  
  1774. finish_disk_write:
  1775.                 mov     cl, 4                   ; AX = # paragraphs left to write
  1776.                 shl     ax, cl                  ; Paragraphs to bytes
  1777.                 mov     cx, ax
  1778.                 mov     ah, 40h                 ; 40h = write to file
  1779.                 mov     bx, wptr cs:handle      ; BX = file handle to write to
  1780.                 xor     dx, dx                  ; DS:DX = buffer
  1781.                 int     21h                     ; Call DOS
  1782.                 jc      save_disk_seg_er        ; Carry set, error (close file first)
  1783.  
  1784. save_disk_seg_ok:
  1785.  
  1786.                 clc
  1787.                 jmp     short save_disk_seg_ret
  1788.  
  1789. save_disk_seg_er:
  1790.                 stc
  1791.  
  1792. save_disk_seg_ret:
  1793.                 pop     di
  1794.                 pop     es
  1795.                 pop     ds
  1796.  
  1797.                 ret
  1798. save_disk_seg   endp
  1799.  
  1800.  
  1801.  
  1802. ENDIF
  1803. ; *****************************************************************************
  1804.  
  1805.  CSEG ENDS
  1806. END
  1807.