   dim Version$/"PATCHBINARY V1.1h"/, DebugFlag/1/
   ! Program delivered with SDOS to allow users to Patch object files.
   ! Will patch any type of file, encrypted or not.
   ! For unencrypted files, will allow user to poke around.
   ! For encrypted files, user can poke around, but won't see anything.
   !     Version 1.0  1978     First revision.  Replaced by BMP
   !     Version 1.1h 6/86     IDB.  Built to go with SDOS1.1h and encrypting files
   !
   dim File$(50),Command$(50)
   dim Lower(200),Upper(200),FilePosition(200)
   dim SCGetPosition$/:F,14,0,0/,IsConsole$/:1A,2/
   dim Byte$/0/,TwoBytes$/0,0/,Temp$(100)
   dim CCSetActivation$/:E,8,0,:14/
   dim ReadA$/:A,:E,0,1/,StartRecordBody$/:00,:00,:FF,:FF/
   dim PatchBinaryActivation$/:00,:00,:00,:00,:00,:80,:00,:84,...
&                             :00,:00,:00,:00,:00,:00,:00,:00/
       ! Patchbinary activates on ? / : and <CR>
   dim NormalActivation$/:00,:00,:00,:00,:00,:00,:00,:00,...
&                        :00,:00,:00,:00,:00,:00,:00,:00/
   dim LoadRecordHeader$(3),SerialNumber$(8),SDOSSecretKey$(8),Length$(2)
   dim CCSetEncryptionKey$/:E,8,0,:1A/

Def CurrentFilePosition
   ! Returns position of file open on channel #1
   Syscall #1,SCGetPosition$,'',Temp$
   Return ((Temp$[1]*256+Temp$[2])*256+Temp$[3])*256+Temp$[4]
End

Def FetchContents(FetchAddress)
   ! Search binary load record table for FetchAddress
   ! Load records are place in list in reverse order
   ! so that index variable doesn't have to go float (...STEP -1)
   For LoadRecordIndex=0 to LoadRecordCount
      If FetchAddress>=Lower(LoadRecordIndex) ...
&     and FetchAddress<=Upper(LoadRecordIndex)
      Then
          FileLocation=FilePosition(LoadRecordIndex)-Lower(LoadRecordIndex)...
&                      +FetchAddress
          Read #1@FileLocation,Byte$
          Return Byte$(1)
   Fi
   Next LoadRecordIndex
   FileLocation=-1 \ ! "Can't find it"
   Return 0
End

Subroutine StoreContents(StoreAddress,StoreByte)
   Byte=FetchContents(StoreAddress)
   If FileLocation<0
   Then
       ! File doesn't contain that location.
       If Upper(LoadRecordIndex-1)+1<>StoreAddress
       Then
           ! Must create new load record
           If LoadRecordIndex>len(Upper)
           Then Print "Load Record capacity exceeded!"\Error 104
           LoadRecordCount=LoadRecordCount+1
           Lower(LoadRecordIndex)=StoreAddress
           Upper(LoadRecordIndex)=StoreAddress-1
           FilePosition(LoadRecordIndex)=FilePosition(LoadRecordIndex-1)...
&                    +Upper(LoadRecordIndex-1)-Lower(LoadRecordIndex-1)+5
           TwoBytes$[1]=int(StoreAddress/256)
           TwoBytes$[2]=StoreAddress&:FF
           Write #1@FilePosition(LoadRecordIndex)-2,chr$(3),TwoBytes$
           Write #1@FilePosition(LoadRecordIndex-1)-5,chr$(2)
           LoadRecordIndex=LoadRecordIndex+1
       Fi
       ! Extend last load record.
       LoadRecordIndex=LoadRecordIndex-1
       Upper(LoadRecordIndex)=Upper(LoadRecordIndex)+1
       LoadRecordLength=Upper(LoadRecordIndex)-Lower(LoadRecordIndex)+1
       TwoBytes$[1]=int(LoadRecordLength/256)
       TwoBytes$[2]=LoadRecordLength&:FF
       Write #1@FilePosition(LoadRecordIndex)-2,TwoBytes$
   Fi
   ! Location exists in file.
   Write #1@FileLocation,chr$(StoreByte)
   Return Subroutine
