!    ****************************************************
!    *                                                  *
!    *    S D O S  S y s t e m   G e n e r a t i o n    *
!    *                                                  *
!    *           SDOS SYSGEN PROGRAM                    *
!    ****************************************************
!
!   Revision History:
!
!   V1.0h    1/1/81
!            Converted to BASIC 1.4h. Made operator interface nice.
!            Upper/Lower cased everything.
!   V1.0j    2/13/82
!            Increased Load record limit to 200 load records.
!            Sorts load records in ascending order.
!            Merges new load records onto either front or tail of old ones.
!            Allowed SDOSSYSGEN to Write protect SDOS.SYS when complete
!                    (for SDOS 1.1)
!   V1.0k    5/1/82
!            Converted BINARYACCESS.ASM to 680c assembly code and tested.
!                    Interface to BINARYACCESS.ASM was changed a little.
!            Removed spectacular banner message; it ate up too much screen.
!            Cleaned up Operator interface a little bit.
!            Allow either 6800, 6801 or 6809 Start records in files.
!                    Verify that all files given are for the same type CPU.
!            Increased Load record limit on input files 250 load records;
!            lowered limit on number of output records to 100 to save space.
!
    DIM    TITLE$/"SDOS System Generation Program V1.0k"/
    DIM    SCGETPROT$/:F,:E,0,:11/,CCSETPROT$/:E,:8,0,:11/
    DIM    PROT$/0/,WPROT/:40/
    DIM    ZERO$/0/
    DIM    ADDRESS$(2)
    DIM    DISK$[100],BOOTFILE$[100],COUNT$(2)
    DIM    CPUTYPE/0/,LOAD2$/2/,LOAD3$/3/
    DIM    COPYBUF$(256), ADD$(2)/0,0/
    DIM    BUFFER1$(100), BUFFER2$(100)

    ! ARRAY1$ must be DIMed as ARRAY1$(8*maxnumberofoutputrecords).
    ! ARRAY2$ must be DIMed as ARRAY2$(8*maxnumberofinputrecords).
    ! LOWER and UPPER must be DIMed as LOWER(maxnumberofinputrecords), etc.
    ! These constraints are derived from BINARYACCESS.ASM.

    DIM    ARRAY1$(800), ARRAY2$(2000)
    DIM    LOWER(250),UPPER(250)
    DIM    FILE$[100], BYTE$(1), DOUBLEBYTE$(2)
    DIM    TEMP$(100)
    DIM    STARTR$[5]
    DIM    ZEROS$[128]

    DEF    MSB(X1)=(X1&:FF00)/256
    DEF    LSB(X2)=X2&:FF

    SUBROUTINE CLEARPROT(CHAN1)
       IF ERROR WHEN SYSCALL #CHAN1,SCGETPROT$,"",PROT$
          THEN IF ERR <> 1034 THEN ERROR FI
          ELSE
             PROT$[1]=PROT$[1]&COM(WPROT)
             SYSCALL #CHAN1,CCSETPROT$,PROT$
          FI
       EXIT SUBROUTINE
    END

    SUBROUTINE SETPROT(CHAN2)
       IF ERROR WHEN SYSCALL #CHAN2,SCGETPROT$,"",PROT$
          THEN IF ERR <> 1034 THEN ERROR FI
          ELSE
             PROT$[1]=PROT$[1]!WPROT
             SYSCALL #CHAN2,CCSETPROT$,PROT$
          FI
       EXIT SUBROUTINE
    END

    SUBROUTINE VERIFYCPUTYPE(CPUTYPEARGUMENT)
        ! Check that CPU type is 1 (6800), 2 (6809) or 3 (6801)
        ! Also check CPU type is consistent with all other modules
        IF ( CPUTYPEARGUMENT=1 OR CPUTYPEARGUMENT=2 OR CPUTYPEARGUMENT=3 ) ...
