BASIC 2.0 on the C64 is well, pretty basic.
Surprisingly, it doesn't support any of the graphics or sound hardware in the C64 apart from Kernal terminal-level I/O, and it does not have a binary save or relocatable binary load instruction.
The C64 Kernal has a binary save call, and it's possible to call the Kernal directly from BASIC using
SYS X will load the CPU registers from locations 780 (.A), 781 (.X), 782 (.Y), and 783 (.P) before jumping to X. After the call, CPU registers are copied back to those locations. In this way you can call any machine language routine, including Kernal routines, and even check the return status.
The Kernal SAVE routine requires you first call the Kernal SETNAM and SETLFS routines to setup the parameters for the file to save.
SETNAM is complicated because it wants the following parameters: the length of the filename in .A, and the address of it in .X (low byte) and .Y (high byte). There is no VARPTR type function to get the address of a string variable in C64 BASIC 2.0, but you can use the tape buffer at 828 to store the filename.
SETLFS is where the device you want to save to is specificed, and the "logical file number" but that's only needed for OPEN, not SAVE>
SAVE then requires the start and end address communicated in a specific way - the start through a zero-page pointer and the end in .X and .Y registers.
So if you wanted to do it the "right way" without the "ugly" hack of calling BASIC ROM from BASIC, you could do this. I have not tested this so double check everything before using it.
1000 REM BSAVE
1001 REM ZN$: FILENAME, ZD: DEVICE#, ZS: START ADDR, ZE: END ADDR
1002 REM USES UP TO 16 BYTES OF TAPE BUFFER AT 828 TO STORE FILENAME
1003 REM USES ZERO PAGE 251 AND 252 FOR SAVE KERNAL ROUTINE
1008 ZR=-1 : REM -1 INDICATES ERROR
1010 IF ZN$="" THEN RETURN : REM NO NULL FILENAMES
1011 IF LEN(ZN$)>16 THEN RETURN : REM FILENAME MAX LEN=16
1012 IF (ZS>65535)OR(ZS<0) THEN RETURN : REM ZS OUT OF RANGE
1013 IF (ZE>65535)OR(ZE<0) THEN RETURN : REM ZE OUT OF RANGE
1014 IF SGN(ZE-ZS)=-1 THEN RETURN : REM SAVE OFF OF END OF MEMORY
1020 REM POKE FILENAME STRING INTO TAPE BUFFER AT 828
1021 FOR ZI=1 TO LEN(ZN$):POKE 827+ZI,ASC(MID$(ZN$,ZI,1)):NEXT
1030 REM CALL SETNAM
1031 POKE 780,LEN(ZN$) : REM LENGTH OF NAME IN .A
1032 POKE 781,60 : REM LO-BYTE OF NAME ADDR IN .X
1033 POKE 782,3 : REM HI-BYTE IN .Y
1034 SYS 65469
1040 REM CALL SETLFS
1041 POKE 780,0 : REM LOGICAL FILE NUMBER IN .A, NOT USED FOR SAVE
1042 POKE 781,ZD : REM DEVICE NUMBER IN .X
1043 POKE 782,0 : REM DEVICE SECONDARY ADDRESS IN .Y, USUALLY 0
1044 SYS 65466
1050 REM CALL SAVE
1051 REM :: USE ZERO PAGE 251-252 TO STORE START ADDRESS
1052 REM :: 6502 IS LITTLE-ENDIAN MEANING LO-BYTE, HI-BYTE
1053 POKE 252,(INT(ZS)/256):POKE 251,ZS-(256*PEEK(252))
1054 REM :: .X AND .Y CONTAIN END ADDRESS, LO-BYTE/HI-BYTE
1055 POKE 782,(INT(ZE)/256):POKE 781,ZE-(256*PEEK(782))
1056 REM :: .A IS ZERO PAGE LOCATION WE USED TO STORE START
1057 POKE 780,251
1058 PRINT "SAVING ..." : SYS 65496
1060 REM GET RESULT
1061 REM CARRY BIT (BIT 0) OF .P IS SET IF THERE IS AN ERROR
1062 REM SO WE'LL JUST MASK BIT 0 AND MAKE IT NEGATIVE
1063 REM MEANING IT WILL RETURN -1 FOR ERROR OR 0 FOR NO ERROR
1064 ZR=-(PEEK(783) AND 254)
Test with this:
Should save the screen RAM (not color RAM) to a file named "SCREEN". Clear the screen, then load it back with