ORG &9000
.MC_START_PROGRAM EQU &BD16
.KL_ROM_WALK EQU &BCCB
.TXT_WIN_ENABLE EQU &BB66
.TXT_GET_CURSOR EQU &BB78
.TXT_SET_CURSOR EQU &BB75
.TXT_PLACE_CURSOR EQU &BB8A
.TXT_REMOVE_CURSOR EQU &BB8D
.SCR_SET_MODE EQU &BC0E
.KL_PROBE_ROM EQU &B915
.KM_WAIT_KEY EQU &BB18
.KM_RESET EQU &BB03
.CAS_IN_OPEN EQU &BC77
.CAS_IN_DIRECT EQU &BC83
.CAS_IN_CLOSE EQU &BC7A
.KL_ROM_SELECT EQU &B90F
.SCR_CHAR_POSITION EQU &BC1A
.CAS_CATALOG EQU &BC9B
.CAS_OUT_OPEN EQU &BC8C
.CAS_OUT_DIRECT EQU &BC98
.CAS_OUT_CLOSE EQU &BC8F
.CAS_IN_ABANDON EQU &BC7D
.CAS_OUT_ABANDON EQU &BC92
.BIOS_GET_STATUS EQU &C048
.TXT_OUTPUT EQU &BB5A
.TXT_WR_CHAR EQU &BB5D
.TXT_RD_CHAR EQU &BB60
.KM_TEST_KEY EQU &BB1E
.INC_HIX EQU &24DD
.DEC_HIX EQU &25DD
.LD_LIX_A EQU &6FDD
.LD_A_HIX EQU &7CDD
LD HL,START
LD C,&FF
JP MC_START_PROGRAM
.START
LD HL,&ABFF
LD DE,&40
CALL KL_ROM_WALK
LD A,2
CALL SCR_SET_MODE
DI ;Disable the interrupts so the Z80 doesn't try to execute an interrupt
LD HL,&3A ;while the jumpblock is being changed.
LD DE,INTERRUPT+2
LD BC,3
LDDR ;Used an LDDR so that HL points to the byte below &38.
INC HL
LD (HL),&C3 ;Alter the interrupt indirection so it jumps to the new routine.
INC HL
LD (HL),BANNER_MODE MOD 256
INC HL
LD (HL),BANNER_MODE/256
EI
LD C,0
CALL KL_PROBE_ROM ;Find out what kind of CPC this is by looking at the BASIC
LD A,H ;ROM version number.
CP 1
JR C,CPC464
JR Z,CPC664
.CPC6128
LD HL,&AE66
LD (REF_ADDR_BAS+2),HL
INC HL
INC HL
LD (REF_ADDR_BAS+6),HL
INC HL
INC HL
LD (REF_ADDR_BAS+10),HL
INC HL
INC HL
LD (REF_ADDR_BAS+14),HL
LD HL,&EA78
LD (REF2_ADDR_BAS+1),HL
JR CONTINUE
.CPC464
LD HL,&AE83
LD (REF_ADDR_BAS+2),HL
INC HL
INC HL
LD (REF_ADDR_BAS+6),HL
INC HL
INC HL
LD (REF_ADDR_BAS+10),HL
INC HL
INC HL
LD (REF_ADDR_BAS+14),HL
LD HL,&E9BD
LD (REF2_ADDR_BAS+1),HL
JR CONTINUE
.CPC664
LD HL,&AE83
LD (REF_ADDR_BAS+2),HL
INC HL
INC HL
LD (REF_ADDR_BAS+6),HL
INC HL
INC HL
LD (REF_ADDR_BAS+10),HL
INC HL
INC HL
LD (REF_ADDR_BAS+14),HL
LD HL,&EA7D
LD (REF2_ADDR_BAS+1),HL
.CONTINUE
LD BC,STARTMESS
LD DE,&C000 ;Address of where the banner will be printed.
CALL MESSAGES ;The characters will be like in mode 1 but printed in mode 2.
LD HL,2
LD DE,&5019
CALL TXT_WIN_ENABLE ;Set the window so that the banner stays at the top.
LD HL,MESS1
CALL TEXTLOOP
CALL KM_WAIT_KEY
LD A,&FF
LD (&BE78),A
.FIND_DISC
LD A,10
CALL TXT_OUTPUT
CALL TXT_GET_CURSOR
NEG ;This is like doing a SUB L,A.
ADD L ;Have to take the screen roll into account.
LD L,A
LD (CURSOR_POS),HL ;Store the position of the cursor for future use.
CALL DISC_DRIVE
LD (DRIVE_NUM+1),A
PUSH AF
LD B,A
INC B
LD HL,&C01B
.AUTO_DETECT ;Alters HL to point to the address for ùA or ùB.
INC L
INC L
INC L
DJNZ AUTO_DETECT
DW INC_HIX
DW INC_HIX
LD A,(IX-&73)
LD (BIOS_READ_SECTOR+2),A
LD C,A
XOR A
PUSH IX
CALL &1B
POP IX
POP AF
ADD A
ADD A
ADD A
ADD A
ADD A
ADD A
ADD &90
DW DEC_HIX
DW LD_LIX_A
LD HL,&2000
LD A,(IX+9)
LD BC,&8FF
.DIRLEN
AND A
RLA
JR NC,DIRCONT
INC C
.DIRCONT
DJNZ DIRLEN
LD A,C
.LOG2A
INC B
SRL A
JR C,LOG2A
LD A,B
ADD (IX+2)
SUB (IX+&14)
LD B,A
LD A,1
.TWO_PWR_A
ADD A
DJNZ TWO_PWR_A
SRL A
LD B,A
LD D,(IX+&D)
.DRIVE_NUM
LD E,0
LD C,(IX+&F)
.READ_DIRECTORY
PUSH HL
RST 3,BIOS_READ_SECTOR
JP NC,BAD_CAT
POP HL
PUSH DE
LD DE,&200
ADD HL,DE
POP DE
DJNZ INC_SECTOR
JR REARRANGE_FILES
.INC_SECTOR
INC C
LD A,(IX+&10)
ADD (IX+&F)
CP C
JR NZ,READ_DIRECTORY
INC D
LD C,(IX+&F)
JR READ_DIRECTORY
.REARRANGE_FILES
LD (HL),&E5
LD L,(IX+7) ;Find out how many directory entries there are.
LD H,(IX+8)
LD (NO_ENTRIES),HL
CALL BUBBLE_SORT
LD A,(&2000)
CP &E5
JR NZ,CAT
LD HL,MESS15
CALL TEXTLOOP
CALL KM_WAIT_KEY
JP FIND_DISC
.CAT
LD HL,&1FE0 ;This loop displays all of the filenames on the disc.
PUSH HL
.DISPLAY_CAT
POP HL
LD DE,&20
ADD HL,DE
LD A,(HL)
CP &E5
JP Z,SELECT_FILES ;If all of the directory entries have been displayed
PUSH HL ;jump to the bit to select the files
INC HL
LD DE,BUFFER
LD A," "
LD (DE),A
INC DE
LD BC,8
LDIR
LD A,"."
LD (DE),A
INC DE
LD BC,3
LDIR
INC HL
INC HL
INC HL
LD IY,0 ;Use IY as a counter for the size of the file
LD A,(HL)
RLCA
JR NC,FIND_SIZE
.MULTI_BLOCK
LD HL,16
EX DE,HL
ADD IY,DE ;Just the same as doing ADD IY,HL.
EX DE,HL
POP BC
PUSH BC
LD HL,44 ;Checks whether this entry has another part.
ADD HL,BC
OR A
JR Z,BLOCK_SIZE ;Jumps to BLOCK_SIZE if it doesn't.
LD HL,32
ADD HL,BC ;Points HL to the next entry for the filename.
LD A,&E5 ;Marks this second part as erased, so that there aren't two entries
LD (HL),A ;with the same name.
.BLOCK_SIZE
LD HL,47
ADD HL,BC ;HL points to the length of this entry.
LD A,(HL)
PUSH AF
PUSH DE
CALL NZ,BUBBLE_SORT ;This moves the erased file to the end, if it was erased.
POP DE
POP AF
RLCA
JR Z,FIND_SIZE
JR C,MULTI_BLOCK ;If this entry is 16K also, look for another entry.
.FIND_SIZE
RRCA
RRCA
RRCA
RRCA
LD H,A
AND &1F
LD L,A
LD A,H
AND &E0
JR Z,ROUND_DECIMALS
INC L
.ROUND_DECIMALS
LD H,0
EX DE,HL
ADD IY,DE
EX DE,HL
PUSH IY
POP HL
XOR A
ADD H
DAA
LD H,A
XOR A
SRL L ;Divides L by two so that when it is changed to decimal it isn't more
JR NC,A0 ;than 199.
INC A ;If the number is odd add one on to A.
.A0
PUSH AF
LD A,L ;Need to find L as a BCD number before adding.
AND &F0
RRCA
RRCA
RRCA
RRCA
LD B,A
INC B
XOR A
.MULTIPLY
ADD &16
DAA
DJNZ MULTIPLY
SUB &16
DAA
LD B,A
LD A,L
AND &F
LD C,A
XOR A
ADD C
DAA
ADD B
DAA
LD L,A
POP AF
ADD L ;Add the first half to A
DAA
JR NC,CHECK_CARRY
PUSH AF ;If the BCD number overflowed increment H.
LD A,H
AND A
INC A
DAA
LD H,A
POP AF
.CHECK_CARRY
ADD L ;Add the second half to A.
DAA
JR NC,PRINT_CHAR
PUSH AF ;If the BCD number overflowed increment H.
LD A,H
AND A
INC A
DAA
LD H,A
POP AF
.PRINT_CHAR
LD L,A ;Store the BCD number in L.
.NUMBER_OF_NUMBERS
XOR A
LD B,A ;This part finds out how many spaces to add so that the number is
LD A,4 ;right justified.
PUSH AF
.TEST_NIBBLES
LD A,L
AND &F ;Only look at the last nibble of the HL register pair.
JR Z,NO_NUM
LD C,B ;C holds the highest number of digits so far.
.NO_NUM
POP AF
RR H ;Rotate the HL register pair so that the next nibble is where the
RR L ;last one was.
RR H
RR L
RR H
RR L
RR H
RR L
PUSH AF
INC B
CP B ;If B is 4 finish the loop, otherwise increment B and repeat it.
JR NZ,TEST_NIBBLES
RR H ;Return HL to its previous value.
RR L
POP AF
SUB C
LD B,A
CALL ADD_SPACES
LD A,C ;C is one less than the number of digits.
SUB 1 ;Need to do SUB rather than DEC, because DEC doesn't set Carry.
JR Z,TWO_DIGITS ;If the number is less than 3 digits skip the next call.
JR C,TWO_DIGITS
SUB 2 ;If there is an odd number of digits Carry will be set.
LD B,H
CALL ADD_NUMBERS ;Routine to add the numbers to the string.
.TWO_DIGITS
LD B,L
CALL ADD_NUMBERS
LD A,"K"
LD (DE),A ;Print the K on the end to show the file size is in Kilobytes.
INC DE
LD B,1
CALL ADD_SPACES ;Add one space onto the end so that there is exactly 4 columns
XOR A ;across the screen.
LD (DE),A
LD HL,BUFFER
CALL TEXTLOOP ;Print the filename and size.
JP DISPLAY_CAT ;Repeat the loop.
.SELECT_FILES ;This routine allows the files to be selected.
LD HL,MESS2
CALL TEXTLOOP
LD H,A ;A is always 0 after the TEXTLOOP routine.
LD L,A
LD (CURRENT_FILE),A
LD (FILECOUNT),HL
CALL TXT_GET_CURSOR
NEG
ADD L
LD L,A
LD (LOCATE+1),HL ;Store the cursor position, for printing messages later.
LD HL,SAVENAME
LD (SELECT_POINTER),HL ;Point to the files that are stored in the buffer.
CALL TXT_GET_CURSOR ;Get the screen roll number in A.
LD HL,(CURSOR_POS)
ADD L ;Adjust L to allow for the screen scrolling.
LD L,A
CALL TXT_SET_CURSOR ;Move the text cursor to the top of the file list.
LD A,243
CALL TXT_OUTPUT ;Print the pointer (an arrow).
.MOVE_POINTER ;This loop checks whether any keys have been pressed.
LD HL,&3200
.PAUSE ;Wait for a little while so that the arrow doesn't move too quickly.
DEC HL
LD A,H
OR L
JR NZ,PAUSE
LD A,8
CALL KM_TEST_KEY ;Test the left arrow key.
CALL NZ,MOVE_LEFT
XOR A
CALL KM_TEST_KEY ;Test the up arrow key.
CALL NZ,MOVE_UP
LD A,1
CALL KM_TEST_KEY ;Test the right arrow key.
CALL NZ,MOVE_RIGHT
LD A,2
CALL KM_TEST_KEY ;Test the down arrow key.
CALL NZ,MOVE_DOWN
LD A,18
CALL KM_TEST_KEY ;Test the return key.
CALL NZ,LOAD_EACH_FILE
LD A,9
CALL KM_TEST_KEY ;Test the copy key.
CALL NZ,SELECT
JR MOVE_POINTER
.MOVE_LEFT
CALL TXT_GET_CURSOR
LD HL,(CURSOR_POS) ;Get the cursor position.
ADD L
LD L,A
PUSH HL
CALL TXT_SET_CURSOR
LD A," "
CALL TXT_OUTPUT ;This clears the cursor from its old position.
POP HL
LD A,H
SUB 20 ;Calculate the position for the next column to the left.
PUSH AF
LD A,(CURRENT_FILE)
LD C,A
JR NC,LEFT_COLUMN
POP AF
ADD 80 ;This adds 80 if the result is negative so that the screen wraps around.
PUSH AF
LD A,C ;This makes sure that the right file number is kept when the screen
ADD 4 ;wraps around.
LD C,A
.LEFT_COLUMN
POP AF
LD B,A
PUSH BC ;Check whether there is actually a filename at the new position.
PUSH HL
LD H,A
INC H
CALL TXT_UNWRITE
POP HL
POP BC
CP " "
LD A,B
JP Z,NO_CHANGE1
LD H,A
LD A,C
DEC A
LD (CURRENT_FILE),A ;This indicates which file the arrow is pointing at.
.NO_CHANGE1
PUSH HL
CALL TXT_SET_CURSOR
CALL TXT_GET_CURSOR
POP HL
NEG
ADD L
LD L,A
LD (CURSOR_POS),HL ;This stores the new position of the cursor.
LD A,243
CALL TXT_OUTPUT ;Print the arrow at its new position.
RET
.MOVE_RIGHT
CALL TXT_GET_CURSOR
LD HL,(CURSOR_POS) ;Get the cursor position.
ADD L
LD L,A
PUSH HL
CALL TXT_SET_CURSOR
LD A," "
CALL TXT_OUTPUT ;This clears the cursor from its old position.
POP HL
LD A,H
ADD 20
CP 81
PUSH AF
LD A,(CURRENT_FILE)
LD C,A
JR NZ,RIGHT_COLUMN
POP AF
SUB 80 ;Move the cursor to the extreme left if it moves off the screen.
PUSH AF
LD A,C
SUB 4
LD C,A
.RIGHT_COLUMN
POP AF
LD B,A
PUSH BC
PUSH HL
LD H,A
INC H
CALL TXT_UNWRITE
POP HL
POP BC
CP " "
LD A,B
JP Z,NO_CHANGE2
LD H,A
LD A,C
INC A
LD (CURRENT_FILE),A
.NO_CHANGE2
PUSH HL
CALL TXT_SET_CURSOR ;Move the text cursor to the new position.
CALL TXT_GET_CURSOR
POP HL
NEG
ADD L
LD L,A
LD (CURSOR_POS),HL ;This stores the new position of the cursor.
LD A,243
CALL TXT_OUTPUT
RET
.MOVE_UP
CALL TXT_GET_CURSOR
LD HL,(CURSOR_POS) ;Get the cursor position.
ADD L
LD L,A
PUSH HL
CALL TXT_SET_CURSOR
LD A," "
CALL TXT_OUTPUT ;These routines are fairly similar.
POP HL
LD A,L
DEC A
PUSH AF
PUSH HL
LD L,A
INC H
CALL TXT_UNWRITE
POP HL
POP BC
CP " "
LD A,B
JP Z,NO_CHANGE3
LD L,A
LD A,(CURRENT_FILE)
SUB 4
LD (CURRENT_FILE),A
.NO_CHANGE3
PUSH HL
CALL TXT_SET_CURSOR
CALL TXT_GET_CURSOR
POP HL
NEG
ADD L
LD L,A
LD (CURSOR_POS),HL
LD A,243
CALL TXT_OUTPUT
RET
.MOVE_DOWN
CALL TXT_GET_CURSOR
LD HL,(CURSOR_POS)
ADD L
LD L,A
PUSH HL
CALL TXT_SET_CURSOR
LD A," "
CALL TXT_OUTPUT
POP HL
LD A,L
INC A
PUSH AF
PUSH HL
LD L,A
INC H
CALL TXT_UNWRITE
POP HL
POP BC
CP " "
LD A,B
JP Z,NO_CHANGE4
LD L,A
LD A,(CURRENT_FILE)
ADD 4
LD (CURRENT_FILE),A
.NO_CHANGE4
PUSH HL
CALL TXT_SET_CURSOR
CALL TXT_GET_CURSOR
POP HL
NEG
ADD L
LD L,A
LD (CURSOR_POS),HL
LD A,243
CALL TXT_OUTPUT
RET
.SELECT
CALL TXT_GET_CURSOR
LD HL,(CURSOR_POS)
ADD L
LD L,A
INC L ;INC L because of the banner window at the top.
CALL SCR_CHAR_POSITION
.REVERSE_CHARACTERS ;Routine to highlight the selected filename.
LD C,8
.REVERSE_ROWS
LD B,18
PUSH HL
.REVERSE_BYTES
LD A,(HL)
CPL ;Reverse all of the bits in the byte.
LD (HL),A
INC HL
LD D,A
DJNZ REVERSE_BYTES
POP HL
LD A,H
ADD 8 ;HL holds the address of the next row (&800 greater than the last).
LD H,A
DEC C
JR NZ,REVERSE_ROWS
PUSH DE ;D holds the last byte that was inverted i.e. the bottom right byte.
LD HL,(FILECOUNT)
INC HL ;Add one to the number of files selected.
LD DE,(SELECT_POINTER)
LD A,(CURRENT_FILE)
LD (DE),A
INC DE
POP AF ;Puts the value of the last byte which was inverted in A.
OR A
JR NZ,ADD_FILE ;If the last byte inverted was changed to a 0 then the file must
DEC HL ;have been deselected. This means that the counter will have to be
DEC HL ;decremented and the pointer decremented also.
PUSH HL
PUSH DE
INC HL
LD B,H ;This bit takes out the filename number that has been deselected.
LD C,L
LD HL,SAVENAME
LD A,(CURRENT_FILE) ;Search for the file number to be removed.
CPIR
JP PO,ONE_BYTE ;If BC is zero don't bother moving any bytes.
LD D,H
LD E,L
DEC DE ;The bytes are moved back one byte.
LDIR ;Move other numbers up to close the gap.
.ONE_BYTE
POP DE
POP HL
DEC DE ;Decrement the file pointer.
DEC DE
.ADD_FILE
LD (FILECOUNT),HL
LD (SELECT_POINTER),DE
RET
.LOAD_EACH_FILE
LD HL,(FILECOUNT)
LD A,H
OR L
RET Z
POP HL
CALL TXT_GET_CURSOR
.LOCATE
LD HL,0
ADD L
LD L,A
CALL TXT_SET_CURSOR
CALL KM_RESET ;Clear the key buffer.
LD HL,MESS14
CALL TEXTLOOP
LD HL,&1000
CALL INPUT_ROUTINE+3
LD C,B
LD B,0
LD (&F00),BC
LD HL,ROM_HEADER
LD DE,&4000
LD BC,NAME_TABLE-ROM_HEADER
LDIR
EX DE,HL ;HL now points to the beginning of the Jumpblock in the ROM image.
LD BC,(FILECOUNT) ;Have to leave some room for the Jumpblock.
LD DE,3
.X3BC ;Multiply BC by 3 and add it on to HL.
ADD HL,DE
DEC BC
LD A,B
OR C
JR NZ,X3BC
LD DE,&8000
PUSH HL
ADD HL,DE
LD (NAMETABLE_ADDR-ROM_HEADER+&4000),HL ;Adds the address of the name table to
POP DE ;the ROM image.
LD HL,NAME_TABLE ;Add the name of the initialisation command to the name table.
LD BC,COMMAND_NAME-NAME_TABLE
LDIR
LD BC,(FILECOUNT)
LD B,C ;Use B as a counter of how many times to go through the loop.
LD HL,SAVENAME ;Points HL to the first filename number.
.ASSIGN_NAMES ;This loop assigns a name to each file.
PUSH BC
PUSH DE
PUSH HL
.INPUT_COMMAND ;If the command name was invalid this bit is repeated.
LD HL,MESS4
CALL TEXTLOOP
POP HL
PUSH HL
CALL CREATE_FILENAME ;Finds the filename from the number pointed to by HL.
EX DE,HL
LD (HL),"?"
INC HL
LD (HL)," "
INC HL
LD (HL),13
INC HL
LD (HL),10
INC HL
LD (HL),0
LD HL,BUFFER
CALL TEXTLOOP
CALL INPUT_ROUTINE ;Gets input from the keyboard.
LD C,B
LD B,0
LD (COMMAND_LEN),BC
LD A,C
CP 17
JR NC,INPUT_COMMAND
LD HL,BUFFER ;The command name is stored at BUFFER.
.CHECK_COMMAND
LD A,(HL)
RES 7,A ;Reset bit 7 so BASIC doesn't think it is the end of the name.
CP "."
JR Z,MAKEUP
CP "0"
JR C,INPUT_COMMAND
CP ":"
JR C,MAKEUP
CP "A"
JR C,INPUT_COMMAND
CP "["
JR C,MAKEUP
CP "a"
JR C,INPUT_COMMAND
CP "é"
JR NC,INPUT_COMMAND
.MAKEUP
SUB 97
JR C,UPPER
SUB 26
JR NC,UPPER
LD A,(HL)
SUB 32
LD (HL),A
.UPPER
INC HL
DEC C
JR NZ,CHECK_COMMAND
DEC HL
SET 7,(HL) ;Set bit 7 of the last character of the command.
POP HL
POP DE
PUSH HL
LD HL,BUFFER
LD BC,(COMMAND_LEN)
LDIR
POP HL
INC HL
POP BC
DJNZ ASSIGN_NAMES
XOR A
LD (DE),A
INC DE
LD HL,INITMESS-ROMINIT+&8000 ;Find out the address of the initialisation
ADD HL,DE ;message
LD (MESS_ADDRESS+1),HL
LD HL,INITMESS-ROMINIT+1
ADD HL,DE
PUSH DE
LD DE,&1000
EX DE,HL
LD BC,(&F00)
LDIR
EX DE,HL
LD (HL),10
INC HL
LD (HL),13
INC HL
LD (HL),10
INC HL
LD (HL),0
INC HL
POP DE
PUSH HL
LD BC,INITMESS-ROMINIT+1
LD HL,&8000
ADD HL,DE
PUSH DE
EX DE,HL
LD HL,INITIALISE_ROM-ROM_HEADER+&4001
LD (HL),E ;Store the address of the initialisation routine, so that the routine
INC HL ;is jumped to on initialisation.
LD (HL),D
INC HL
LD (COMMAND_LEN),HL ;Store the address of where all the other JP commands go.
LD HL,ROMINIT ;Copy the initialisation code to the ROM image.
POP DE
LDIR
POP DE
LD HL,&8000
AND A
SBC HL,DE ;Find out how much space there is for programs and store in HL.
LD (RAM_REMAINING),HL
LD HL,SAVENAME
LD BC,(FILECOUNT)
LD B,C
LD (HEADER_POINTER),DE
.LOAD_FILES ;This loop loads each file and fills in the addresses to jump to.
PUSH BC
PUSH HL
CALL CREATE_FILENAME
.LOAD
LD B,12
LD DE,&8000
LD HL,BUFFER
CALL CAS_IN_OPEN
JP NC,RETRY
CALL FREE_BYTES
JP NC,TOO_LONG ;If the file is too big carry is reset.
LD (RAM_REMAINING),IX
PUSH BC ;Store the length of the program.
CP 2 ;This part copies the loader code for each program to its position in the
JR NC,BIN ;ROM image.
LD HL,(HEADER_POINTER)
LD DE,BASRUN-BASIC+&8000
PUSH HL
ADD HL,DE
LD (BASRUN_PROG+1),HL
POP HL
PUSH HL
LD DE,BASEND-BASIC+&8000
ADD HL,DE
LD (BASIC+1),HL
LD (BAS_LENGTH+1),BC
POP DE
PUSH DE
LD HL,&8000
ADD HL,DE
EX DE,HL
LD HL,(COMMAND_LEN) ;Get the address of the next space in the Jumpblock.
LD (HL),&C3 ;The opcode for the JP instruction.
INC HL
LD (HL),E ;Put the execution address of the loader code in the Jumpblock.
INC HL
LD (HL),D
INC HL
LD (COMMAND_LEN),HL ;Store the address of the next space in the Jumpblock.
POP DE
LD HL,BASIC
LD BC,BASEND-BASIC
LDIR
JR LOAD_FILE
.BIN
PUSH DE
LD DE,26
ADD HL,DE
LD E,(HL)
INC HL
LD D,(HL)
LD (BIN_EXEC+1),DE
LD HL,(HEADER_POINTER)
LD DE,BINEND-BINARY+&8000
ADD HL,DE
LD (BIN_ADDRESS+1),HL
POP DE
LD (BIN_DEST+1),DE
LD (BIN_LENGTH+1),BC
LD DE,(HEADER_POINTER)
LD HL,&8000
ADD HL,DE
PUSH DE
EX DE,HL
LD HL,(COMMAND_LEN)
LD (HL),&C3
INC HL
LD (HL),E
INC HL
LD (HL),D
INC HL
LD (COMMAND_LEN),HL
POP DE
LD HL,BINARY
LD BC,BINEND-BINARY
LDIR
.LOAD_FILE
LD H,D
LD L,E
POP BC
ADD HL,BC ;Find where the next program will be stored.
LD (HEADER_POINTER),HL ;Store the position of the next program.
EX DE,HL ;Get the address of where the program is to be loaded from DE.
.IMAGE_LOAD
CALL CAS_IN_DIRECT ;Load the program.
CALL CAS_IN_CLOSE
POP HL
POP BC
INC HL ;Move on to the next program to load.
DEC B
JP NZ,LOAD_FILES
.RETRY_ROM
LD C,16 ;Finds the number of the RAMROM.
.FIND_RAMROM
DEC C
JP Z,NO_RAMROM ;Assumes that the BASIC ROM will be at number 0.
PUSH BC
CALL KL_ROM_SELECT
POP BC
LD A,&FE
LD (&C000),A
LD A,(&C000) ;If it is a RAMROM the byte that was written will be returned.
CP &FE ;If it isn't a RAMROM the byte returned will be 0,1,2, &80, or &FF.
JR NZ,FIND_RAMROM ;If it isn't a RAMROM loop again.
LD A,2 ;Reset the screen offset and clear the cursor to the top.
CALL SCR_SET_MODE
XOR A
LD (DI),A ;Disables the normal firmware interrupts
LD BC,&BC06 ;Restrict the vertical size of the screen to 3 lines.
OUT (C),C
LD BC,&BD03
OUT (C),C
LD HL,&40 ;Clear the area from &40 to &3FFF, so the screen appears to be blank.
LD DE,&41
LD BC,&3FBF
LD (HL),0
LDIR
LD BC,&BC0C ;Screen moved to &50 so there aren't any lines across the screen.
OUT (C),C ;Select CRTC register 12.
INC B
OUT (C),A ;Set the screen base to &00.
DEC B
INC C
OUT (C),C ;Select CRTC register 13.
LD C,&50
INC B
OUT (C),C ;Set the offset to &50, so the Lower Jumpblock isn't overwritten.
INC A
LD (MODE_NUM),A
LD BC,STARTMESS
LD DE,&A0
CALL MESSAGES ;Reprints the banner at the new screen location.
LD HL,&4000 ;This bit copies the ROM image to the RAMROM.
LD BC,&4000
LD DE,&C000
LDIR
LD A,2
LD (MODE_NUM),A
LD BC,MESS65
LD DE,&140 ;Screen address of the 3rd line down.
CALL MESSAGES ;This is a different routine to TEXTLOOP because the firmware
LD BC,NUMBERS ;can't be used, otherwise the RAMROM will be corrupted.
PUSH DE
CALL MESSAGES ;Prints out how long the user has to switch the RAMROM to read
LD BC,MESS675 ;only.
CALL MESSAGES
.COUNTDOWN
LD BC,NUMBERS+1 ;Points to the ASCII representation of the countdown numbers.
LD A,(NUMBER) ;Finds what the current number is and changes it to ASCII.
PUSH AF
AND &F
ADD 48
LD (BC),A
POP AF
DEC BC
AND &F0
RRCA ;Rotate the top nibble of the A register to the right.
RRCA
RRCA
RRCA
ADD 48
LD (BC),A
LD BC,NUMBERS
POP DE
PUSH DE
CALL MESSAGES
.LOOP_TO_COUNTDOWN
JP COUNTDOWN ;This jump will be changed to JP CNT when the time has expired.
.CNT
POP DE
LD HL,&C000
LD DE,&C001
LD BC,&3FFF
LD (HL),0 ;Clear the screen.
LDIR
LD A,48 ;Set the normal screen area.
LD BC,&BC0C
OUT (C),C
INC B
OUT (C),A
DEC B
INC C
OUT (C),C
INC B
XOR A
OUT (C),A ;Reset the screen offset.
LD BC,&BC06 ;Change the vertical screen size back to 25 lines.
OUT (C),C
LD BC,&BD19
OUT (C),C
LD A,1
LD (MODE_NUM),A
LD BC,STARTMESS
LD DE,&C000
CALL MESSAGES ;Print the banner in the normal screen area.
LD A,&FF
LD (DI),A ;Enable the normal firmware interrupts.
LD HL,2
LD DE,&5019
CALL TXT_WIN_ENABLE ;Set the window so that the banner stays at the top.
LD HL,MESS7 ;Ask whether to save the ROM image.
CALL TEXTLOOP
CALL KM_WAIT_KEY
CP "Y"
JR Z,SAVE
CP "y"
JR Z,SAVE
RST 0
.SAVE
CALL DISC_DRIVE
.CAT_AGAIN
LD DE,&8000 ;Area used as a buffer for the firmware disc routines.
CALL CAS_CATALOG
AND &C0
JP NZ,SAVE_CAT
.FILENAME
LD HL,MESS8
CALL TEXTLOOP
CALL INPUT_ROUTINE
LD (COMMAND_LEN),BC
.SAVING
LD BC,(COMMAND_LEN)
PUSH BC
LD HL,BUFFER
LD DE,SAVENAME
LD C,B
LD B,0
LDIR
LD HL,IMAGE_INIT ;Copy the loader code to just before the ROM image.
LD DE,&4000-IMAGE_INIT_END+IMAGE_INIT
LD BC,IMAGE_INIT_END-IMAGE_INIT
LDIR
POP BC
LD HL,SAVENAME
LD DE,&8000
CALL CAS_OUT_OPEN
JR NC,SAVE_RETRY
LD HL,&4000-IMAGE_INIT_END+IMAGE_INIT
LD DE,&4000+IMAGE_INIT_END-IMAGE_INIT
LD B,H ;The execution address is the same as the load address.
LD C,L
LD A,2
CALL CAS_OUT_DIRECT
CALL CAS_OUT_CLOSE
JP NC, SAVE_RETRY
LD HL,MESS12
CALL TEXTLOOP
CALL KM_WAIT_KEY
RST 0
.RETRY
CALL CAS_IN_ABANDON
LD HL,MESS3
CALL TEXTLOOP
CALL KM_WAIT_KEY
CP "R"
JP Z,LOAD
CP "r"
JP Z,LOAD
JP FIND_DISC
.TOO_LONG
CALL CAS_IN_ABANDON
LD HL,MESS5
CALL TEXTLOOP
CALL KM_WAIT_KEY
POP HL
POP HL
JP FIND_DISC
.BAD_CAT ;If an error occured, while cataloguing, go back to FIND_DISC.
LD HL,MESS6
CALL TEXTLOOP
CALL KM_WAIT_KEY
JP FIND_DISC
.SAVE_CAT
LD HL,MESS11
CALL TEXTLOOP
CALL KM_WAIT_KEY
CP "R"
JP Z,CAT_AGAIN
CP "r"
JP Z,CAT_AGAIN
JP SAVE
.SAVE_RETRY
CALL CAS_OUT_ABANDON
LD HL,MESS9
CALL TEXTLOOP
CALL KM_WAIT_KEY
CP "R"
JP Z,SAVING ;If R is pressed try saving again.
CP "r"
JP Z,SAVING
CP "F"
JP Z,FILENAME ;If F is pressed go to the filename entry routine.
CP "f"
JP Z,FILENAME
JP SAVE
.ASCII ;If a file is ASCII the appropriate message is displayed.
POP HL
POP HL
POP HL
POP HL
CALL CAS_IN_ABANDON
LD HL,MESS10
CALL TEXTLOOP
CALL KM_WAIT_KEY
JP FIND_DISC ;Always goes back to here no matter what is pressed.
.DISC_DRIVE ;Tests whether there is a disc in either drive.
LD IX,(&BE7D) ;Loads IX with the start of the operating area.
DW INC_HIX
DW INC_HIX
XOR A ;Tests drive 1 first.
.TEST_DRIVE
XOR 1 ;Toggles between 0 and 1.
PUSH AF
LD HL,BIOS_GET_STATUS ;BIOS command, which returns the status of the specified
LD C,(IX-&73) ;disc drive. C holds the ROM number of the operating system.
CALL &1B
LD B,A
POP AF
BIT 5,B ;If bit 5 is set a disc is fitted and ready.
JR Z,TEST_DRIVE ;Otherwise keep looping until a disc drive has a disc in it.
DW DEC_HIX ;If a disc was found A holds the drive number.
DW DEC_HIX
LD (IX),A
RET
.TEXTLOOP ;Simple routine to display text.
LD A,(HL)
OR A
RET Z
RES 7,A
CALL TXT_OUTPUT
INC HL
JR TEXTLOOP
.CREATE_FILENAME ;Finds the filename that the number corresponds to.
LD B,(HL)
INC B ;Increments B so that the loop executes once when B is 0.
LD HL,&1FE1 ;Subtract 32 from &2001 because B was incremented.
LD DE,&20
.POINT_TO_FILE ;Multiplies B by 32 and adds it to &1FE1.
ADD HL,DE
DJNZ POINT_TO_FILE
LD DE,BUFFER ;Copies the filename to the buffer so it can be printed.
LD BC,8
LDIR
LD A,"."
LD (DE),A
INC DE
LD BC,3
LDIR
RET
.INPUT_ROUTINE
LD HL,BUFFER
LD B,0
CALL TXT_PLACE_CURSOR
.INPUT
CALL KM_WAIT_KEY
CP 13
JR NZ,DEL
LD A,B
OR A
JR Z,INPUT
CALL TXT_REMOVE_CURSOR
LD A,13
CALL TXT_OUTPUT
LD A,10
JP TXT_OUTPUT
.DEL
CP 127
JR NZ,ADD_TO_BUFFER
LD A,B
OR A
JR Z,INPUT
CALL TXT_REMOVE_CURSOR
XOR A
DEC HL
LD (HL),A
DEC B
LD A,8
CALL TXT_OUTPUT
LD A,16
CALL TXT_OUTPUT
CALL TXT_PLACE_CURSOR
JR INPUT
.ADD_TO_BUFFER
INC B
JR NZ,CONT
DEC B
JR INPUT
.CONT
LD (HL),A
PUSH AF
CALL TXT_REMOVE_CURSOR
POP AF
PUSH HL
PUSH BC
CALL TXT_WR_CHAR
CALL TXT_PLACE_CURSOR
POP BC
POP HL
INC HL
JR INPUT
.MESSAGES
DI ;The interrupts have to be disabled for this routine. The countdown counts
PUSH BC ;in 299/300ths of a second, to allow for this routine.
LD BC,&7F8A
OUT (C),C ;Enable the lower ROM.
POP BC
LD A,(BC)
.SPECIAL_MESS
PUSH BC
PUSH DE
LD L,A
LD H,7
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD B,8
.X8_ROWS
PUSH BC
LD A,(MODE_NUM)
RRCA ;If it is one Carry is set.
LD A,(HL)
PUSH DE
JR NC,MODE2
CPL
PUSH HL
PUSH AF
AND &F0 ;Look at the top nibble.
LD B,4
.EXPAND_TOP
RLA
RL H
SLA H
DJNZ EXPAND_TOP
LD A,H
RRCA ;Synchronise the 0s with the 1s in the two bytes.
OR H ;Add the two bytes together.
LD (DE),A ;Store the new byte.
POP AF
AND &F
LD B,4
.EXPAND_BOTTOM
RRA
RR L
SRL L
DJNZ EXPAND_BOTTOM
INC DE
LD A,L
RLCA
OR L
POP HL
.MODE2
LD (DE),A
POP DE
INC HL
LD A,D
ADD 8
LD D,A
POP BC
DJNZ X8_ROWS
POP DE
INC DE
LD A,(MODE_NUM)
RRCA
JR NC,MODE_2
INC DE ;If it is in mode 1 then move two bytes to the right.
.MODE_2
POP BC
INC BC
LD A,(BC)
OR A
JR NZ,SPECIAL_MESS
LD BC,&7F8E ;Turn the lower ROM off and select mode 2.
OUT (C),C
EI
RET
.ADD_NUMBERS
JR C,NEXT_NIBBLE
LD A,B
AND &F0
RRCA
RRCA
RRCA
RRCA
ADD 48
LD (DE),A
INC DE
.NEXT_NIBBLE
LD A,B
AND &F
ADD 48
LD (DE),A
INC DE
RET
.TXT_UNWRITE ;Routine to read a character from the screen.
PUSH HL ;Store where the character to be read is located.
CALL TXT_GET_CURSOR
EX (SP),HL ;Stores where the cursor was and gets where it is to be moved to.
CALL TXT_SET_CURSOR ;Move the cursor to where the character is to be read from.
CALL TXT_RD_CHAR ;Get the character from the screen.
POP HL
PUSH AF
CALL TXT_SET_CURSOR
POP AF
RET
.ADD_SPACES
LD A," "
LD (DE),A
INC DE
DJNZ ADD_SPACES
RET
.FREE_BYTES
PUSH DE
PUSH AF
LD IX,&AA34
LD A,(IX+2)
CP &C3
JR NZ,NOT_ROMIMAGE
LD A,(IX+1)
AND &C0
JR Z,NOT_ROMIMAGE
LD A,(IX+4)
AND &C0
JR Z,NOT_ROMIMAGE
LD A,(IX-4)
CP 3
JR C,IS_IMAGE
CP &80
JR NZ,NOT_ROMIMAGE
.IS_IMAGE
POP AF
POP DE
LD HL,(FILECOUNT)
DEC HL
LD A,H
OR L
RET NZ
POP AF
LD H,&40
JP IMAGE_LOAD
.NOT_ROMIMAGE
POP AF
PUSH AF
LD IX,(RAM_REMAINING)
CP 2
JR C,BASIC_LOADER
CP 16
JR C,BINARY_LOADER
POP HL
JP ASCII
.BASIC_LOADER
LD DE,&10000-BASEND+BASIC
ADD IX,DE
JR ADD_PROG
.BINARY_LOADER
LD DE,&10000-BINEND+BINARY
ADD IX,DE
.ADD_PROG
LD A,B ;Change BC to -BC and store it in DE.
CPL ;To make a number negative all the bits are complemented and 1 is added.
LD D,A
LD A,C
CPL
LD E,A
INC DE
ADD IX,DE ;This is the equivalent of doing a SBC IX,BC.
POP AF
LD D,A
DW LD_A_HIX
CP &40
LD A,D
POP DE
RET
.BANNER_MODE
DI
PUSH AF
PUSH HL
LD A,(DI) ;Check the state of the DI flag. When it is 0 the countdown starts.
OR A
JR NZ,FIRMWARE_INTERRUPT
LD HL,TIMER ;TIMER holds the 1/300ths of a second counter.
DEC (HL)
JR NZ,TIMER_END ;If this byte has reached 0, decrement the next byte.
INC HL
DEC (HL) ;If it isn't 0 return from routine.
JR NZ,TIMER_END
LD HL,&22B ;This number happens to be 300+&99 in hexadecimal. This is so that,
LD (TIMER),HL ;when both bytes are 0, 299 1/300ths of a second have passed.
LD A,(NUMBER) ;When a 299/300ths of a second have passed decrement the counter.
DEC A
DAA ;Change the number to BCD.
JR NZ,STORE_SECS ;If the counter isn't zero then store the new number.
LD HL,CNT ;If the counter has reached zero the number print routine is ended.
LD (LOOP_TO_COUNTDOWN+1),HL ;Alter the program so that the loop terminates.
.STORE_SECS
LD (NUMBER),A ;Store the new number of seconds.
.TIMER_END
POP HL
POP AF
EI
RET ;Return from the interrupt.
.FIRMWARE_INTERRUPT
POP HL
POP AF
EI
.INTERRUPT DS 3 ;Will hold the address of the interrupt handling routine.
.NO_RAMROM
LD HL,MESS13
CALL TEXTLOOP
CALL KM_WAIT_KEY
JP RETRY_ROM
.BUBBLE_SORT
LD HL,(NO_ENTRIES)
LD DE,&2000
.START_SORTING
PUSH HL
PUSH DE
LD A,(DE)
CP &E5
LD HL,32
ADD HL,DE
JR NZ,NEXT_CHAR
CP (HL)
JR Z,SKIP_IT
JR SWAP
.NEXT_CHAR
LD A,&E5
CP (HL)
JR Z,SKIP_IT
INC DE
LD A,(DE)
LD HL,32
ADD HL,DE
CP (HL)
JR C,SKIP_IT
JR Z,NEXT_CHAR
.SWAP
LD B,32
POP DE
PUSH DE
LD HL,32
ADD HL,DE
.SWAP_LOOP
LD C,(HL)
LD A,(DE)
EX DE,HL ;Swap the contents of the HL and DE registers.
LD (HL),C
LD (DE),A
EX DE,HL
INC DE
INC HL
DJNZ SWAP_LOOP
XOR A
LD (FLAG),A
.SKIP_IT
POP DE
LD HL,32
ADD HL,DE
EX DE,HL
POP HL
DEC HL
LD A,H
OR L
JR NZ,START_SORTING
LD A,(FLAG)
OR A
LD A,&FF
LD (FLAG),A
JR Z,BUBBLE_SORT
RET
.ROM_HEADER
DB 1,1,0,0
.NAMETABLE_ADDR
DW 0 ;When the name table is constructed this will store its address.
.INITIALISE_ROM
JP ROMINIT ;All ROMs are initialised at this address.
.NAME_TABLE DB "START U","P"+&80;Name of the initialisation routine.
.COMMAND_NAME ;Beginning of the command name table.
.ROMINIT
PUSH HL
.MESS_ADDRESS ;This will store the address of the start up message.
LD HL,0
.INITTEXT
LD A,(HL)
OR A
JR Z,END_INIT
CALL TXT_OUTPUT
INC HL
JR INITTEXT
.END_INIT
POP HL
SCF
RET
.INITMESS DB " "
.BASIC
LD HL,0 ;This will hold the address of the BASIC program when it is copied.
LD DE,&170
.BAS_LENGTH
LD BC,0 ;This will hold the length of the BASIC program when it is copied.
LDIR
.REF_ADDR_BAS
LD (&AE66),DE
LD (&AE68),DE
LD (&AE6A),DE
LD (&AE6C),DE
.BASRUN_PROG
LD HL,0 ;This will hold the address of the BASRUN code in the ROM.
LD DE,&40 ;The code will be copied to here before it is executed.
LD BC,14
LDIR
JP &40
.BASRUN ;This code runs the BASIC program and must be copied into RAM before it
LD C,0 ;is executed.
CALL KL_ROM_SELECT
LD HL,&B0
LD (HL),0
.REF2_ADDR_BAS
JP &EA78
.BASEND
.BINARY ;Beginning of the binary loader code.
PUSH HL ;Make sure all of the registers are passed to the destination routine
PUSH DE ;unaltered.
PUSH BC
.BIN_ADDRESS
LD HL,0 ;This will hold the address in the ROM of the binary program.
.BIN_DEST
LD DE,0 ;This will hold the address of where the program would normal load to.
.BIN_LENGTH
LD BC,0 ;This will hold the length of the binary program.
LDIR
POP BC
POP DE
POP HL
.BIN_EXEC
JP 0 ;This will jump to the execution address of the binary program.
.BINEND
.IMAGE_INIT
LD A,2
CALL SCR_SET_MODE
LD C,16 ;Finds the number of the RAMROM.
.FIND_RAMROM2
DEC C
PUSH BC
CALL KL_ROM_SELECT
POP BC
LD A,&FE
LD (&C000),A
LD A,(&C000)
CP &FE
JR NZ,FIND_RAMROM2
LD HL,&4000-IMAGE_INIT_END+IMAGE_INIT+IMAG_MESS-IMAGE_INIT ;Points to message.
.PRINT_MESS
LD A,(HL)
OR A
JR Z,PRINT_END
CALL TXT_OUTPUT
INC HL
JR PRINT_MESS
.PRINT_END
CALL KM_WAIT_KEY
DI
LD HL,&4000 ;Copies the ROM image to the RAMROM
LD DE,&C000
LD BC,&4000
LDIR
LD B,10 ;Does nothing for a few seconds so that the RAMROM can be switched to
.DELAY1 ;read only.
LD HL,0
.DELAY2
DEC HL
LD A,H
OR L
JR NZ,DELAY2
DJNZ DELAY1
RST 0
.IMAG_MESS DB "Switch the RAMROM to read only when the lines across the screen"
DB " have stopped.",10,13,"Press a key to load the RAMROM.",0
.IMAGE_INIT_END
.COMMAND_LEN DW 0 ;Stores the length of the RSX commands as they are entered.
.HEADER_POINTER DW 0 ;Keeps track of where the next program will go in the ROM.
.FILECOUNT DW 0
.NO_ENTRIES DW 0
.NUMBER DB &30
.NUMBERS DB "30"
DB 0 ;Marks the end of the numbers string.
.CURSOR_POS DW 0
.CURRENT_FILE DB 0
.SELECT_POINTER DW 0
.RAM_REMAINING DW &4000
.MODE_NUM DB 1
.TIMER DW &22B
.DI DB &FF
.FLAG DB &FF
.BIOS_READ_SECTOR DW &C03C
DB 7
.STARTMESS DB " RAMROM Loader ",&A4," James Hoskisson 1998 ",0
.MESS1 DB "Insert disc, with program for ROM image, in drive and press any
DB " key",10,13,0
.MESS2 DB 10,13,10,"Select the files using the cursor keys and COPY. Press"
DB " RETURN to continue.",10,10,13,0
.MESS3 DB "There was an error reading a file.",10,13
DB "Press R to retry, F to reselect the files, or D for a different"
DB " disc.",10,13,0
.MESS4 DB "What command do you want to use to access the program called ",0
.MESS5 DB "This selection is too big to fit in the ROM.",10,13
DB "Press F to reselect, or D for a different disc.",10,13,0
.MESS6 DB "There was an error cataloguing the disc.",10,13
DB "Press R to retry, or D for a different disc.",10,13,0
.MESS65 DB "You have ",0
.MESS675 DB " seconds to switch the RAMROM to read only.",0
.MESS7 DB "Do you want to save the ROM image before initialising?"
DB " (Y/N)",10,13,0
.MESS8 DB "Type in the filename: ",0
.MESS9 DB "There was an error while saving.",10,13
DB "Press R to retry, F to use a different filename, or D for a different"
DB " disc.",10,13,0
.MESS10 DB "One of the files is ASCII.",10,13
DB "Press F to reselect, or D for a different disc",10,13,0
.MESS11 DB "There was a disc error.",10,13
DB "Press R to retry, or D to catalogue a different disc",10,13,0
.MESS12 DB "ROM image saved successfully. Press any key to initialise the"
DB " RAMROM.",0
.MESS13 DB "Couldn't find a RAMROM. Make sure it is set to read/write.",10,13
DB "Press any key to retry.",10,13,0
.MESS14 DB "What is the start up message for your RAMROM?",10,13,0
.MESS15 DB "There aren't any files on this disc. Press a key to try another"
DB " disc.",10,13,0
.BUFFER DS 21
.SAVENAME DS 256
DB 0