&          AND ( CPUTYPE=0 OR CPUTYPE=CPUTYPEARGUMENT )
        THEN CPUTYPE=CPUTYPEARGUMENT \ ! Remember type of CPU for later checks
        ELSE ERROR 1028 \ ! Can't load that, not load format file!
        EXIT SUBROUTINE
    END

    ! Reset the arrays.
    TOP    =0
    LEN(ARRAY1$)=MAXLEN(ARRAY1$)
    LEN(BUFFER1$)=MAXLEN(BUFFER1$)\! BINARY ACCESS REQUIRES THIS
    LEN(ARRAY2$)=MAXLEN(ARRAY2$)
    LEN(BUFFER2$)=MAXLEN(BUFFER2$)\! BINARY ACCESS REQUIRES THIS
    LEN(ZEROS$)=MAXLEN(ZEROS$)
    FOR    I=1 TO LEN(ZEROS$) DO ZEROS$(I)=0

    IF COL(0)>1
    THEN
        ! OPERATOR KNOWS THAT HE MUST TYPE FILE NAME NEXT.
        INPUT "" DISK$
        PRINT TITLE$
    ELSE
        ! THIS IS A DUMMY OR HE FORGOT. GIVE HIM DIRECTIONS.
        PRINT TITLE$ \ ! TELL THE WORLD WHO WE ARE.
        INPUT    "Sysgen to: " DISK$
    FI
    IF LEN(DISK$)=0
    THEN PRINT "You must be more specific than that."\Exit
    IF DISK$[LEN(DISK$),1]<>":"
    THEN
        REM USER WANTS TO MERGE BINARY FILES
        CREATE #1,DISK$
        GOTO GENSDOS
    ELSE
        OPEN #3, DISK$ CAT "BOOT.SYS"
        CLEARPROT(3)\ ! REMOVE WRITE PROTECTION ON BOOT.SYS
        OPEN #1, DISK$ CAT "SDOS.SYS"
        CLEARPROT(1)\ ! REMOVE WRITE PROTECTION ON SDOS.SYS
    FI
    PRINT

 STARTOVER: PRINT "Enter name of Object file to be installed in BOOT.SYS"
    INPUT "(Default is none): " BOOTFILE$
    IF BOOTFILE$="" THEN SETPROT(3)\CLOSE #3\GOTO GENSDOS
    OPEN #2, BOOTFILE$
    READ #2, STARTR$
    VERIFYCPUTYPE(STARTR$(1))
    BOOTBASE=STARTR$(2)*256+STARTR$(3)
    CURBYTE=5

GENBOOT: RESTORE #2, CURBYTE
    READ #2, BYTE$
    ON BYTE$(1)+1 GOTO BOOTSKIP,BOOTSTART,BOOTLOAD,BOOTGO
BOOTSTART: PRINT "Boot binary not in valid load format!"
    GOTO STARTOVER
BOOTSKIP: READ #2, DOUBLEBYTE$
    CURBYTE =CURBYTE+3+DOUBLEBYTE$(1)*256+DOUBLEBYTE$(2)
    GOTO GENBOOT

BOOTLOAD: GOSUB GETBOOTREC
    GOTO GENBOOT

GETBOOTREC: READ #2, ADDRESS$, COUNT$
    ADDRESS=ADDRESS$(1)*256+ADDRESS$(2)
    COUNT=COUNT$(1)*256+COUNT$(2)

    RESTORE #3, ADDRESS-BOOTBASE
    ! COPY BOOT PROGRAM CONTENTS THE ICKY WAY.
    ! ITS A GOOD THING IT ISN'T VERY BIG.
    FOR I=1 TO COUNT
        READ #2, BYTE$
        WRITE #3, BYTE$
    NEXT I

    CURBYTE=CURBYTE+5+COUNT
    RETURN

BOOTGO: GOSUB GETBOOTREC
    SETPROT(3)\ ! PUT WRITE PROTECTION BACK ON BOOT.SYS
    CLOSE #3
    CLOSE #2
    PRINT "BOOT.SYS file generated."

GENSDOS:
    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    !                                           !
    !   S D O S . S Y S   G e n e r a t i o n   !
    !                                           !
    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    PRINT    "*** SDOS.SYS Generation Step ***"
    PRINT    "Please enter the names of the files to be merged"
    PRINT    "Type <CR> for first name to skip this step."
    PRINT    "Type <CR> for file name when all names have been entered."
    PRINT
    FOR    CHAN=2 TO 255
ASKAGAIN: PRINT    "Enter the name for file #";CHAN-1;": ";
        INPUT    "" FILE$
        IF FILE$="" THEN
            IF CHAN=2 THEN DONE ELSE EXIT CHAN
        FI
        IF ERROR WHEN
            OPEN #CHAN, FILE$
        THEN
            IF ERR=1011 THEN PRINT "No such file."\GOTO ASKAGAIN
            ERROR
        FI
    NEXT CHAN

PASS1: NCHANS=CHAN-1
!   CONSTRUCT MAP OF OUTPUT FILE
    FOR CHAN=2 TO NCHANS DO GOSUB EATRECORDS