End
!^L
! *** BEGIN MAIN PROGRAM ***
   If Col(0)=1
   Then
       ! User wants to be prompted.
       Print Version$
       Input "File to patch: " File$
       Print "Next time, you can type: "
       Print "     .PATCHBINARY ";File$
   Else
       ! User has supplied file name on prompt line
       Input "" File$
       Print Version$
   Fi
   Gosub ProcessObjectFile
   If Error When Call DoCommands
   Then
       Syscall CCSetActivation$,'',NormalActivation$ \ ! Restore activation to standard
       Error \ ! and force error
   Else
       Syscall CCSetActivation$,'',NormalActivation$ \ ! Restore activation to standard
       Exit \ ! and leave nicely
   Fi
!^L
Subroutine DoCommands
! Processes commands from user
   Syscall CCSetActivation$,'',PatchBinaryActivation$

NextCommand: ! Process another command
   If Error When
      Syscall Reada$,'',Command$
   Then
       If Err=1
       Then
           Print "Operator Requested Attention"
           Goto NextCommand
       Else Error
   Fi
ProcessCommand: ! Re-enter here after skipping
   Let Command$=UpperCase$(Command$)
   If Command$[len(Command$),1]="?"
   Then
       If Command$="?" then Gosub PrintHelpMenu\Goto NextCommand
       ElseIf Command$="START?"
       Then
           Print " ";hex$(StartAddress)
       Else
           ! Process XX? command
           If Error When Byte=val(":" cat Command$)
           Then
               If Err=7
               Then Print " Bad hex value."
               Else Error
           Else
               If OpenAddress<0
               Then Print " No location open."
               If EncryptedObjectFile
               Then
                   If Error When Syscall IsConsole$
                   Then
                       If Err=1059
                       Then
                           ! Patching encrypted file from DO file. OK!
                       Else Error \ ! What could it be???
                   Else Error 103 \ ! "Can't do GOTO from Console:"
               Fi
               If Byte=FetchContents(OpenAddress)
               Then Print " Matched!"
               Else
                   Repeat
                       Print " Mismatch, skipping..."
                       If Error When
                       Syscall Reada$,'',Command$
                       Then
                           If Err=1
                           Then
                               Print "Operator Requested Attention"
                           Else Error
                       Fi
                       If Command$(len(Command$),1)=":"
                       Then ProcessCommand
                   End
               Fi
           Fi
       Fi
   ElseIf Command$[len(Command$)]=:D
   Then
       ! Command activated on <CR>
       Let len(Command$)=len(Command$)-1
       If Command$=''
       Then
           ! Must be EXAMINE NEXT
           If OpenAddress<0 Then NextCommand \ ! Ignore <CR> by itself
           Gosub ExamineNext
       ElseIf Command$="EXIT" then Return Subroutine \ ! Done
       ElseIf find(Command$,"START=")=1
       Then
           ! Must want to set start address
           If Error When Byte=val(":" cat Right$(Command$,7))
           Then
               If Err=7
               Then Print " Bad hex value."\Goto NextCommand
               Else Error
           Else
               StartAddress=Byte
               StartRecordBody$[1]=int(Byte/255)
               StartRecordBody$[2]=Byte&:FF
               StartRecordBody$[3]=:FF-StartRecordBody$[1]
               StartRecordBody$[4]=:FF-StartRecordBody$[2]
               Write #1@StartRecordAddress+1,StartRecordBody$
           Fi
       Else
           ! DEPOSIT AND EXAMINE NEXT COMMAND
           If OpenAddress<0
           Then Print "No open location."\Goto NextCommand
           If Error When Byte=val(":" cat Command$)
           Then
               If Err=7
               Then Print " Bad hex value."\Goto NextCommand
               Else Error
           Else
               StoreContents(OpenAddress,Byte)
           Fi
           Gosub ExamineNext
       Fi
   ElseIf Command$[len(Command$),1]="/"
   Then
       ! Command activated on SLASH character
       OpenAddress=-1 \ ! Assume "No longer open"
       If Error When Byte=val(":" cat Command$)
       Then
           If Err=7
           Then Print " Bad hex value."
           Else Error
       Else
           OpenAddress=Byte
       Fi
       Gosub ExamineLocation
   ElseIf Command$[len(Command$),1]=":"
   Then
       ! Command activated on COLON character
       OpenAddress=-1 \ ! Assume "No longer open"
       If Error When Byte=val(":" cat Command$)
       Then
           If Err=7
           Then Print " Bad hex value."
           Else Error
       Else
           OpenAddress=Byte
       Fi
       Print " ";
   Else
       Print "Bad command. Type '?' for help."
   Fi
   Goto NextCommand
