home *** CD-ROM | disk | FTP | other *** search
/ CP/M / CPM_CDROM.iso / lambda / soundpot / p / sap50.lbr / SAP50.AZM < prev    next >
Encoding:
Text File  |  1993-10-25  |  26.3 KB  |  1,020 lines

  1. ; SAP v50 -- Sort & Pack Directory,  8/7/86
  2. ;
  3. ;;    .Z80        ; Needed for M80, ignore error otherwise
  4. ;;    ASEG        ; Needed for M80, ignore error otherwise
  5. ;
  6. ;=======================================================================
  7. ;
  8. ; User-customizable options:
  9. ;
  10.  
  11. NO    EQU    0
  12. YES    EQU    .NOT.NO
  13.  
  14. MAXDRIVE EQU    'B'        ; Set to maximum drive in system, in UPPER CASE!
  15.  
  16. ONLY22    EQU    YES        ; Set this to YES if SAP will be running ONLY under
  17.                 ; CP/M v2.2.  If not sure, set to NO.
  18.  
  19. ONLY14    EQU    NO        ; Set this to YES if SAP will be running ONLY under
  20.                 ; CP/M v1.4.  If not sure, set to NO.
  21.  
  22. CPMONLY    EQU    YES        ; Set this to YES if SAP will be running ONLY under
  23.                 ; CP/M, either v1.4 or 2.2   If possibility exists
  24.                 ; That SAP might be executed under MP/M or CP/M 3.0,
  25.                 ; Set to NO.
  26.  
  27. ASCENDING EQU    YES        ; Set this to NO for a descending sort of directory
  28.                 ; Entries.
  29.  
  30.  
  31. DELZRO    EQU    YES        ; Set this to YES to cause SAP to delete all
  32.                 ; Zero-length files if SAVDASH is NO, or to delete
  33.                 ; All zero-length files not beginning with "-" if
  34.                 ; SAVDASH is YES.
  35.  
  36. SAVDASH    EQU    YES        ; Set this to YES and DELZRO to YES to cause SAP to
  37.                 ; Delete all zero-length files not beginning with
  38.                 ; "-" (as in SAP40).  Set this to NO to not check
  39.                 ; Filenames before deleting zero-length files (if
  40.                 ; DELZRO is set YES).
  41. ;
  42. ; End of user-selected options
  43. ;
  44. ;=======================================================================
  45. ;
  46. ; Various system equates
  47. ;
  48.  
  49. UNSPECI    EQU    .NOT.(ONLY22.OR.ONLY14)
  50.  
  51. BOOT    EQU    0000H        ; BDOS warm boot vector.
  52. BDOS    EQU    0005H        ; BDOS entry point vector.
  53. FCB    EQU    005CH        ; Default CP/M file control block.
  54. CMDBUF    EQU    0080H        ; CP/M command line buffer.
  55.  
  56. PRINT    EQU    9        ; BDOS print string function.
  57. VERNO    EQU    12        ; BDOS CP/M version number function.
  58. SELDRV    EQU    14        ; BDOS select drive function.
  59. GETDSK    EQU    25        ; BDOS "get disk #" function.
  60.  
  61. ENTRYLEN EQU    32        ; Length of directory entry on disk in bytes.
  62.  
  63. CR    EQU    0DH
  64. LF    EQU    0AH
  65. EOS    EQU    '$'        ; BDOS fnc #9, end of string marker.
  66.  
  67. ;
  68. ;=======================================================================
  69. ;
  70. ; Start of code.
  71. ;
  72. ; The code has been rearranged to make the flow linear.  Thus we no longer have
  73. ; a mainline with several calls to subroutines.  This optimizes execution time
  74. ; and code space at the expense of complexity.    C'est la vie!
  75. ;
  76.  
  77.     ORG    100H
  78.  
  79.                 ; Obtain certain BIOS vectors.
  80.     LD    SP,STACK    ; Use our own stack.
  81.     LD    C,PRINT
  82.     LD    DE,IDENTITY    ; Tell user who we are.
  83.     CALL    BDOS
  84.     LD    HL,(0001H)    ; Addr of warm boot BIOS vector
  85.     LD    BC,24        ; Offset to SELDSK
  86.     ADD    HL,BC        ; Source
  87.     LD    DE,SELDSK    ; Destination
  88.     LD    BC,24        ; Get all BIOS vectors from SELDSK to the end.
  89.     LDIR
  90.  
  91.      IF    UNSPECI
  92.     LD    C,VERNO        ; Returns H=0 for CP/M, H=1 for MP/M,
  93.     CALL    BDOS        ; And L=0 for pre-2.0, else L = 2x or 3x.
  94.      ENDIF
  95.  
  96.      IF    UNSPECI.AND.(.NOT.CPMONLY)
  97.     LD    DE,BADOPSYS    ; Tell user we can't work with MP/M or CP/M 3.0
  98.                 ; This is only printed if we detect a problem.
  99.     DEC    H        ; H=1 for MPM
  100.     JP    NZ,PRLAST    ; If MP/M then print error and die.
  101.     CP    30H
  102.     JP    NC,PRLAST    ; Exit if CP/M 3.0 or higher, we can't use it.
  103.      ENDIF            ; UNSPECI AND (NOT CPMONLY)
  104.  
  105.      IF    UNSPECI
  106.     LD    (VERFLG),A    ; Store the version num.
  107.      ENDIF
  108.  
  109. ;
  110. ;=======================================================================
  111. ;
  112. ; Select drive, load disk parm block, and calculate free mem.
  113. ;
  114.  
  115. SETUP:    LD    A,(FCB)        ; (FCB) = (0=default, 1=A, 2=B, 3=C, etc.)
  116.     DEC    A        ; SELDSK needs 0=A, 1=B, 2=C, etc.
  117.     JP    P,SETUP1    ; Skip BDOS call if drive mentioned,
  118.     LD    C,GETDSK    ; Otherwise get current default drive.
  119.     CALL    BDOS        ; Returns current drive code in A.
  120.  
  121. BIGDRIVE EQU    (MAXDRIVE-'A')+1
  122.  
  123. SETUP1:    CP    BIGDRIVE    ; Verify that specified drive is not too big.
  124.     JP    C,OKDRIVE
  125.     LD    DE,BADDRIVE    ; Else tell user it is an improper drive spec.
  126.     JP    PRLAST        ; Print message and die.
  127.  
  128. OKDRIVE:
  129.     LD    C,A        ; SELDSK needs drive code in reg C.
  130.     CALL    SELDSK        ; Returns addr of DPH in HL for selected drive.
  131.  
  132.      IF    UNSPECI
  133.     LD    A,(VERFLG)
  134.     OR    A
  135.     JP    NZ,CPM22    ; If ver 2.2 then jump to CPM22,
  136.      ENDIF            ; Else must be 1.4
  137.  
  138. ;
  139. ;-----------------------------------------------------------------------
  140. ;
  141. ; CP/M 1.4 routine
  142. ;
  143.  
  144.      IF    ONLY14.OR.UNSPECI
  145. CPM14:    LD    HL,(BDOS+1)
  146.     LD    L,0
  147.     LD    A,(JP)        ; This bizarre inst loads the op code for a JP!
  148.     LD    (RECTRN),A
  149.     EX    DE,HL        ; Store ptr in DE.
  150.     LD    HL,15        ; RECTRAN offset from BDOS in CP/M 1.4
  151.     ADD    HL,DE
  152.     LD    (RECTRN+1),HL
  153.     EX    DE,HL        ; Restore ptr to HL.
  154.     LD    DE,3AH        ; Offset from BDOS to 1.4 DPB
  155.     ADD    HL,DE
  156. ;
  157. ;    Note that the previous LD DE,003AH zeroed out the D registor for us.
  158. ;
  159. ;    LD    D,0        ; Note that D=0 for rest of procedure.
  160. ;
  161.     LD    E,(HL)
  162.     LD    (SPT),DE
  163.     INC    HL
  164.     LD    E,(HL)
  165.     LD    (DRM),DE
  166.     LD    E,5        ; Offset to systrk field.  (since D=0)
  167.     ADD    HL,DE
  168.     LD    E,(HL)
  169.     LD    (SYSTRK),DE
  170.      ENDIF            ; ONLY14 OR UNSPECI
  171.  
  172.      IF    UNSPECI        ; If CPM22 not present then just fall through,
  173.     JP    CALCSPACE    ; Else we need to jump around CPM22.
  174.      ENDIF
  175.  
  176. ;
  177. ;-----------------------------------------------------------------------
  178. ;
  179. ; CP/M 2.2 routine
  180. ;
  181. ; Implementation Note:    BC is used here only for loading offsets < 256.
  182. ;            Thus after 1st 16 bit load, B=0 for rest of routine.
  183. ;
  184.  
  185.      IF    ONLY22.OR.UNSPECI
  186. CPM22:    LD    E,(HL)        ; On entry HL points to DPH.
  187.     INC    HL
  188.     LD    D,(HL)
  189.     LD    (RECTBL),DE    ; Save the XLTO addr.
  190.  
  191.     LD    BC,9        ; Offset to ptr to DPB (in DPH)  (Now B=0)
  192.     ADD    HL,BC
  193.     LD    E,(HL)
  194.     INC    HL
  195.     LD    D,(HL)
  196.     EX    DE,HL        ; Now HL = addr of DPB.
  197.  
  198.     LD    E,(HL)
  199.     INC    HL
  200.     LD    D,(HL)
  201.     LD    (SPT),DE
  202.  
  203.     LD    C,6        ; Offset to DRM (in DPB)  (since B=0)
  204.     ADD    HL,BC
  205.     LD    E,(HL)
  206.     INC    HL
  207.     LD    D,(HL)
  208.     LD    (DRM),DE
  209.  
  210.     LD    C,5        ; Offset to SYSTRK (in DPB)  (since B=0)
  211.     ADD    HL,BC
  212.     LD    E,(HL)
  213.     INC    HL
  214.     LD    D,(HL)
  215.     LD    (SYSTRK),DE    ; Now drop into CALCSPACE.
  216.      ENDIF            ; ONLY22 OR UNSPECI
  217.  
  218. ;
  219. ;=======================================================================
  220. ;
  221. ; Determine if directory will fit in available memory.
  222. ; If not, determine how much of it will.
  223. ;
  224.  
  225. CALCSPACE:
  226.                 ; First compute amount of free space in bytes.
  227.     LD    HL,(BDOS+1)    ; Get addr of 1st byte of BDOS.
  228.     LD    DE,BUF        ; Get addr of 1st byte of BUF.
  229. ;
  230. ;    OR    A        ; Clear carry.
  231. ;    SBC    HL,DE
  232. ;
  233. ; Since we know that BUF starts on an even page boundary, E=0, so just go:
  234. ;
  235.     LD    A,H
  236.     SUB    D
  237.     LD    H,A        ; Now HL = HL - DE,  (since E=0).
  238.                 ; Note that E=0 is used in next paragraph.
  239.     DEC    HL        ; This is for the extra 0E5H at the end of the
  240.                 ; Buf: needed for CLEAN to be bullet proof.
  241.  
  242.                 ; Compute HL = HL/128  by  HL = (HL * 2) / 256.
  243.     SLA    L        ; New carry was msb of L.
  244.     RL    H        ; Rotate left H  (moving carry into lsb).
  245.                 ; The new carry was msb of H.
  246.     LD    L,H        ; Effectively go HL = HL/256      (carry saved)
  247.     LD    H,E        ; Zero H reg  (since E=0 before)  (carry saved)
  248.     RL    H        ; Set H = carry.  Now HL = HL/128.
  249.     LD    (NUMDIRRECS),HL    ; Assume we need all space until proven wrong.
  250.  
  251.                 ; Now determine how many 128-byte records the directory takes up.
  252.     LD    DE,(DRM)    ; Get # 32-byte directory entries . . .
  253.     INC    DE        ; Relative to 1.
  254.     LD    (NUMFNAMES),DE    ; Save as a more convenient count for later.
  255.     SRL    D
  256.     RR    E        ; Divide by 2.
  257.     SRL    D
  258.     RR    E        ; Now  DE = # 128-byte records needed.
  259.                 ; Note HL = # 128-byte records available.
  260. ;
  261. ; Now see if it all fits in available memory
  262. ;
  263. ; Note that carry flag = 0 from the above RR E
  264. ; since # direc entries is always a multiple of 4.
  265. ; Thus I don't need to go:
  266. ;
  267. ;    OR    A        ; clear carry
  268. ;
  269.     SBC    HL,DE
  270.     JR    C,NOMEM        ; If need > space then process exception.
  271.     LD    (NUMDIRRECS),DE    ; Save needed recs as num to process.  This
  272.     JP    BYESETUP    ; Finally synchronizes NUMDIRRECS and NUMFNAMES.
  273.  
  274. NOMEM:    LD    HL,(NUMDIRRECS)    ; Recall available space (in records).
  275.     ADD    HL,HL
  276.     ADD    HL,HL        ; Now HL = available space (in direc entries).
  277.     LD    (NUMFNAMES),HL    ; Save new num dirc entries.  This finally
  278.     LD    C,PRINT        ; Synchronizes NUMDIRRECS and NUMFNAMES.
  279.     LD    DE,LACKMEMORY    ; Warn user we'll do only as much as we can.
  280.     CALL    BDOS        ; BDOS will return for me.
  281.  
  282. BYESETUP:
  283.  
  284. ;
  285. ;=======================================================================
  286. ;
  287.  
  288. GETDIR:    CALL    RDDIR        ; Read in the directory entries.
  289.  
  290. ;
  291. ;=======================================================================
  292. ;
  293. ; CLEAN out the erased file entries to all E5's.
  294. ;
  295. ; While we're at it, move all empty entries to the bottom of the buffer.
  296. ; This way we can avoid adding them to the sort's work.  For large underused
  297. ; directories (such as on my hard disk) this can speed up the sort
  298. ; substantially.
  299. ;
  300. ; The dummy entries before and after the buffer are generated to prevent this
  301. ; algorithm from leaving the buffer's boundaries.  When we are scanning backward
  302. ; with the right pointer looking for a non-empty entry we must guard against
  303. ; the chance that all entries are empty.  Similarly, when we scan forward with
  304. ; the left pointer looking for empty entries we must guard against the chance
  305. ; that all entries are non-empty.
  306. ;
  307.  
  308. CLEAN:                ; First we need to create a dummy non-empty record before BUF, and a
  309.                 ; Dummy empty record at end of buffer.
  310.     LD    A,0FFH
  311.     LD    (BUF-ENTRYLEN),A ; Mark a dummy entry as not deleted.
  312.  
  313.      IF    DELZRO
  314.     LD    (BUF-ENTRYLEN+15),A ; And as having >0 number of records.
  315.      ENDIF
  316.  
  317.     LD    HL,(NUMFNAMES)
  318.     ADD    HL,HL        ; Take advantage of ENTRYLEN = 32 = 2 ^ 5.
  319.     ADD    HL,HL
  320.     ADD    HL,HL
  321.     ADD    HL,HL
  322.     ADD    HL,HL        ; Compute HL = NUMFNAMES * 32.
  323.     LD    DE,BUF
  324.     ADD    HL,DE        ; Now HL = 1st entry after end of buffer.
  325.     LD    A,0E5H        ; Needed at the entry point.
  326.                 ; Note that if .NOT.DELZRO then this register
  327.                 ; Will be preserved throughout the routine!
  328.     LD    (HL),A        ; Mark as a dummy empty record.
  329.     LD    BC,-ENTRYLEN
  330.     ADD    HL,BC        ; Now HL = last entry = BUF + 32*(NUMFNAMES-1).
  331.                 ; DE = 1st entry = BUF.
  332.     JP    CLNENTRY    ; Jump to entry point within the big loop.
  333.  
  334. ;
  335. ;-----------------------------------------------------------------------
  336. ;
  337. ; Swap left and right entries.
  338. ;    DE = right = non-empty entry.
  339. ;    HL = left = empty entry.
  340. ;
  341.  
  342. CLSWAP:    PUSH    DE        ; Save right pointer.
  343.     EX    DE,HL
  344.     LD    BC,ENTRYLEN
  345.     LDIR            ; Left <== Right.  Now DE = left + 32.
  346.     POP    HL        ; Recall right pointer.
  347.                 ; To finish the swap drop into FILLE5.
  348. ;
  349. ;
  350. ;-----------------------------------------------------------------------
  351. ;
  352. ; Here we clear out the entry at (HL).
  353. ;
  354.  
  355. FILLE5:    LD    B,ENTRYLEN    ; Number of bytes to clear
  356.  
  357.      IF    DELZRO        ; The cond. code for DELZRO won't preserve A.
  358.     LD    A,0E5H        ; Load up char to replicate.
  359.      ENDIF
  360.  
  361. FILLOP:    LD    (HL),A
  362.     INC    HL
  363.     DJNZ    FILLOP
  364.  
  365. ;
  366. ; Now we need to continually back up the right pointer as long as the entries
  367. ; are empty.  I.e., we need to search for the 1st non-empty entry from the
  368. ; right.
  369. ;
  370.     LD    BC,-(ENTRYLEN*2) ; FILLE5 made HL = right + 32.
  371.     ADD    HL,BC        ; Now HL = right - 32.
  372.  
  373. CLNENTRY:
  374.  
  375.     CP    (HL)        ; Is the right pointer at an empty entry?
  376.     JP    Z,FILLE5    ; If so then clear it.
  377.  
  378. ;
  379. ; This is all conditional code to implement DELZRO and SAVDASH.
  380. ;
  381.  
  382.      IF    DELZRO
  383.     PUSH    HL        ; Save right pointer.
  384.      ENDIF
  385.  
  386.      IF    DELZRO.AND.(.NOT.ONLY14)
  387.     LD    BC,14
  388.     ADD    HL,BC
  389.     LD    A,(HL)        ; Get s2 byte (extended rc).
  390.     AND    0FH        ; For CP/M 2.2, 0 for CP/M 1.4
  391.     INC    HL
  392.     OR    (HL)        ; Check record count field.
  393.      ENDIF
  394.  
  395.      IF    DELZRO.AND.ONLY14
  396.     LD    BC,15
  397.     ADD    HL,BC
  398.     LD    A,(HL)        ; Check record count field.
  399.     OR    A
  400.      ENDIF
  401.  
  402.      IF    DELZRO
  403.     POP    HL        ; Recall right pointer.  (Flags unaffected)
  404.      ENDIF
  405.  
  406.      IF    DELZRO.AND.(.NOT.SAVDASH)
  407.     JR    Z,FILLE5    ; IF record count = 0 then delete it.
  408.      ENDIF
  409.  
  410.      IF    DELZRO.AND.SAVDASH
  411.     JP    NZ,NEXTLFT    ; If record count <> 0 then don't delete.
  412.     INC    HL
  413.     LD    A,(HL)        ; Get first character of filename.
  414.     DEC    HL        ; Now HL is start of entry.
  415.     CP    '-'        ; Don't delete files with '-' as 1st char.
  416.     JR    NZ,FILLE5
  417.      ENDIF
  418.  
  419. ;
  420. ;-----------------------------------------------------------------------
  421. ;
  422. ; Now we have HL = right = non-empty record.  So keep bumping the left pointer
  423. ; until we find an empty record to trade with.    Note that DE = next left, so
  424. ; check the current record at DE first.
  425. ;
  426.  
  427. NEXTLFT:
  428.  
  429.     EX    DE,HL        ; Now HL = left, and DE = right.
  430.  
  431.      IF    .NOT.DELZRO    ; If we don't need to delete 0 length files,
  432.     LD    BC,ENTRYLEN    ; Then pull this constant out of the loop.
  433.      ENDIF
  434.  
  435.     JP    LFTENTRY    ; Skip bumping the left pointer.
  436.  
  437. LFTLOOP:
  438.  
  439.      IF    DELZRO        ; The conditional code for DELZRO will not
  440.     LD    BC,ENTRYLEN    ; Preserve this register pair.
  441.      ENDIF
  442.  
  443.     ADD    HL,BC        ; Bump left pointer by an entry.
  444.  
  445. LFTENTRY:
  446.  
  447.      IF    DELZRO        ; The conditional code for DELZRO will not
  448.     LD    A,0E5H        ; Preserve this register.
  449.      ENDIF
  450.  
  451.     CP    (HL)        ; Is it marked for deletion?
  452.  
  453.      IF    .NOT.DELZRO
  454.     JP    NZ,LFTLOOP
  455.      ENDIF
  456. ;
  457. ; The following is all conditional code to handle DELZRO and SAVDASH.
  458. ;
  459.      IF    DELZRO
  460.     JR    Z,LFTFND    ; If marked for deletion then we're done.
  461.     PUSH    HL        ; Save left pointer.
  462.      ENDIF
  463.  
  464.      IF    DELZRO.AND.(.NOT.ONLY14)
  465.     LD    BC,14
  466.     ADD    HL,BC
  467.     LD    A,(HL)        ; Get s2 byte (extended rc).
  468.     AND    0FH        ; For CP/M 2.2, 0 for CP/M 1.4
  469.     INC    HL
  470.     OR    (HL)        ; Check record count field.
  471.      ENDIF
  472.  
  473.      IF    DELZRO.AND.ONLY14
  474.     LD    BC,15
  475.     ADD    HL,BC
  476.     LD    A,(HL)        ; Check record count field.
  477.     OR    A        ; Is A = 0 ?
  478.      ENDIF
  479.  
  480.      IF    DELZRO
  481.     POP    HL        ; Recall left pointer.    (Flags unaffected)
  482.     JP    NZ,LFTLOOP    ; If record count <> 0 then it's not empty.
  483.      ENDIF
  484.  
  485.      IF    DELZRO.AND.SAVDASH
  486.     INC    HL
  487.     LD    A,(HL)        ; Get first character of filename.
  488.     DEC    HL        ; Now HL is start of entry.
  489.     CP    '-'        ; Don't delete files with '-' as 1st char.
  490.     JR    Z,LFTLOOP    ; If '-' is 1st char then it's not empty to us.
  491.      ENDIF
  492.  
  493. ;
  494. ;-----------------------------------------------------------------------
  495. ;
  496. ; Now we have HL = left = an empty entry, and DE = right = a non-empty record.
  497. ; Check if right address < left address.  (Note that DE = HL is impossible.)
  498. ;
  499.  
  500. LFTFND:    LD    A,E        ; Do a CP DE,HL  (as if it existed).
  501.     SUB    L
  502.     LD    A,D
  503.     SBC    A,H
  504.     JP    NC,CLSWAP    ; If left < right then we're ok, do the swap.
  505.  
  506.                 ; Ok, now we need to compute the minimum NUM2DO and return.
  507.                 ; DE = last non-empty entry, HL = DE + 32 = 1st empty entry.
  508.     LD    DE,-BUF
  509.     ADD    HL,DE        ; Now HL = # bytes in buffer to sort.
  510.  
  511.                 ; Now compute HL = HL / 32 quickly.
  512.                 ; Note that HL is a multiple of 32.
  513.     OR    A        ; Clear carry.
  514.     LD    A,H        ; This will allow me to use RRA several times.
  515.     RRA
  516.     RR    L        ; By 2.
  517.     RRA
  518.     RR    L        ; By 4.
  519.     RRA
  520.     RR    L        ; By 8.
  521.     RRA
  522.     RR    L        ; By 16.
  523.     RRA
  524.     RR    L        ; By 32.
  525.     LD    H,A
  526.     LD    (NUM2DO),HL    ; Now we won't have to add the overhead of the
  527.                 ; Empty entries to the sort routine.
  528.  
  529. BYECLEAN:            ; This symbol is useful for debugging.
  530.  
  531. ;
  532. ;=======================================================================
  533. ;
  534. ; SORT the directory with a Shell-Metzner sort.
  535. ;
  536. ; Brief Synopsis:
  537. ;
  538. ;      A shell sort works by comparing entries that are far apart (by INXOFFSET)
  539. ; and successively shrinking the offset (by a factor of 2 at each iteration)
  540. ; until the sort degenerates into a simple version of an insert sort.  There is
  541. ; a notion of a "left" and a "right" pointer at all times.  In general, the
  542. ; current "left" pointer = LFTPTR, and the current "right" pointer = LFTPTR +
  543. ; PTROFFSET.  The "left" entry is analagous to the higher entry in the sorted
  544. ; list.  The index variables are used for counting and determining how many
  545. ; more entries need to be compared against at the current INXOFFSET value.
  546. ;
  547.  
  548. SORT:    XOR    A
  549.     LD    (NOSWAP),A    ; Zero the flag in case already sorted.
  550.     LD    C,PRINT
  551.     LD    DE,SORTING
  552.     CALL    BDOS
  553.     LD    HL,(NUM2DO)    ; Get # direc entries to process.
  554.     JP    SRTENTRY    ; Skip the next instruction and go.
  555.  
  556. ;
  557. ;-----------------------------------------------------------------------
  558. ;
  559. ; Now divide index offset size by 2.
  560. ;
  561.  
  562. DIVIDE:    LD    HL,(INXOFFSET)
  563.  
  564. SRTENTRY:
  565.  
  566.     SRL    H
  567.     RR    L        ; Now HL = HL / 2
  568.     LD    (INXOFFSET),HL
  569.     LD    A,L
  570.     OR    H
  571.     JP    Z,BYESORT    ; If INXOFFSET=0 then we're done.
  572.  
  573.     EX    DE,HL        ; DE = INXOFFSET.
  574.     LD    HL,(NUM2DO)    ; Get # direc entries to process.
  575.     SCF            ; Set carry flag.
  576.     SBC    HL,DE
  577.     LD    (MAXINX),HL    ; Store (NUM2DO-1) - INXOFFSET.
  578.                 ; The -1 part is to make the index 0 relative.
  579.                 ; Thus our 0 relative indexes will all be
  580.                 ; Consistent.
  581.     LD    HL,0
  582.     LD    (CURINX),HL
  583.     LD    (SAVINX),HL
  584.     EX    DE,HL        ; Now HL = INXOFFSET.
  585.     ADD    HL,HL        ; Take advantage of ENTRYLEN = 32 = 2 ^ 5.
  586.     ADD    HL,HL
  587.     ADD    HL,HL
  588.     ADD    HL,HL
  589.     ADD    HL,HL        ; Now HL = INXOFFSET * 32.
  590.     LD    (PTROFFSET),HL
  591.     LD    HL,BUF        ; Load initial LFTPTR.
  592.  
  593. ;
  594. ;-----------------------------------------------------------------------
  595. ;
  596. ; On entry to NDONE we have HL = next left pointer.
  597. ; On entry to NDONE1 we also have DE = (PTROFFSET).
  598. ;
  599.  
  600. NDONE:    LD    (SAVLFT),HL
  601.     LD    DE,(PTROFFSET)
  602.  
  603. NDONE1:    LD    (LFTPTR),HL    ; This is an alternate entry point for SWITCH.
  604.     EX    DE,HL
  605.     ADD    HL,DE        ; Now HL = right, DE = left.
  606.  
  607.     LD    B,ENTRYLEN
  608. CMPARE:    LD    A,(DE)
  609.     AND    7FH
  610.     LD    C,A        ; C = (DE) AND 7FH
  611.     LD    A,(HL)
  612.     AND    7FH        ; A = (HL) AND 7FH
  613.     CP    C        ; Form (HL) - (DE) == (right) - (left).
  614.  
  615.      IF    ASCENDING
  616.     JP    C,SWITCH    ; If (right) < (left) then switch.
  617.     JR    Z,EQUAL
  618.      ENDIF
  619.  
  620.      IF    .NOT.ASCENDING
  621.     JR    Z,EQUAL
  622.     EX    AF,AF'          ; Save carry flag.
  623.     LD    A,B        ; Get loop counter.
  624.     CP    ENTRYLEN    ; Are we looking at the 1st byte of entry?
  625.     JR    Z,CP1ST        ; User area byte must be compared ascending.
  626.     EX    AF,AF'
  627.     JP    C,NOSWITCH    ; If (right) < (left) then don't switch.
  628.     JP    SWITCH
  629.  
  630. CP1ST:    EX    AF,AF'
  631.     JP    C,SWITCH    ; If (right) < (left) then switch.
  632.      ENDIF
  633.  
  634. ;
  635. ;-----------------------------------------------------------------------
  636. ;
  637. ; Either drop into here or jump in from SWITCH or from above if descending sort.
  638. ;
  639.  
  640. NOSWITCH:
  641.     LD    HL,(SAVINX)
  642.     INC    HL
  643.     LD    (SAVINX),HL
  644.     LD    (CURINX),HL
  645.     EX    DE,HL        ; Now DE = CURINX
  646.     LD    HL,(MAXINX)
  647.     LD    A,L        ; Now do a CP HL,DE  (as if it existed).
  648.     SUB    E
  649.     LD    A,H
  650.     SBC    A,D
  651.     JR    C,DIVIDE    ; IF CURINX > MAXINX then DIVIDE.
  652.     LD    HL,(SAVLFT)
  653.     LD    DE,ENTRYLEN
  654.     ADD    HL,DE        ; Bump left by 32 bytes.
  655.     JP    NDONE
  656.  
  657. ;
  658. ;-----------------------------------------------------------------------
  659. ;
  660.  
  661. EQUAL:    INC    HL
  662.     INC    DE
  663.     DJNZ    CMPARE        ; Keep checking each byte of entry.
  664.     JP    NOSWITCH    ; If equal then don't switch.
  665.  
  666. ;
  667. ;-----------------------------------------------------------------------
  668. ;
  669. ; Note that on entry B contains the number of chars we need to move.
  670. ;
  671.  
  672. SWITCH:    LD    A,0FFH
  673.     LD    (NOSWAP),A    ; Mark that we've switched something once.
  674.     LD    DE,(LFTPTR)    ; Get the left pointer,
  675.     LD    HL,(PTROFFSET)    ; And the offset from left to the right ptr.
  676.     ADD    HL,DE        ; Now HL = right, DE = left.
  677.     LD    B,ENTRYLEN    ; Length of entry to swap.
  678.  
  679. SWLOOP:    LD    C,(HL)
  680.     LD    A,(DE)
  681.     LD    (HL),A        ; (HL) <== (DE)
  682.     LD    A,C
  683.     LD    (DE),A        ; (HL) ==> (DE)
  684.     INC    HL
  685.     INC    DE        ; Bump both pointers.
  686.     DJNZ    SWLOOP
  687.  
  688.     LD    HL,(CURINX)
  689.     LD    DE,(INXOFFSET)
  690.     OR    A        ; Clear carry.
  691.     SBC    HL,DE
  692.     JR    C,NOSWITCH    ; A bug fix for SAP 46, traces back to 43.
  693.                 ; If CURINX-INXOFFSET < 0 then NOSWITCH.
  694.     LD    (CURINX),HL    ; Now CURINX = CURINX - INXOFFSET.
  695.     LD    HL,(LFTPTR)
  696.     LD    DE,(PTROFFSET)
  697. ;
  698. ; Since we just did a conditional jump on carry set we know that when we
  699. ; are here we have carry clear.
  700. ;
  701. ;    OR    A        ; Clear carry.
  702. ;
  703.     SBC    HL,DE        ; Now HL = left = LFTPTR - PTROFFSET.
  704.     JP    NDONE1
  705.  
  706. BYESORT:            ; This is the sort's procedure exit location.
  707.  
  708. ;
  709. ;=======================================================================
  710. ;
  711. ; PACK the directory entries.
  712. ;
  713.  
  714. PACK:    LD    HL,0        ; I = 0
  715.     LD    BC,BUF+9    ; This is a constant taken out of the loop.
  716.  
  717. PACK1:    PUSH    HL        ; Save index i.
  718.     ADD    HL,HL
  719.     ADD    HL,HL
  720.     ADD    HL,HL
  721.     ADD    HL,HL
  722.     ADD    HL,HL        ; Compute HL = 32 * i
  723.     ADD    HL,BC        ; Now HL = buf + 9 + 32 * i = addr of file type.
  724.  
  725.     LD    A,(HL)        ; Jump if filetype not 'x??'.
  726.     SUB    '0'
  727.     JR    C,PACK2
  728.     CP    10
  729.     JP    NC,PACK2
  730.     EX    AF,AF'          ; Save extent number x in A'.
  731.  
  732.     LD    A,'$'        ; Make sure file type is '.x$$'.
  733.     INC    HL
  734.     CP    (HL)
  735.     JP    NZ,PACK2    ; If not '$' then next entry.
  736.     INC    HL
  737.     CP    (HL)
  738.     JR    NZ,PACK2    ; If not '$' then next entry.
  739.  
  740.     INC    HL        ; Bump to extent number field.
  741.     EX    AF,AF'          ; Recall x.
  742.     LD    (HL),A        ; Set extent number to x.
  743.     DEC    HL
  744.     DEC    HL
  745.     DEC    HL        ; Point back to the x.
  746.     LD    (HL),'$'    ; Make file type .$$$ once again.
  747.  
  748. PACK2:    POP    HL        ; Recall index i.
  749.     INC    HL
  750.     LD    DE,(NUM2DO)    ; Skip the empty entries at end of buffer.
  751.     LD    A,E        ; Now do a CP DE,HL  (as if it existed).
  752.     SUB    L
  753.     LD    A,D
  754.     SBC    A,H
  755.     JP    NZ,PACK1    ; Loop until i = # file names to process.
  756.  
  757. BYEPACK:            ; This convenient symbol helps in debugging.
  758.  
  759. ;
  760. ;=======================================================================
  761. ;
  762. ; Now write the directory back and finish up.  The PUTDIR symbol is only used
  763. ; for debugging purposes - nothing actually references it.
  764. ;
  765.  
  766. PUTDIR:    CALL    WRDIR        ; Write the modified directory to disk.
  767.     LD    DE,FINISHED    ; Drop into PRLAST to finish up.
  768.  
  769. ;
  770. ;=======================================================================
  771. ;
  772. ; Print the message pointed to by DE and terminate.
  773. ; This routine is used to compress needless code repetition.
  774. ;
  775.  
  776. PRLAST:    LD    C,PRINT
  777.     CALL    BDOS
  778.     JP    BOOT
  779.  
  780. ;
  781. ;=======================================================================
  782. ;
  783. ; READ DIRECTORY entry point.
  784. ;
  785.  
  786. RDDIR:    LD    C,PRINT
  787.     LD    DE,READING    ; Tell user we're about to read the directory.
  788.     CALL    BDOS
  789.     XOR    A        ; A=0 to indicate directory read.
  790.     JP    DODIR        ; Enter main directory routines.
  791.  
  792. ;
  793. ; WRITE DIRECTORY entry point.
  794. ;
  795.  
  796. WRDIR:    LD    A,(NOSWAP)
  797.     OR    A        ; Is noswap = 0?
  798.     JP    NZ,WRDIR1    ; If not zero then needed sorting,
  799.     LD    C,PRINT
  800.     LD    DE,PREVIOUS    ; Else tell user no swaps were needed.
  801.     CALL    BDOS
  802.  
  803. WRDIR1:    LD    C,PRINT
  804.     LD    DE,WRITING    ; Tell user we're now writing the directory.
  805.     CALL    BDOS
  806.     LD    A,1        ; A <> 0 signals writes instead of reads.
  807.                 ; Fall into DODIR.
  808.  
  809. ;
  810. ;=======================================================================
  811. ;
  812. ; The main read/write directory routine.
  813. ;
  814.  
  815. DODIR:    LD    (RWFLAG),A    ; Save read/write flag.
  816.     LD    BC,(SYSTRK)    ; Get track num of directory.
  817.     LD    (TRACK),BC    ; Save as current track.
  818.     CALL    SETTRK
  819.     LD    HL,0
  820.     LD    (RECORD),HL    ; Set current record to 0.
  821.     LD    HL,(NUMDIRRECS)
  822.     LD    (DIRCNT),HL    ; Init loop counter.
  823.     LD    HL,BUF
  824.     LD    (DMAPTR),HL    ; Start at buf for dma.
  825.  
  826. DIRLOP:    LD    BC,(RECORD)
  827.     INC    BC        ; Bump to next record.
  828.     LD    HL,(SPT)    ; Get num records per track.
  829.     LD    A,L        ; Now do a CP HL,BC  (as if it existed).
  830.     SUB    C
  831.     LD    A,H
  832.     SBC    A,B
  833.     JP    NC,NOTROV    ; If spt >= record then we're ok,
  834.                 ; Else we drop into track bumping.
  835.  
  836.     LD    BC,(TRACK)    ; Track overflow, bump to next.
  837.     INC    BC
  838.     LD    (TRACK),BC
  839.     CALL    SETTRK
  840.     LD    BC,1        ; Rewind record number to start of track.
  841.  
  842. NOTROV:    LD    (RECORD),BC    ; Save computed record number.
  843.     DEC    BC        ; Make relative to 0 instead of to 1.
  844.     LD    DE,(RECTBL)
  845.     CALL    RECTRN        ; Returns HL = physical record number.
  846.  
  847.      IF    UNSPECI
  848.     LD    B,H
  849.     LD    C,L        ; SETREC needs BC = record number.
  850.     LD    A,(VERFLG)
  851.     OR    A
  852.     CALL    NZ,SETREC    ; If CP/M 2.2 then call SETREC.
  853.      ENDIF
  854.  
  855.      IF    ONLY22
  856.     LD    B,H
  857.     LD    C,L        ; SETREC needs BC = record number.
  858.     CALL    SETREC
  859.      ENDIF
  860.  
  861.     LD    BC,(DMAPTR)
  862.     CALL    SETDMA
  863.     LD    A,(RWFLAG)    ; Are we reading the directory, or writing it?
  864.     OR    A
  865.     JR    NZ,DWRT
  866.  
  867.                 ; Read the directory.
  868.     CALL    READ
  869.     OR    A        ; Test flags on read.
  870.     JP    Z,MORE        ; If 0 then ok, else . . . .
  871.  
  872.     LD    DE,RDERR    ; Oops, a read error.
  873.     JP    PRLAST        ; Print message and die.
  874.  
  875.                 ; Write the directory.
  876. DWRT:    LD    C,1        ; For CP/M 2.2 deblocking BIOS's.
  877.     CALL    WRITE
  878.     OR    A        ; Test flags on write.
  879.     JR    NZ,BADWRT    ; If A <> 0 then an error.
  880.  
  881.                 ; The read or write succeeded.
  882. MORE:    LD    HL,(DMAPTR)
  883.     LD    DE,128
  884.     ADD    HL,DE
  885.     LD    (DMAPTR),HL    ; Bump dma address for next pass
  886.     LD    HL,(DIRCNT)
  887.     DEC    HL
  888.     LD    (DIRCNT),HL    ; Count down entries.
  889.     LD    A,H
  890.     OR    L
  891.     JP    NZ,DIRLOP    ; Loop till zero left,
  892.     RET            ; And then we're done.
  893.  
  894.                 ; Come here if we get a write error.
  895. BADWRT:    LD    DE,WRTERR
  896.     JP    PRLAST        ; Print message and die.
  897.  
  898. ;
  899. ;=======================================================================
  900. ;
  901. ; Various Messages
  902. ;
  903.  
  904. IDENTITY:
  905.     DEFB    CR,LF,'Sort and Pack Directory v'
  906.     DEFB    '50 8/7/86',CR,LF,EOS
  907.  
  908. READING:
  909.     DEFB    LF,'---> Reading, ',EOS
  910.  
  911. SORTING:
  912.     DEFB    'Sorting',EOS
  913.  
  914. PREVIOUS:
  915.     DEFB    ', (Previously Sorted)',EOS
  916.  
  917. WRITING:
  918.     DEFB    ', Writing',EOS
  919.  
  920. FINISHED:
  921.     DEFB    ', done'
  922.     DEFB    CR,LF,EOS
  923.  
  924. RDERR:
  925.     DEFB    CR,LF,'++ Read Error - no change made ++',CR,LF,EOS
  926.  
  927. WRTERR:
  928.     DEFB    CR,LF,'++ Write Error '
  929.     DEFB    '- directory left in unknown condition ++',CR,LF,EOS
  930.  
  931.      IF    UNSPECI.AND.(.NOT.CPMONLY)
  932. BADOPSYS:
  933.     DEFB    LF,'++ SAP not useable with MP/M or CP/M 3.0 ++',CR,LF,EOS
  934.      ENDIF            ; UNSPECI AND (NOT CPMONLY)
  935.  
  936. LACKMEMORY:
  937.     DEFB    LF,'++ Directory too large - '
  938.     DEFB    'SAP will process as much as possible. ++',CR,LF,EOS
  939.  
  940. BADDRIVE:
  941.     DEFB    LF,'++ Only drives A through ',MAXDRIVE,' are valid ++'
  942.     DEFB    CR,LF,EOS
  943. ;
  944. ;=======================================================================
  945. ;
  946. ; Stack and Buffer Area.
  947. ;
  948.  
  949.     DEFS    4+ENTRYLEN    ; Minimum stack depth (of 2), plus a
  950.                 ; Dummy entry used for CLEAN.
  951.                 ; Note that BDOS will switch to its own
  952.                 ; Stack when called, so this is
  953.                 ; Enough.
  954.  
  955. EVEN    EQU    (($+255)/256)*256 ; Start buffer on even page, which also
  956.                 ; Increases stack area greatly.
  957.     ORG    EVEN
  958. STACK    EQU    $-ENTRYLEN    ; Note that stacks grow downward.
  959. BUF    EQU    $
  960.  
  961. ;
  962. ;=======================================================================
  963. ;
  964. ; Variable Area.
  965. ;
  966.  
  967.     ORG    CMDBUF        ; The CP/M command line buffer is convenient.
  968.                 ; This gives me 128 bytes of free storage!
  969.  
  970. ;
  971. ; The BIOS vectors we use.
  972. ;
  973. SELDSK:    DEFS    3
  974. SETTRK:    DEFS    3
  975. SETREC:    DEFS    3
  976. SETDMA:    DEFS    3
  977. READ:    DEFS    3
  978. WRITE:    DEFS    3
  979. LSTS:    DEFS    3        ; Only in CP/M 2.2  (not used here)
  980. RECTRN:    DEFS    3        ; Only in CP/M 2.2
  981.  
  982.      IF    UNSPECI
  983. VERFLG    EQU    LSTS        ; Squeeze him in where he fits!
  984.      ENDIF
  985.  
  986. NUMDIRRECS EQU    LSTS+1        ; Squeeze him in where he fits!
  987.  
  988. ;
  989. ; Disk parameter block vars.
  990. ;
  991. SPT:    DEFS    2        ; Sectors per track
  992. DRM:    DEFS    2        ; # direc entries - 1, before calcspace
  993. SYSTRK:    DEFS    2        ; # reserved tracks = 1st direc track
  994. RECTBL:    DEFS    2        ; Logical ==> physical, record translation table addr
  995. NUMFNAMES EQU    DRM        ; This is drm + 1 = # direc entries, after calcspace.
  996.  
  997. ;
  998. ; Temp vars for reads/writes.
  999. ;
  1000. RWFLAG:    DEFS    1
  1001. DIRCNT:    DEFS    2
  1002. DMAPTR:    DEFS    2
  1003. RECORD:    DEFS    2
  1004. TRACK:    DEFS    2
  1005.  
  1006. ;
  1007. ; Temp vars for sorting.
  1008. ;
  1009. NOSWAP:    DEFS    1        ; Flag:  00h = no swaps,  0FFh = some swaps.
  1010. NUM2DO:    DEFS    2        ; Number of entries to sort.
  1011. MAXINX:    DEFS    2        ; Maximum left pointer value.
  1012. SAVLFT:    DEFS    2        ; Save for left pointer.
  1013. LFTPTR:    DEFS    2        ; Current left pointer.
  1014. SAVINX:    DEFS    2        ; Save for entry index.
  1015. CURINX:    DEFS    2        ; Current entry index.
  1016. INXOFFSET: DEFS    2        ; Index offset (or comparison distance).
  1017. PTROFFSET: DEFS    2        ; Pointer offset = entrylen * INXOFFSET.
  1018.  
  1019.     END
  1020.