!   gosub printlist
!
!    GET START RECORD ADDRESS
!
ASKSTARTADDR: INPUT "Enter desired Start address (default is :00FB): " TEMP$
    IF LEN(TEMP$)=0
    THEN STARTADDR=:00FB
    ELSE
        IF ERROR WHEN
            STARTADDR=VAL(TEMP$)
        THEN ASKSTARTADDR
    FI
    ! BUILD SKELETAL OBJECT OUTPUT FILE
    STARTR$(1)=CPUTYPE \ ! MARK START RECORD WITH PROCESSOR TYPE
    ! PRINT "CPU TYPE= ";CPUTYPE
    STARTR$(2)=MSB(STARTADDR)
    STARTR$(3)=LSB(STARTADDR)
    STARTR$(4)=MSB(COM(STARTADDR))
    STARTR$(5)=LSB(COM(STARTADDR))
    LEN(STARTR$)=5
    RESTORE #1, 0
    WRITE #1, STARTR$
    CURBYTE=5
    FOR I=1 TO TOP
        LAST2REC=CURBYTE
        WRITE #1, LOAD2$
        ADD$(1)=MSB(LOWER(I))
        ADD$(2)=LSB(LOWER(I))
        WRITE #1, ADD$

        ADD$(1)=MSB(UPPER(I)-LOWER(I)+1)
        ADD$(2)=LSB(UPPER(I)-LOWER(I)+1)

        WRITE #1, ADD$
        OLDCURBYTE=CURBYTE
        CURBYTE=CURBYTE+5+UPPER(I)-LOWER(I)+1
        COUNT=CURBYTE-OLDCURBYTE-5
        ! ENSURE THAT FILE DOES NOT BECOME SPARSE
        FOR J=1 TO INT(COUNT/MAXLEN(ZEROS$)) DO WRITE #1,ZEROS$
        FOR J=1 TO COUNT-MAXLEN(ZEROS$)*INT(COUNT/MAXLEN(ZEROS$))
           WRITE #1,ZERO$
        NEXT J
    NEXT I
    RESTORE #1, LAST2REC
    WRITE #1, LOAD3$
!
!    PASS 2: NOW COPY FILES
!
    CALL    INIT(1,BUFFER1$,ARRAY1$)\! TELL BINARYACCESS ABOUT OUTPUT FILE

    FOR CHAN=2 TO NCHANS
        TOP =0
        GOSUB EATRECORDS
!       gosub printlist
        CALL INIT(CHAN,BUFFER2$,ARRAY2$)
        FOR I=1 TO TOP
            CURLOWER=LOWER(I)
            CURUPPER=UPPER(I)
            GOSUB COPYBINARY
        NEXT I
        CALL RELEASE(CHAN)
    NEXT CHAN
    CALL RELEASE(1)

DONE:
    PRINT "Done."
QUIT:
    IF DISK$(LEN(DISK$),1)=":"
    THEN
        ! USER SYSGENNED SDOS.SYS
        SETPROT(1) \ ! PUT PROTECTION BACK ON SDOS.SYS
    FI
    CLOSE #1
    EXIT

COPYBINARY: IF MAXLEN(COPYBUF$)>CURUPPER-CURLOWER+1
    THEN LEN(COPYBUF$)=CURUPPER-CURLOWER+1
    ELSE LEN(COPYBUF$)=MAXLEN(COPYBUF$)
    IF LEN(COPYBUF$)=0 THEN RETURN
    CALL GETBYTE(CHAN,CURLOWER,COPYBUF$)
    CALL PUTBYTE(1,CURLOWER,COPYBUF$)
    CURLOWER=CURLOWER+LEN(COPYBUF$)
    GOTO COPYBINARY

EATRECORDS: ! EAT THE LOAD RECORDS OF A COMPONENT FILES TO FIND ADDRESS RANGES
    READ #CHAN@0, BYTE$
    VERIFYCPUTYPE(BYTE$[1])
    READ #CHAN,DOUBLEBYTE$
    ADDRESS=DOUBLEBYTE$(1)*256+DOUBLEBYTE$(2)
    READ #CHAN,DOUBLEBYTE$
    IF ADDRESS+DOUBLEBYTE$(1)*256+DOUBLEBYTE$(2)<>:FFFF THEN BADREC
    CURBYTE=5 \ ! SKIP PAST START RECORD
NEXTRECORD: RESTORE #CHAN, CURBYTE
    READ #CHAN, BYTE$
    ON BYTE$(1)+1 GOTO SKIPREC, BADREC, LOADREC, GOREC
BADREC: PRINT "Bad Load record at";CURBYTE
    GOTO QUIT