End
!^L
ExamineNext: ! Do work for Examine Next logic
    If OpenAddress=65535
    Then OpenAddress=0
    Else OpenAddress=OpenAddress+1
ExamineLocation:
    Byte=FetchContents(OpenAddress)
    Print hex$(OpenAddress)[2,4];"/ ";
    If EncryptedObjectFile
    Then Print "?? "; \ ! Not allowed to look.
    ElseIf FileLocation<0
    Then Print "xx "; \ ! No data at that location.
    Else Print hex$(Byte)[4,2];" "; \ ! Normal examine
    Return

PrintHelpMenu:
   Print "Commands valid for PatchBinary:"
   Print "  ?                To get this display"
   Print "  xx?              To verify that location contains xx"
   Print "                     (if not, skips commands until next xxxx:)"
   Print "  start?           To examine start address"
   Print "  start=xxxx<CR>   To change start address"
   Print "  xxxx/            To examine a location"
   Print "  xxxx:            To set location to modify"
   Print "  <CR>             To examine next location"
   Print "  xx<CR>           To deposit byte, examine next"
   Print "  exit             To exit"
   Print
   Return

ProcessObjectFile: ! Collect information about object file
    Open #1,File$
    Read #1,Byte$ \ ! Get type of object file
    If EOF(1) Then Error 1001
    If Byte$[1]<>:05
    Then
        EncryptedObjectFile=False \ ! File is not encrypted
        StartRecordAddress=CurrentFilePosition \ ! Remember where this is
    Else
        EncryptedObjectFile=True
        ! An encrypted file! Well, allow him to patch it...
        ! but not to look at it.
        ! We don't care whether it is for this machine or not.
        Print "Encrypted File for Serial Number(s):"
        Read #1,Byte$
        If EOF(1) Then Error 1001
        Let NumberOfKeys=Byte$[1] \ ! # of Serial Numbers in file
        Position #1@8 \ ! Where 1st serial number is in file
        For i=1 to NumberOfKeys
            ! For each serial number
            Read #1,SerialNumber$
            If EOF(1) Then Error 1001
            For j=1 to 8
                Print hex$(SerialNumber$[j])[4,2]; \ ! Print Serial number byte
            Next j
            Print \ ! Force one serial number printed per line
        Next i
        len(SDOSSecretKey$)=8 \ ! Construct SDOSSecretKey
        SDOSSecretKey$[1]=:4C \ ! Doing it this way keeps it well hidden...
        SDOSSecretKey$[2]=:B0 \ ! as this is compiled BASIC code
        SDOSSecretKey$[3]=:B7 \ ! and virtually nobody knows how to decompile it
        SDOSSecretKey$[4]=:4E
        SDOSSecretKey$[5]=:9B
        SDOSSecretKey$[6]=:65
        SDOSSecretKey$[7]=:72
        SDOSSecretKey$[8]=:C9
        Syscall #1,CCSetEncryptionKey$,SDOSSecretKey$ \ ! To Start Decrypt cascade
        Read #1@0,SDOSSecretKey$ \ ! Decrypt type 5 record to start cascade
        Syscall #1,CCSetEncryptionKey$,SDOSSecretKey$ \ ! Set decryption key
        For i=1 to NumberOfKeys
            Read #1,SDOSSecretKey$ \ ! Decrypt next serial number cascade
            Syscall #1,CCSetEncryptionKey$,SDOSSecretKey$ \ ! and use as key
        Next i
        ! Now decryption key for file has been computed.
        StartRecordAddress=CurrentFilePosition \ ! Remember where this is
        Read #1,Byte$ \ ! Read what should be 1st byte of start record
        If EOF(1) Then Error 1001
    Fi
    Read #1,Length$ \ IF EOF(1) Then Error 1001
    StartAddress=Length$[1]*256+Length$[2]
    Read #1,Length$ \ IF EOF(1) Then Error 1001
    ComplementStartAddress=Length$[1]*256+Length$[2]
    If StartAddress+ComplementStartAddress<>:FFFF
    Then Error 1028 \ ! Bad start record

    If Byte$[1]=:01 Then Print "6800/6802 Object file"
    ElseIf Byte$[1]=:02 Then Print "6809 Object file"
    ElseIf Byte$[1]=:03 Then Print "6801/6803 Object file"
    ElseIf Byte$[1]=:07 Then Print "6303 Object file"
    ElseIf Byte$[1]=:09 Then Print "6805 Object file"
    ElseIf Byte$[1]=:11 Then Print "6811 Object file"

    LoadRecordCount=0 \ ! Number of actual load records encountered

