17

I know I can use LOAD "PROGRAM",8,1 to load a program in a certain spot in memory, using the first two bytes of the file on disk.

Now let's say I have used the .MON command on the Final Cartridge III to write a machine language program.

How can I get that program on the disk?

I could think of something like

SAVE "PROGRAM",8,<<start address>>, <<length>>

I have looked all around online, but I cannot find on how to do this. I don't want to use any emulator tools or external hardware.

Edit I found the .S command in .MON on the FC3. That would solve my problem, but I still would like if to know if there are C64-only ways of doing this.

2
  • 3
    There are LOTS of "missing features" with the C64 firmware. Think about doing anything with bitmap graphics or sound. FC3 fills in a lot of gaps, and later Commodore firmware fills in more.
    – Brian H
    Oct 11, 2021 at 20:41
  • Support for graphics would have been expensive, but performance of many tasks could have been greatly improved by adding DEEK and DOKE functions that would load/store pairs of bytes, extending POKE/DOKE to keep writing bytes until the end of statement was reached, and allowing "POKE" to accept a hexadecimal string encoding multiple bytes to be stored. Probably under 200 bytes total for all those extensions.
    – supercat
    Oct 12, 2021 at 20:08

3 Answers 3

42

I have looked all around online, but I cannot find on how to do this.

Well, you won't, as the C64's BASIC2 does not provide such function or a monitor. The old PETs had TIM in ROM, while BASIC 3.5 for the TED series (C16/etc.) and 7.0 for the C128 offered TEDMON. The VIC20 was left without and the cheap quickhack C64 was based thereon.

I don't want to use any emulator tools or external hardware.

Well, then you'll need to get your hands dirty and use the following lines:

f=<first address to save>
l=<last address to save>

SYS 57812("filename"),8,1  // Read and prepare parameter list for SAVE
POKE 193,f AND 255         // Patch IO start address ($C1/2)
POKE 194,f / 256
POKE 174,l AND 255         // Patch IO end address ($AE/F)
POKE 175,l / 256
SYS 62957                  // Jump into save right where writing starts 

This is essentially building a desired SAVE BINARY command by using the existing parameter parsing, hacking the addresses and let the stock SAVE do the rest.

9
  • 4
    I'm amazed that a syntax like SYS 57812("filename"),8,1 is possible. I see why it works, I've just never seen this before.
    – Nimloth
    Oct 12, 2021 at 12:24
  • Great thanks, do you have any references on those SYS addresses? Oct 12, 2021 at 13:43
  • 3
    @BartFriederichs As Nimloth explains, SYS tcalles the parser to fetch a single argument. The function collects and evaluates an expression and stops as soon as there is a non expected character (and no open expression) , returning the collected value in FAC with the read pointer right after the last character swallowed. This allows to use the same function, no matter what delimiter comes next (comma, closing parenthesis, etc.), as that will be decided by the caller. In this case the SYS does nothing but execute it's function. Quite useful if one wants to add parameters when calling ...
    – Raffzahn
    Oct 12, 2021 at 16:02
  • 2
    ... some ML routine. Usually such a ML routine would first swallow a comma before continuing to parse, but here we hack an existing routine, not made to do so. So while looking uggly, it's perfect fine and best unless one wants to write a BASIC extension.
    – Raffzahn
    Oct 12, 2021 at 16:03
  • 2
    See Compute Gazette issue 33, 1986, page 75. It refers to the Compute! book “Programming the Commodore 64” from 1985 by Raeto West. That book has a section about block load and block save in the “Advanced BASIC” chapter, page 169, that shows exactly this method. The source of all PET/CBM knowledge “Programming the PET/CBM”, also by Raeto West, already shows a similar, slightly more complicated routine, on page 243.
    – WimC
    Oct 12, 2021 at 17:48
10

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 POKE and SYS commands.

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
1007 :
1008 ZR=-1 : REM -1 INDICATES ERROR
1009 :
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
1019 :
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
1029 :
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
1039 :
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
1049 :
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
1059 :
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)
1069 RETURN

Test with this:

ZN$="SCREEN":ZD=8:ZS=1024:ZE=2023:GOSUB 1000

Should save the screen RAM (not color RAM) to a file named "SCREEN". Clear the screen, then load it back with LOAD "SCREEN",8,1

1
  • 1
    Beside the fact, that this of course needs BASIC program storage not used for other purpose, as well as whatever memory management will do during run time. In addition you might want to should as well check for areas unable to be saved that way, like tape buffer. (P.S.: "BASIC 2.0 on the C64 is well, pretty basic." are very kind words :) )
    – Raffzahn
    Oct 12, 2021 at 19:27
0

there is no BSAVE command, unlike that apple II. there is no built in ML monitor either.

People usually would use a monitor program in a cartridge to do this.

Even the famous fast load cartridge (which nearly everyone in the US had, or something similar) has a monitor in it. with the help of a monitor you can usually enter a command similar to

s "filename" 8 $4000 $9FFe

to save the area from $4000 to $9fff

This same command actually works inside of the VICE monitor as well.

there are other carts that are literally just a monitor program. HESMON was a very common one.

there are also monitors that you can load from disk. you just need to find one that fits in an area you are not using.

any assembler program on the c64 is also capable of such saves. If you are writing a program on c64 in assembly, you would generally use a monitor or assembler, and it would have the required save command.

The POKE and SYS method also does work, but using a monitor cart or program is what people actually did, unless they were using MLX or flankspeed to enter the type in, which had it's own save routine.

The only real reason to use poke and sys is if you used data statements to poke in your ML, and then decided to save it to disk to speed things up (actually a pretty good idea). Otherwise you'd already have your save command available. I made a version of Telengard that benefited a lot from this stunt. I saved the area that were poked in with a M/L monitor, then rewrote the loader to load them instead of poke them in. :)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .