home *** CD-ROM | disk | FTP | other *** search
/ Columbia Kermit / kermit.zip / archives / packetdrivers.tar.gz / pd.tar / src / winpkt.asm < prev    next >
Assembly Source File  |  1995-06-25  |  16KB  |  623 lines

  1. version    equ    2
  2. ; File WINPKT.ASM                        8 Dec 1991
  3. ; Provides a Packet Driver interface between Windows 3 Enhanced mode 
  4. ; applications and a real Packet Driver. This attempts to solve the problem
  5. ; of Windows moving applications around in memory willy nilly. Install WINPKT
  6. ; after the Packet Driver and before starting Windows. 
  7. ; Command line is:
  8. ;    WINPKT  WINPKT_interrupt number PD_interrupt_number
  9. ; with both in the range of 60h to 7fh.
  10. ; Build with the Clarkson Packet Driver Collection subprograms:
  11. ;     masm WINPKT;
  12. ;     link WINPKT;
  13. ;     exe2bin WINPKT.EXE WINPKT.COM
  14. ;     del WINPKT.EXE
  15. ;
  16.     include defs.asm
  17.  
  18. ;    Copyright, 1988-1992, Russell Nelson
  19. ;    Copyright, 1991, Roger F. James
  20. ;    Code revised slightly, formatting cleaned up enormously, added
  21. ;    documentation, Joe R. Doupnik, jrd@cc.usu.edu, Utah State Univ,
  22. ;    8 Dec 1991.
  23.  
  24. ;   This program is free software; you can redistribute it and/or modify
  25. ;   it under the terms of the GNU General Public License as published by
  26. ;   the Free Software Foundation, version 1.
  27. ;
  28. ;   This program is distributed in the hope that it will be useful,
  29. ;   but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. ;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  31. ;   GNU General Public License for more details.
  32. ;
  33. ;   You should have received a copy of the GNU General Public License
  34. ;   along with this program; if not, write to the Free Software
  35. ;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  36.  
  37. code    segment word public
  38.     assume  cs:code, ds:code, es:nothing
  39.  
  40.     org 2ch
  41. phd_environ dw  0
  42.     org 80h
  43. phd_dioa    label   byte
  44.     org 100h
  45. start:    jmp start_1
  46.     even
  47.  
  48. ;Debugging stuff
  49. ;total_pkts    dw  0
  50. ;gvm_pkts    dw  0
  51. ;bvm_pkts    dw  0
  52.  
  53. per_handle  struc
  54. in_use        db  0        ; non-zero if this handle is in use
  55. their_handle    dw  0        ; lower layer handle
  56. recv_handler    dd  0        ; receiver upcall
  57. vm_id        dw  0        ; VM_ID for this handler
  58. per_handle  ends
  59.  
  60. handles    per_handle MAX_HANDLE dup(<>)
  61. end_handles label   byte
  62.  
  63. is_186        db    0        ;=0 if 808[68], =1 if 80[123]86.
  64. is_286        db    0        ;=0 if 80[1]8[68], =1 if 80[234]86.
  65. is_386        db    0        ;=0 if 80[12]8[68], =1 if 80[34]86.
  66.  
  67. regs    struc               ; stack offsets of incoming regs
  68.     _ES dw  ?
  69.     _DS dw  ?
  70.     _BP dw  ?
  71.     _DI dw  ?
  72.     _SI dw  ?
  73.     _DX dw  ?
  74.     _CX dw  ?
  75.     _BX dw  ?
  76.     _AX dw  ?
  77.     _IP dw  ?
  78.     _CS dw  ?
  79.     _F  dw  ?           ; flags, Carry flag is bit 0
  80. regs    ends
  81.  
  82. CY    equ 0001h
  83. EI    equ 0200h
  84.  
  85.  
  86. bytes   struc            ; stack offsets of incoming regs
  87.         dw  ?            ; es, ds, bp, di, si are 16 bits
  88.         dw  ?
  89.         dw  ?
  90.         dw  ?
  91.         dw  ?
  92.     _DL db  ?
  93.     _DH db  ?
  94.     _CL db  ?
  95.     _CH db  ?
  96.     _BL db  ?
  97.     _BH db  ?
  98.     _AL db  ?
  99.     _AH db  ?
  100. bytes   ends
  101.  
  102. old_isr        dd  0        ; old pkt driver int
  103. their_2f_isr    dd  0        ; original int 2f ISR
  104.  
  105. ; The following are globals that assume that the code that access them
  106. ; single threads.
  107. MAX_BUFFER_LEN  equ 1520
  108. our_buffer  db  MAX_BUFFER_LEN dup (0)
  109. buffer_flag db  0
  110. buffer_len  dw  0
  111. their_handler   dd  0        ; receiver handler to call
  112. their_bx    dw  0
  113. ;
  114. ;
  115.  
  116. vmm_running db  0        ; 386 virtual machine manager is running
  117. our_handle  dw  0        ; current top level handle
  118.  
  119.     include    movemem.asm
  120.  
  121. our_isr:
  122.     jmp our_isr_0        ; the required signature.
  123.     db  'PKT DRVR',0
  124.     db  'WINPKT',0
  125.  
  126. our_isr_0:            ; check if it one of the calls we
  127.     assume  ds:nothing    ; want to intercept (passes addresses)
  128.     cmp    ah,2        ; f_access_type?
  129.     je    our_isr_2    ; e = yes
  130.     cmp    ah,3        ; f_release_type?
  131.     je    our_isr_2
  132.     cmp    ah,5        ; f_terminate
  133.     je    our_isr_2
  134.     cmp    ah,8        ; f_stop
  135.     je    our_isr_2
  136. our_isr_1:            ; nothing needing intercepion
  137.     jmp    old_isr         ; chain to the original Packet Driver
  138.  
  139. our_isr_2:
  140.     push    ax        ; We are interested in this one
  141.     push    bx        ; so save some registers
  142.     push    cx
  143.     push    dx
  144.     push    si
  145.     push    di
  146.     push    bp
  147.     push    ds
  148.     push    es
  149.     cld
  150.     mov    bx,cs        ; set up DS
  151.     mov    ds,bx
  152.     assume    ds:code
  153.  
  154.     mov    bp,sp        ; use bp to access the original regs
  155.     and    _F[bp],not CY   ; start by clearing the carry flag
  156.  
  157.     cmp    ah,2            ; f_access_type?
  158.     jne    our_isr_3    ; ne = no (it's special here)
  159.     jmp    f_access_type    
  160. our_isr_3:
  161.     cmp    ah,3        ; f_release_type?
  162.     jne    our_isr_4    ; ne = no
  163.     jmp    f_release_type
  164. our_isr_4:
  165.     cmp    ah,5        ; f_terminate?
  166.     jne    our_isr_5    ; ne = no
  167.     jmp    f_terminate
  168. our_isr_5:
  169.     jmp    f_stop        ; must be f_stop
  170.  
  171. our_isr_error:
  172.     mov    _DH[bp],dh    ; error code
  173.     or    _F[bp],CY       ; return their carry flag
  174. our_isr_return:
  175.     pop    es
  176.     pop    ds
  177.     pop    bp
  178.     pop    di
  179.     pop    si
  180.     pop    dx
  181.     pop    cx
  182.     pop    bx
  183.     pop    ax
  184.     iret
  185.  
  186. ; Windows (and many many other programs) use the Int 2Fh Multiplexor link,
  187. ; and each is supposed to grab it's own calls as seen in AX, or chain them
  188. ; to the previous Int 2Fh owner. Windows 3 uses function 16h for most work.
  189. ; This is our Int 2Fh routine.
  190. our_2f_isr:
  191.     assume    ds:nothing, es:nothing
  192.     cmp    ax,1608h    ; Windows Enhanced "Init completed" broadcast?
  193.     jne    our_2f_1    ; ne = not that function, chain it
  194.     mov    vmm_running,1   ; remember that Windows has started
  195.     jmp    their_2f_isr    ; pass it on down the chain
  196. our_2f_1:
  197.     cmp    ax,1609h    ; Windows Enhanced "Begin Exit" broadcast?
  198.     jne    our_2f_2    ; ne = no
  199.     mov    vmm_running,0   ; remember that Windows has stopped
  200. our_2f_2:
  201.     jmp    their_2f_isr    ; propagate broadcasts down the Int 2Fh chain
  202.  
  203. our_handler:            ; Receive upcalls from the Packet Driver
  204.     or    ax,ax        ; first (AX=0) call to get buffer?
  205.     jnz    our_handler_2    ; nz = no, must be second upcall
  206.     cmp    buffer_flag,1    ; must buffer, is it free?
  207.     je    our_handler_1   ; e = no, busy
  208.     cmp    cx,MAX_BUFFER_LEN ; pkt larger than our buffer?
  209.     ja    our_handler_1   ; a = yes, too large
  210.     mov    buffer_flag,1   ; mark buffer as busy now
  211.     mov    buffer_len,cx   ; store pkt length
  212.     mov    ax,cs
  213.     mov    es,ax        ; segment of our buffer
  214.     mov    di,offset our_buffer ; tell PD es:di is the buffer address
  215.     retf
  216.  
  217. our_handler_1:
  218.     xor    ax,ax        ; reject the packet by returning es:di = NULL
  219.     mov    es,ax
  220.     xor    di,di
  221.     retf
  222.  
  223. our_handler_2:            ; second upcall, packet transfer complete
  224.     mov    their_bx,bx    ; save their registers
  225.     mov    dx,bx
  226.     mov    bx,cs        ; set our data segment
  227.     mov    ds,bx
  228.     assume    ds:code
  229.  
  230.     mov    bx,offset handles    ; array of PD handles
  231. our_handler_3:
  232.     cmp    [bx].their_handle,dx    ; dx is upcoming handle
  233.     je    our_handler_4        ; found their handle
  234.     add    bx,(size per_handle)    ; next handle
  235.     cmp    bx,offset end_handles   ; examined all handles?
  236.     jb    our_handler_3        ; b = no, continue.
  237.                             ; get here if no matching handle
  238.     mov    buffer_flag,0        ; mark the buffer free
  239.     retf
  240.  
  241. our_handler_4:                      ; found the handle
  242. ;    inc    total_pkts
  243.     mov    ax,[bx].recv_handler.segm ; appliation's call address
  244.     mov    their_handler.segm,ax
  245.     mov    ax,[bx].recv_handler.offs
  246.     mov    their_handler.offs,ax
  247.     push    bx
  248.     mov    ax,1683h        ; Windows, get current vir mach ident
  249.     int    2fh
  250.     mov    ax,bx        ; ident returned in bx
  251.     pop    bx
  252.     cmp    ax,[bx].vm_id    ; same as the one the app is using?
  253.     je    our_handler_5    ; e = yes, correct VM is running
  254.     jmp    our_handler_7   ; no, another VM is running, switch to wanted
  255. our_handler_5:
  256. ;    inc    gvm_pkts
  257.     call    pass_to_app    ; copy buffer to application, via double call
  258.     retf
  259.  
  260. pass_to_app:
  261.     xor    ax,ax        ; set up register for first upcall to app
  262.     mov    bx,their_bx    ; handle from Packet Driver
  263.     mov    cx,buffer_len    ; packet size
  264.     push    ds
  265.     call    their_handler    ; do first upcall (request buffer address)
  266.     pop    ds
  267.     mov    ax,es        ; check for 0:0 as reject value
  268.     or    ax,ax
  269.     jnz    pass_to_app_1    ; nz = have an address
  270.     or    di,di        ; check for 0:offset (rather unlikely)
  271.     jnz    pass_to_app_1    ; nz = have an offset
  272.     mov    buffer_flag,0   ; packet is being declined, free our buffer
  273.     ret
  274. pass_to_app_1:            ; copy from our buffer to app's es:di
  275.     push    di
  276.     mov    cx,buffer_len
  277.     mov    si,offset our_buffer
  278.     cld
  279.     call    movemem        ; copy frame into apps buffer
  280.     mov    ax,1        ; set up regs for second upcall
  281.     mov    bx,their_bx    ; handle
  282.     mov    cx,buffer_len    ; report packet length too
  283.     pop    si
  284.     mov    dx,es
  285.     push    ds
  286.     mov    ds,dx
  287.     assume    ds:nothing
  288.  
  289.     call    their_handler    ; call the application
  290.     pop    ds
  291.     assume    ds:code
  292.     mov    buffer_flag,0    ; free buffer
  293.     ret
  294.  
  295. ; Windows Enhanced, request virtual machine in bx, and call back at es:di 
  296. ; when it's ready (which, knowing Windows, may take quite a while, hence 
  297. ; the requirement to buffer the packet to clear the lan board and ints).
  298. our_handler_7:
  299. ;    inc    bvm_pkts
  300.     mov    ax,1685h    ; request switch VMs and callback
  301.     mov    bx,[bx].vm_id    ; virtual machine of the app
  302.     xor    cx,cx        ; flags (bits 0 and 1), zero is don't wait
  303.     mov    dx,40h        ; dx:si is priority boost
  304.     xor    si,si
  305.     movseg    es,cs
  306.     mov    di,offset our_callback
  307.     int    2fh
  308.     retf
  309.  
  310.     assume ds:nothing
  311. our_callback:            ; get here with correct virtual machine
  312.     push    ax        ; call application twice (a la PD) to deliver
  313.     push    bx        ; the buffered packet
  314.     push    cx
  315.     push    dx
  316.     push    si
  317.     push    di
  318.     push    ds
  319.     push    es
  320.     push    bp
  321.     mov    ax,cs
  322.     mov    ds,ax        ; set up our ds
  323.     assume    ds:code
  324.  
  325.     call    pass_to_app
  326.     pop    bp
  327.     pop    es
  328.     pop    ds
  329.     assume  ds:nothing
  330.     pop    di
  331.     pop    si
  332.     pop    dx
  333.     pop    cx
  334.     pop    bx
  335.     pop    ax
  336.     iret
  337.  
  338.     assume  ds:code
  339. f_access_type:                ; register for a packet Type
  340.     mov    bx,offset handles    ; array of PD handles
  341. access_type_1:
  342.     cmp    [bx].in_use,0        ; is this handle in use?
  343.     je    access_type_2        ; e = yes, found a free one
  344.     add    bx,(size per_handle)    ; next handle
  345.     cmp    bx,offset end_handles   ; examined all handles?
  346.     jb    access_type_1        ; b = no, continue
  347.     jmp    access_type_space    ; no handle found, return error
  348. access_type_2:
  349.     mov    our_handle,bx        ; save our handle
  350.     mov    [bx].in_use,1        ; make handle as in-use
  351.     cmp    vmm_running,0        ; is Windows Enhanced mode running?
  352.     je    access_type_3        ; e = no, don't bother to redirect
  353.     mov    [bx].recv_handler.segm,es
  354.     mov    [bx].recv_handler.offs,di
  355.     mov    bx,cs
  356.     mov    es,bx
  357.     mov    di,offset our_handler    
  358. access_type_3:
  359.     push    ds
  360.     mov    bx,_DS[bp]
  361.     mov    ds,bx
  362.     assume  ds:nothing
  363.  
  364.     mov    bx,_BX[bp]        ; restore callers registers
  365.     pushf
  366.     call    old_isr            ; call Packet Driver
  367.     pop    ds
  368.     assume    ds:code
  369.     mov    bx,our_handle
  370.     jnc    access_type_4        ; nc = success
  371.     mov    [bx].in_use,0        ; failed, free our handle
  372.     jmp    our_isr_error
  373. access_type_4:
  374.     mov    [bx].their_handle,ax    ; handle returned in ax
  375.     mov    _AX[bp],ax        ; save return handle
  376.     cmp    vmm_running,0        ; Windows Enhanced mode running?
  377.     je    access_type_5        ; e = no
  378.     push    bx            ; Windows "Get Current Virtual Mach"
  379.     mov    ax,1683h
  380.     int    2fh            ; get current VM_ID to bx
  381.     mov    ax,bx
  382.     pop    bx
  383.     mov    [bx].vm_id,ax        ; save ident as part of our handle
  384. access_type_5:
  385.     jmp    our_isr_return
  386.  
  387. access_type_space:
  388.     mov    dh,NO_SPACE
  389.     jmp    our_isr_error
  390.  
  391. f_release_type:
  392.     mov    bx,_BX[bp]        ; restore callers registers
  393.     pushf
  394.     call    old_isr
  395.     jnc    release_type_1        ; nc = success
  396.     jmp    our_isr_error
  397. release_type_1:
  398.     mov    ax,_BX[bp]        ;just in case
  399.     mov    bx,offset handles
  400. release_type_2:
  401.     cmp    [bx].their_handle,ax    ; compare handles
  402.     je    release_type_3        ; e = found a match
  403.     add    bx,(size per_handle)    ; next handle
  404.     cmp    bx,offset end_handles   ; examined all handles?
  405.     jb    release_type_2        ; b = no, continue
  406.     jmp    err_bad_handle        ; no handle found, return error
  407. release_type_3:
  408.     mov    [bx].in_use,0        ; say handle is no longer in use
  409.     jmp    our_isr_return
  410.  
  411. err_bad_handle:
  412.     mov    dh,BAD_HANDLE        ; dh is error code
  413.     jmp    our_isr_error
  414.  
  415. f_terminate:
  416.     mov    bx,_BX[bp]          ; restore callers registers
  417.     pushf
  418.     call    old_isr
  419.     jnc    terminate_1         ; nc = success
  420.     jmp    our_isr_error
  421. terminate_1:
  422.     mov    ax,_BX[bp]          ; just in case
  423.     mov    bx,offset handles
  424. terminate_2:
  425.     cmp    [bx].their_handle,ax    ; compare handles
  426.     je    terminate_3         ; e = found a match
  427.     add    bx,(size per_handle)    ; next handle
  428.     cmp    bx,offset end_handles   ; examined all handles?
  429.     jb    terminate_2         ; b = no, continue
  430.     jmp    err_bad_handle          ; no match, return error
  431. terminate_3:
  432.     mov    [bx].in_use,0        ; mark handle as free
  433.     mov    bx,offset handles    ; check that all handles are free
  434. terminate_4:
  435.     cmp    [bx].in_use,0        ; is this handle free?
  436.     jne    terminate_5        ; ne = no, so can't exit completely
  437.     add    bx,(size per_handle)    ; next handle
  438.     cmp    bx,offset end_handles   ; examined all handles?
  439.     jb    terminate_4        ; b = no, continue examination
  440.  
  441.     mov    ah,35h            ; got owner of Int 2Fh
  442.     mov    al,2fh
  443.     int    21h            ; to see if it's still us
  444.     mov    ax,es
  445.     mov    cx,cs
  446.     cmp    ax,cx            ; our segment?
  447.     jne    terminate_5        ; ne = no, can't terminate
  448.     cmp    bx,offset our_2f_isr    ; our offset?
  449.     jne    terminate_5        ; ne = no, can't terminate
  450.     mov    al,2fh            ; restore Int 2f
  451.     mov    ah,25h
  452.     push    ds
  453.     lds    dx,their_2f_isr        ; previous owner now is current owner
  454.     int    21h
  455.     pop    ds
  456.  
  457.     movseg    es,cs
  458.     mov    ah,49h            ; free our memory
  459.     int    21h
  460.     jmp    our_isr_return
  461. terminate_5:
  462.     mov    dh,CANT_TERMINATE    ; error code
  463.     jmp    our_isr_error
  464.  
  465. ; Stop the packet driver doing upcalls. Also a following terminate will
  466. ; always succed (no in use handles any longer).
  467. f_stop:
  468.     mov    bx,_BX[bp]        ; restore caller's registers
  469.     pushf
  470.     call    old_isr
  471.     mov    bx,offset handles
  472. f_stop_2:
  473.     mov    [bx].in_use,0        ; say handle is free
  474.     add    bx,(size per_handle)    ; next handle
  475.     cmp    bx,offset end_handles    ; done all?
  476.     jb    f_stop_2        ; b = not yet
  477.     clc
  478.     ret
  479.  
  480. end_resident    label byte
  481.     include    printnum.asm
  482.     include decout.asm
  483.     include digout.asm
  484.     include chrout.asm
  485.  
  486. usage_msg   label   byte
  487.     db    ' Usage: WINPKT <packet_int_number>',CR,LF
  488.     db    ' Install WINPKT after the regular Packet Driver, but'
  489.     db    CR,LF,' before starting Windows 3.$'
  490.  
  491. copyright_msg   label   byte
  492.     db    "Virtual packet driver for Windows 3.x, version ",'0'+(majver / 10),'0'+(majver mod 10),".",'0'+version,CR,LF
  493.     db    "Portions Copyright 1991 Roger F. James",CR,LF,'$'
  494.  
  495. copyleft_msg    label   byte
  496.     db "Packet driver skeleton copyright 1988-90, Russell Nelson.",CR,LF
  497.     db "This program is free software; see the file COPYING for details."
  498.     db    CR,LF
  499.     db    "NO WARRANTY; see the file COPYING for details.",CR,LF
  500.     db    CR,LF
  501. crlf_msg db    CR,LF,'$'
  502.  
  503. entry_point   db  0,0,0,0
  504.  
  505. not_found_msg   db  CR,LF,'There is no packet driver at $'
  506. new_int_msg    db  CR,LF,'Virtual packet driver installed on interrupt $'
  507.  
  508. not_found_error:
  509.     mov    dx,offset not_found_msg
  510.     mov    di,offset entry_point
  511.     call    print_number
  512.     mov    ax,4c05h        ; exit to DOS with errorlevel = 5
  513.     int    21h
  514.  
  515. usage_error:
  516.     mov    dx,offset usage_msg
  517. error:    mov    ah,9
  518.     int    21h
  519.     mov    ax,4c0ah            ; give errorlevel 10
  520.     int    21h
  521.  
  522. start_1:cld
  523.     mov    dx,offset copyright_msg
  524.     mov    ah,9
  525.     int    21h
  526.     mov    dx,offset copyleft_msg
  527.     mov    ah,9
  528.     int    21h
  529.     mov    si,offset phd_dioa+1
  530.     call    skip_blanks         ; end of line?
  531.     cmp    al,CR
  532.     je    usage_error        ; e = yes
  533.  
  534. chk_options:
  535.     call    skip_blanks
  536.     cmp    al,'-'          ; any options?
  537.     jne    no_more_opt
  538. usage_error_j_1:
  539.     jmp usage_error
  540. no_more_opt:
  541.     mov    di,offset entry_point
  542.     call    get_number
  543.     call    skip_blanks
  544.     cmp    al,CR
  545.     jne    usage_error
  546.  
  547.     mov    al,entry_point
  548.     call    verify_packet_int
  549.     jnc    packet_int_ok        ; nc = success
  550.     jmp    error
  551. packet_int_ok:
  552.     jne    not_found_error        ; error if no Packet Driver
  553.  
  554. ;Determine the processor type.  The 8088 and 8086 will actually shift ax
  555. ;over by 33 bits, while the 80[123]86 use a shift count mod 32.
  556.     mov    cl,33
  557.     mov    ax,0ffffh
  558.     shl    ax,cl            ;186 or better?
  559.     jz    processor_identified    ;no.
  560.     mov    is_186,1
  561.  
  562.     push    sp
  563.     pop    ax
  564.     cmp    ax,sp            ;286 or better?
  565.     jne    processor_identified    ;no.
  566.     mov    is_286,1
  567.  
  568.     pushf
  569.     pop    ax
  570.     or    ax,7000h        ;the 386 lets us set these bits
  571.     push    ax
  572.     popf                ;this should be a real popf.
  573.     pushf
  574.     pop    ax
  575.     test    ax,7000h        ;did the bits get set?
  576.     je    processor_identified
  577.     mov    is_386,1
  578.  
  579. processor_identified:
  580.  
  581.     mov    ah,35h              ; get Packet Driver interrupt routine
  582.     mov    al,entry_point
  583.     int    21h
  584.     mov    old_isr.offs,bx        ; save here
  585.     mov    old_isr.segm,es
  586.     mov    ah,25h            ; install our packet interrupt
  587.     mov    dx,offset our_isr
  588.     int    21h
  589.  
  590.     mov    ah,35h            ; get Int 2Fh interrupt routine
  591.     mov    al,2fh
  592.     int    21h
  593.     mov    their_2f_isr.offs,bx    ; save here
  594.     mov    their_2f_isr.segm,es
  595.     mov    ah,25h          ;install our 2f interrupt
  596.     mov    dx,offset our_2f_isr
  597.     int    21h
  598.  
  599.     mov    ah,49h            ; free our environment, because
  600.     mov    es,phd_environ        ; we won't need it
  601.     int    21h
  602.     mov    bx,1                ; get the stdout handle
  603.     mov    ah,3eh            ; close it in case they redirected it
  604.     int    21h
  605.     mov    dx,offset end_resident
  606.     add    dx,0fh            ; round up to next highest paragraph
  607.     mov    cl,4
  608.     shr    dx,cl
  609.     mov    ah,31h            ; terminate, stay resident
  610.     xor    al,al
  611.     int    21h
  612.  
  613.     include verifypi.asm
  614.     include getnum.asm
  615.     include getdig.asm
  616.     include skipblk.asm
  617.     include printea.asm
  618.     include    crlf.asm
  619.  
  620. code    ends
  621.  
  622.     end    start
  623.