Repeat
    ! Process load records in file
    Read #1,LoadRecordHeader$
    If Eof(1) Then Error 1001
    If LoadRecordHeader$[1]=0
    Then
        SkipCount=LoadRecordHeader$[2]*256+LoadRecordHeader$[3]
        If DebugFlag
        Then
            Print "Skip Record @ ";hex$(CurrentFilePosition-3);...
&                 " of length ";SkipCount
        Fi
        Position #1@CurrentFilePosition+SkipCount
    ElseIf LoadRecordHeader$[1]=2
    Then
        Gosub ProcessLoadRecord
    ElseIf LoadRecordHeader$[1]=3
    Then
        Gosub ProcessLoadRecord
        If DebugFlag
        Then Print "A total of";LoadRecordCount;"load records"
        Return \ ! All load records seen
    Else
Print LoadRecordHeader$[1];"@";CurrentFilePosition
        Error 1028 \ ! Bad load record (no, I'm not sympathetic)
    Fi
End

ProcessLoadRecord: ! Add load record information to load record list
    LoadRecordStartAddress=LoadRecordHeader$[2]*256+LoadRecordHeader$[3]
    Read #1,Length$
    If Eof(1) Then Error 1001
    LoadRecordLength=Length$[1]*256+Length$[2]
    If DebugFlag
    Then
 Print "file position=";CurrentFilePosition
        Print "Type";LoadRecordHeader$[1];...
&              "Load Record @ ";hex$(CurrentFilePosition-5);...
&              " Start Address ";hex$(LoadRecordStartAddress);...
&              " of length ";LoadRecordLength
    Fi
    ! Save load record to allow later examination/modification of file
    ! Shuffle records so low index records are last records of file
    ! This ensures that load records "later" in file are inspected
    ! first when looking for data to examine or change.
    If LoadRecordCount=Len(Lower)
    Then
        Print LoadRecordCount;"load record limit exceeded."
        Error 104
    Fi
    LoadRecordCount=LoadRecordCount+1 \ ! Count number of load records
    For i=LoadRecordCount to 1 step -1
       ! Shuffle collected load records up to make room.
       Lower(i)=Lower(i-1)
       Upper(i)=Upper(i-1)
       FilePosition(i)=FilePosition(i-1)
    Next i
    Lower(0)=LoadRecordStartAddress
    Upper(0)=Lower(0)+LoadRecordLength-1
    FilePosition(0)=CurrentFilePosition
    Return
END
