Saving and loading memory is quite easy on the VIC-20 once you know how. However, it isn't obvious how to do this and therefore this article will present a few simple ways of doing it from BASIC and Assembler.
Saving/Loading from BASIC a Byte at a Time
A straight forward way to save and load memory from BASIC is to use the
GET# commands to write or read a byte at a time.
In the following file we are saving memory at 673-692 to 'FLASH.PRG' on device 8. If we wanted to save it to a .SEQ file then we would use
FLASH,S,W. We use
CHR$ to convert the byte to PETSCII which the
PRINT# command will then convert back to its byte representation when writing to the file. One interesting thing to note is that even though this is saving to a .PRG file it doesn't save the load address. If we want this included in the file then we need to start it with two bytes representing the LSB/MSB of the load address.
10 OPEN 8,8,1,"FLASH,W" 20 FOR I=673 TO 692 30 B=PEEK(I) 40 PRINT#8, CHR$(B); 50 NEXT I 60 CLOSE 8
The following program will load 'FLASH.PRG' into locations 673-692 from device 8.
10 OPEN 8,8,0,"FLASH" 20 FOR I=673 TO 692 30 GET#8, B$ 40 POKE I,ASC(B$) 50 NEXT I 60 CLOSE 8
If the file contains the origin address as the first two bytes then we can use that if desired. We have to be careful that the origin address isn't where our tokenized basic program is being stored. This allows us to load a memory block without corrupting a running BASIC program. In the following
ST is the status after certain I/O operations and is used here to detect the end of file by checking if it is equal to 64.
10 OPEN 8,8,0,"FLASH,R" 20 GET#8, O1$ 30 GET#8, O2$ 40 O=ASC(O1$)+256*ASC(O2$) 50 PRINT "ORIGIN: ";O 60 GET#8, B$ 70 IF ST<>0 AND ST<>64 GOTO 200 80 POKE O,ASC(B$) 90 O=O+1 100 IF ST<>64 GOTO 60 110 CLOSE 8 120 END 200 PRINT "ERROR STATUS: ";ST 210 CLOSE 8
Using the SAVE/LOAD Commands in BASIC
BASIC has built-in commands to save and load programs which we can use to save and load our block of memory.
We can use the
SAVE command in BASIC to save a block of memory by telling the Vic that the tokenized BASIC area is located at the same block of memory that we want to save.
The following example saves a block of memory at 673-692 to 'FLASH.PRG' on cassette (device 1). The start address is stored at 43/44 ($2B/2C) which indicates the start of tokenized BASIC in LSB/MSB order. The end address is stored at 45/56 ($2D/$2E) which indicates the end of tokenized BASIC in LSB/MSB order.
POKE 44,673/256:POKE 43,673-256*PEEK(44) POKE 46,692/256:POKE 45,692-256*PEEK(46) SAVE "FLASH",1,1
This works well but because it alters the BASIC start and end points it can't be used from within a BASIC program. We should reset the Vic or restore these locations to their original values once finished. It should also be noted that this method saves the load/origin address to the first two bytes of the file because the final
,1 is at the end of the
SAVE command. On cassette this prevents the relocate function of the
LOAD command but doesn't apply to disk.
If we have saved a block of memory that had a start address and is a self-contained runnable program then we can simply use:
SYS to BASIC/KERNEL ROM from BASIC
We can call BASIC/KERNEL ROM routines from BASIC using
SYS to save and load blocks of memory in a way which will work within a BASIC program. This method uses the BASIC ROM routine PARSL at 57809 ($E1D1) which sets the LOAD, VERIFY and SAVE parameters and therefore is designed to process the rest of the parameters after it is called. In this case
(N$),<device>. Locations 780, 781 and 782 are used to access the 6502 registers: A, X, Y.
The following method saves memory to the filename in
N$. end must be one past the address of the last byte to save. This routine uses the KERNEL ROM routine SAVE at 65496 ($FFD8) to perform the save. It uses register A (location 780) to set the starting address store to 193.
N$="FLASH" SYS57809(N$),<device>:POKE193,<start_lo>:POKE194,<start_hi> POKE780,193:POKE781,<end_lo>:POKE782,<end_hi>:SYS65496
If we wanted to save the memory at 673-692 ($02A1-$02B4) to 'FLASH.PRG' on device '8' and store the load address using
,1 it would look like the following. The final
,1 after the device isn't actually used by disk but we may want to use it if saving to cassette (device 1). We can also replace
(NS) with the quoted filename:
Loading is equally easy and in this case uses the KERNEL ROM routine LOAD at 65493 ($FFD5) to perform the load. If
,1 after device then it is forced to load at original address and pokes for
start_hi aren't needed. Register A (location 780) is used to specify a LOAD (0) operation rather than a VERIFY (1) operation.
Therefore if we want to load our 'FLASH.PRG' program, saved previously, we can use the following to load it from device 8 into its original memory location:
Assembly Routines for Saving and Loading
We can use some of the same routines used by the SYS calls above to load and save memory from assembly. We first have to set up the device, file number and secondary address using the SETLFS routine at $FFBA (65466) and then use the SETNAM routine at $FFBD (65469) to set the filename. Finally we call either the LOAD or SAVE routine after setting the start and end addresses appropriately of the memory block to load or save.
The following routine would save block-blockend from memory to the file 'FLASH.PRG' on drive 8. The secondary address has no effect when being saved to disk but has the same effect as the BASIC
SAVE command if saving to cassette. It uses the same SAVE routine at $FFD8 (65496) used above.
; KERNEL/BASIC ROM Routines SAVE = $FFD8 SETLFS = $FFBA SETNAM = $FFBD LDA #$08 ; Logical file number LDX #$08 ; Device number LDY #$01 ; Secondary address JSR SETLFS ; Set above parameters LDA #$05 ; Length of file name LDX #<filename ; Low byte of file name location LDY #>filename ; High byte of file name location JSR SETNAM ; Set the name LDA #<block ; Low byte of start of memory block STA $C1 LDA #>block ; High byte of start of memory block STA $C2 LDA #$C1 ; Pointer to location of start address LDX #<(blockend+1) ; Low byte of (end of memory block + 1) LDY #>(blockend+1) ; High byte of (end of memory block + 1) JSR SAVE ; Perform save BCS error ; Jump to error handler if error RTS error ; Handle error in A RTS filename .asc "FLASH" ; The two byte memory block to save block .byt $05 blockend .byt $06
The following routine would load the file 'FLASH.PRG' from drive 8 to memory at location 673 ($02A1). It uses the same LOAD routine at $FFD5 (65493) used above.
block = $02A1 ; Load address ; KERNEL/BASIC ROM Routines LOAD = $FFD5 SETLFS = $FFBA SETNAM = $FFBD main LDA #$08 ; Logical file number LDX #$08 ; Device number LDY #$00 ; Secondary address ; $00 Load using specifed address ; $01 Load using original address from file JSR SETLFS ; Set above parameters LDA #$05 ; Length of file name LDX #<filename ; Low byte of file name location LDY #>filename ; High byte of file name location JSR SETNAM ; Set the name LDA #$00 ; Load = 0, Verify = 1 LDX #<block ; Low byte of start address LDY #>block ; High byte of start address JSR LOAD ; Perform load BCS error ; Jump to error handler if error RTS error ; Handle error in A RTS filename .asc "FLASH"
Machine Language Monitors
To finish this off the last method I want to mention is loading and saving via a machine language monitor such as VICMON.
We can save in VICMON using the S command. The following example saves the memory at 673-692 ($02A1-$02B4) to 'FLASH.PRG' on device '8'. Note that we are using
02B5 as the end address because the end address for this command has to be one past the last byte that we want to save. The .PRG file will be stored with its origin address.
To load a file we use the L command which takes an optional device number. If the device number isn't specified then it defaults to 01 - the cassette. In the following example we are loading 'FLASH.PRG' from device 8 into the same memory location that it was saved from.
The following video shows these methods of saving and loading memory in action.