SKIPREC: READ #CHAN, DOUBLEBYTE$
    CURBYTE=CURBYTE+3+DOUBLEBYTE$(1)*256+DOUBLEBYTE$(2)
    GOTO NEXTRECORD

LOADREC: GOSUB GOREC
    GOTO NEXTRECORD

GOREC: READ #CHAN, DOUBLEBYTE$
    NEWLOWER=DOUBLEBYTE$(1)*256+DOUBLEBYTE$(2)
    READ #CHAN, DOUBLEBYTE$
    NEWUPPER=DOUBLEBYTE$(1)*256+DOUBLEBYTE$(2)
    CURBYTE=CURBYTE+5+NEWUPPER
    NEWUPPER=NEWUPPER+NEWLOWER-1
    GOTO ADDRECORD


!PRINTLIST: FOR I=1 TO TOP
!    PRINT HEX$(LOWER(I));" TO ";HEX$(UPPER(I))
!    NEXT I
!    RETURN

ADDRECORD:

    ! NEWLOWER has virtual address of 1st data byte in load record.
    ! NEWUPPER has virtual address of last data byte in load record.
    ! Likewise, LOWER(i) has virtual address of 1st byte in proposed record.
    ! and UPPER(i) has virtual address of last data byte in proposed record.

    ! This routine builds a minimum count list of load record descriptors
    ! by sorting, and merging overlapping records found from files being
    ! combined.

    ! The stack of load records is sorted in ascending order: find where
    ! the new load record should go, and see whether it should be merged
    ! with an existing one or inserted as a new one.

!   print hex$(newlower);" to ";hex$(newupper);

    ! find the first record whose highest address is greater or equal to...
    ! the base address of the new record

    !  Find point of insert or merge.
    !  Merge records that overlap or abut one another.
    !  Insert records that cannot be merged, according to sorted order.
    !
    FOR J=1 TO TOP
        ! See if insertion is required.
        IF NEWUPPER<LOWER(J)-1 THEN INSERT \ ! Assert: NEWLOWER<=NEWUPPER
        ! See if merging NOT possible.
        IF NEWLOWER>UPPER(J)+1
        THEN CYCLE J
        ELSE MERGE \ ! After you eliminate the others, this is only choice!
    NEXT J
    ! We arrive here if there is no record with a larger upper bound
    ! --> we must insert a new record

INSERT: ! New load record overlaps no existing one: insert it in slot J

!    if j=top+1
!        then print " on top of stack"
!        else print " before ";hex$(lower(j));" to ";hex$(upper(j))

    IF TOP+1 > LEN(LOWER) THEN
        PRINT 'Too many load records  ( more than';TOP;')'
        CLEARPROT(1)
        EXIT
    FI

    ! Expand the stack and shuffle all records, starting at current one,
    ! up by one

    FOR I=TOP TO J STEP -1 DO
        LOWER(I+1)=LOWER(I)
        UPPER(I+1)=UPPER(I)
    END
    TOP=TOP+1

    ! Insert new record before current one

    LOWER(J)=NEWLOWER
    UPPER(J)=NEWUPPER
    RETURN


MERGE:

    ! New load record overlaps existing one: make one big load record

!    print " into ";hex$(lower(j));" to ";hex$(upper(j))

    IF NEWLOWER < LOWER(J) THEN LOWER(J)=NEWLOWER
    IF NEWUPPER > UPPER(J) THEN UPPER(J)=NEWUPPER

    ! Now check: can expanded record be merged with neighbor below ?

    IF J>1 AND UPPER(J-1)+1=LOWER(J)
    THEN
        ! Assert: this can only happen once per each merge
!       print "Combine with previous ";hex$(lower(j-1));" to ";hex$(upper(j-1))
        LET UPPER(J-1)=UPPER(J)
        LET J=J-1
        FOR K=J+1 TO TOP DO LOWER(K)=LOWER(K+1)\UPPER(K)=UPPER(K+1)
        LET TOP=TOP-1
    FI

    ! and then check: can combine with neighbor above ?

    UNTIL J=TOP OR UPPER(J)+1<LOWER(J+1) DO
!      print "Combine with next ";hex$(lower(J+1));" to ";hex$(upper(j+1))
       ! Set new upper bound to max upper bound of combined records
       LET UPPER(J)= IF UPPER(J)<UPPER(J+1) THEN UPPER(J+1) ELSE UPPER(J) FI
       FOR K=J+1 TO TOP DO LOWER(K)=LOWER(K+1)\UPPER(K)=UPPER(K+1)
       LET TOP=TOP-1
    END
    RETURN

    END
