ROM calls and other tricks
Moderator: Moderators
ROM calls and other tricks
As Leif pointed out, there are more or less annotated ROM listings on the Internet and in various books. Each listing tends to use different labels, entry points and vary in detail information.
Here are a collection of (what I've found) particulary useful ROM calls to use. Rather than sorting them memory-wise, I tried to arrange them after what they can be used for. All addresses are of course for VIC-20, but most of them could even be used on the C64 by subtracting $2000 for addresses < $E000. In a few cases, the two ROMs are off by a few more bytes though.
First, a few zero page locations used further down:
INDEX1: $0022-$0023, First utility pointer
INDEX2: $0024-$0025, Second utility pointer
FORNAM: $0049-$004A, Pointer to FOR/NEXT index variable etc
TEMPF3: $004E-$0052, Temporary FLPT storage
TEMPF1: $0057-$005B, Temporary FLPT storage
TEMPF2: $005C-$0060: Temporary FLPT storage
FAC: $0061-$0066, Floating-point Accumulator (FAC)
AFAC: $0069-$006E, Alternative/Auxilary FAC
Basic does most of its calculations in floating-point (referred to as FLPT or MFLPT below), as you probably know. Therefore, a number of interesting routines working with floating-point numbers are stored in Basic ROM.
Notice that the ROM routines will use these, and possibly more zero page addreses, so if you use the zero page for own things, carefully disassemble or experiment which addresses are not touched. If you are not using floating point at all in your program, I would say that all these addresses are available for your own usage.
The syntax (A/Y) means the accumulator should contain the low byte, and the Y register contains the high byte.
Routines for execution of a Basic program
$C533: Rebuild Basic text link pointers, as discussed in the VIC utility cartridge thread. This would rarely be used unless you are moving a Basic program within memory (or make an OLD command or similar).
$C659: Reset execution to start of program and perform CLR (set pointers to variables).
$C7AE: Handle next Basic statement from current position. Not really the entry point for RUN ($C871), but it works..
Routines for SYS parameter passing
$CEFD: Read comma from BASIC text. If another symbol is found, ?SYNTAX ERROR. There are sister routines: $CEF7 checks right parenthesis, $CEFA checks left parenthesis, $CEFF compares vs accumulator (i.e. any character).
$D08B: Find variable from BASIC text. Returns address to variable in A/Y.
$D79B: Evaluate expression from BASIC text. Returns integer 0-255 in X register. Call $D79E if text pointer has already advanced.
$DBD0: Store FAC in variable pointed to by FORNAM ($49/4A).
Routines for the jiffy clock
$C9E3: Set TI$ from string. Store the address to the string into INDEX1 (low/hi) and load the accumulator with 6 (the string length). Not so useful maybe, but nice to know. If the string contains non-numeric values, you get ?ILLEGAL QUANTITY just like you would get from Basic. Notice that there are Kernel calls both to read and set the clock numerically.
$CF48: Convert TI to ASCII string and set (FAC+3) to point to string. This is useful in combination with $CB21 below to PRINT TI$.
Routines for printing
$CB1E: Print string pointed to by (A/Y) until zero byte.
$CB21: Print string pointed to by (FAC+3) until zero byte.
$CB24: Print string pointed to by (INDEX1) of length (A).
$DDCD: Print integer in (X/A). This is actually part of the routine that prints the current line number, stored in $0039-$003A.
$DDD7: Print (FAC) as ASCII string.
Routines for converting from/to FLPT/FAC
$D391: Convert integer in (A/Y) to FLPT in (FAC) within range 0 to 32767.
$D3A2: Convert (Y) to FLPT in (FAC) within range 0 to 255. These two routines are common entry points if we want to take advantage of the FAC.
$D7B5: Convert string starting at (INDEX1) of length (A) to FLPT value in (FAC). Could be useful if you have floating point constants in text format?
$D1AA: Convert (FAC) to integer in (A/Y).
$D7F7: Convert FAC to integer in (INDEX1) in range 0 to 65535.
$DDDD: Convert FAC to ASCII string starting at STACK ($0100) and ending with null byte. It corrupts $00FF. It can be useful if you want the string in memory but not print it on screen.
Routines for loading and storing FAC
$DA8C: Load AFAC with MFLPT pointed to by (A/Y).
$DBA2: Load FAC with MFLPT pointed to by (A/Y).
$DBC7: Store (FAC) into TEMPF2.
$DBCA: Store (FAC) into TEMPF1.
$DBD4: Store (FAC) into location pointed to by (X/Y).
$DBFC: Load (FAC) from (AFAC).
$DC0C: Load (AFAC) from (FAC).
Arithmetic FAC routines
$D850: Subtraction: (FAC) = MFLPT value at (A/Y) - (FAC). It calls $DA8C, so entering at $D853 would mean (FAC) = (AFAC) - (FAC).
$D867: Addition: (FAC) = MFLPT value at (A/Y) + (FAC). Ditto call as for subtraction.
$DA28: Multiply (FAC) by MFLPT value at (A/Y), answer in (FAC). It also calls $DA8C, checks whether the multiplier is zero and then continues at $DA30.
$DAE2: Multiply (FAC) by 10.
$DAFE: Divide (FAC) by 10.
$DB07: Divide (AFAC) by MFLPT value at (A/Y), sign in (X), answer to (FAC).
$DC2B: Find sign of (FAC), result in A: $01 = positive, $00 = zero, $FF = negative.
$DC58: Perform ABS function. It actually only shifts down the FAC sign ($0066) by one bit, so it becomes a positive value..
$DC5B: Compare (FAC) with MFLPT value at (A/Y): $01 = (FAC) > MFLPT, $00 = equal, $FF = (FAC) < MFLPT.
$DCCC: Perform INT function, convert (FAC) to integer and back to FLPT format again.
Various Basic calls
$E094: Perform RND. To get RND(1) functionality, enter at $E0BB. It will leave its value in (FAC).
Kernal calls - these may differ between VIC and 64
$E518: Initalize input/output. Pretty much what happens to the screen when you press RUN/STOP + RESTORE.
$E581: Home cursor, reset screen line link table.
Oh dear, this is a long list, and it is nowhere a complete disassembly. Most of it deals with how to juggle and use the FAC. While you probably can do integer arithmetic anyway, complex multiplication or use of other Basic routines (SQR [$DF71], EXP [$DFED], SIN [$E268], ATN [$E30B] etc) will be more handy - although maybe not execution time efficient - than reinventing the wheel.
I didn't even list the Kernel vector table ($FF8A to $FFF3), as it should be fairly documented elsewhere.
Here are a collection of (what I've found) particulary useful ROM calls to use. Rather than sorting them memory-wise, I tried to arrange them after what they can be used for. All addresses are of course for VIC-20, but most of them could even be used on the C64 by subtracting $2000 for addresses < $E000. In a few cases, the two ROMs are off by a few more bytes though.
First, a few zero page locations used further down:
INDEX1: $0022-$0023, First utility pointer
INDEX2: $0024-$0025, Second utility pointer
FORNAM: $0049-$004A, Pointer to FOR/NEXT index variable etc
TEMPF3: $004E-$0052, Temporary FLPT storage
TEMPF1: $0057-$005B, Temporary FLPT storage
TEMPF2: $005C-$0060: Temporary FLPT storage
FAC: $0061-$0066, Floating-point Accumulator (FAC)
AFAC: $0069-$006E, Alternative/Auxilary FAC
Basic does most of its calculations in floating-point (referred to as FLPT or MFLPT below), as you probably know. Therefore, a number of interesting routines working with floating-point numbers are stored in Basic ROM.
Notice that the ROM routines will use these, and possibly more zero page addreses, so if you use the zero page for own things, carefully disassemble or experiment which addresses are not touched. If you are not using floating point at all in your program, I would say that all these addresses are available for your own usage.
The syntax (A/Y) means the accumulator should contain the low byte, and the Y register contains the high byte.
Routines for execution of a Basic program
$C533: Rebuild Basic text link pointers, as discussed in the VIC utility cartridge thread. This would rarely be used unless you are moving a Basic program within memory (or make an OLD command or similar).
$C659: Reset execution to start of program and perform CLR (set pointers to variables).
$C7AE: Handle next Basic statement from current position. Not really the entry point for RUN ($C871), but it works..
Routines for SYS parameter passing
$CEFD: Read comma from BASIC text. If another symbol is found, ?SYNTAX ERROR. There are sister routines: $CEF7 checks right parenthesis, $CEFA checks left parenthesis, $CEFF compares vs accumulator (i.e. any character).
$D08B: Find variable from BASIC text. Returns address to variable in A/Y.
$D79B: Evaluate expression from BASIC text. Returns integer 0-255 in X register. Call $D79E if text pointer has already advanced.
$DBD0: Store FAC in variable pointed to by FORNAM ($49/4A).
Routines for the jiffy clock
$C9E3: Set TI$ from string. Store the address to the string into INDEX1 (low/hi) and load the accumulator with 6 (the string length). Not so useful maybe, but nice to know. If the string contains non-numeric values, you get ?ILLEGAL QUANTITY just like you would get from Basic. Notice that there are Kernel calls both to read and set the clock numerically.
$CF48: Convert TI to ASCII string and set (FAC+3) to point to string. This is useful in combination with $CB21 below to PRINT TI$.
Routines for printing
$CB1E: Print string pointed to by (A/Y) until zero byte.
$CB21: Print string pointed to by (FAC+3) until zero byte.
$CB24: Print string pointed to by (INDEX1) of length (A).
$DDCD: Print integer in (X/A). This is actually part of the routine that prints the current line number, stored in $0039-$003A.
$DDD7: Print (FAC) as ASCII string.
Routines for converting from/to FLPT/FAC
$D391: Convert integer in (A/Y) to FLPT in (FAC) within range 0 to 32767.
$D3A2: Convert (Y) to FLPT in (FAC) within range 0 to 255. These two routines are common entry points if we want to take advantage of the FAC.
$D7B5: Convert string starting at (INDEX1) of length (A) to FLPT value in (FAC). Could be useful if you have floating point constants in text format?
$D1AA: Convert (FAC) to integer in (A/Y).
$D7F7: Convert FAC to integer in (INDEX1) in range 0 to 65535.
$DDDD: Convert FAC to ASCII string starting at STACK ($0100) and ending with null byte. It corrupts $00FF. It can be useful if you want the string in memory but not print it on screen.
Routines for loading and storing FAC
$DA8C: Load AFAC with MFLPT pointed to by (A/Y).
$DBA2: Load FAC with MFLPT pointed to by (A/Y).
$DBC7: Store (FAC) into TEMPF2.
$DBCA: Store (FAC) into TEMPF1.
$DBD4: Store (FAC) into location pointed to by (X/Y).
$DBFC: Load (FAC) from (AFAC).
$DC0C: Load (AFAC) from (FAC).
Arithmetic FAC routines
$D850: Subtraction: (FAC) = MFLPT value at (A/Y) - (FAC). It calls $DA8C, so entering at $D853 would mean (FAC) = (AFAC) - (FAC).
$D867: Addition: (FAC) = MFLPT value at (A/Y) + (FAC). Ditto call as for subtraction.
$DA28: Multiply (FAC) by MFLPT value at (A/Y), answer in (FAC). It also calls $DA8C, checks whether the multiplier is zero and then continues at $DA30.
$DAE2: Multiply (FAC) by 10.
$DAFE: Divide (FAC) by 10.
$DB07: Divide (AFAC) by MFLPT value at (A/Y), sign in (X), answer to (FAC).
$DC2B: Find sign of (FAC), result in A: $01 = positive, $00 = zero, $FF = negative.
$DC58: Perform ABS function. It actually only shifts down the FAC sign ($0066) by one bit, so it becomes a positive value..
$DC5B: Compare (FAC) with MFLPT value at (A/Y): $01 = (FAC) > MFLPT, $00 = equal, $FF = (FAC) < MFLPT.
$DCCC: Perform INT function, convert (FAC) to integer and back to FLPT format again.
Various Basic calls
$E094: Perform RND. To get RND(1) functionality, enter at $E0BB. It will leave its value in (FAC).
Kernal calls - these may differ between VIC and 64
$E518: Initalize input/output. Pretty much what happens to the screen when you press RUN/STOP + RESTORE.
$E581: Home cursor, reset screen line link table.
Oh dear, this is a long list, and it is nowhere a complete disassembly. Most of it deals with how to juggle and use the FAC. While you probably can do integer arithmetic anyway, complex multiplication or use of other Basic routines (SQR [$DF71], EXP [$DFED], SIN [$E268], ATN [$E30B] etc) will be more handy - although maybe not execution time efficient - than reinventing the wheel.
I didn't even list the Kernel vector table ($FF8A to $FFF3), as it should be fairly documented elsewhere.
Last edited by carlsson on Tue Mar 14, 2006 5:11 pm, edited 1 time in total.
Anders Carlsson
![Image](http://www.cbm.sfks.se/i/compact.gif)
![Image](http://www.cbm.sfks.se/i/vic20.gif)
![Image](http://www.cbm.sfks.se/i/c64.gif)
![Image](http://www.cbm.sfks.se/i/electron.gif)
![Image](http://www.cbm.sfks.se/i/bbc-b.gif)
![Image](http://www.cbm.sfks.se/i/compact.gif)
Err.. not sure. For my latest game (Jewels 20), I used 28 zero page locations, which is a new personal record: $4E-$52, $57-$60 (minus $5D-$5E), $A7-$AB, $F7-$FF. Some of these addresses will be reused by the ROM routines I call, but as long as I only use them for temporary stuff and not need them after the call, it is ok.
I've seen some (C64) programs that use a few hundred, depending on whether you want to return to Basic afterwards or not. I suppose $00-$02 also are "available" on the VIC-20, and possibly many more like $22-$25, $61-66, $69-$6E and so on.
I've seen some (C64) programs that use a few hundred, depending on whether you want to return to Basic afterwards or not. I suppose $00-$02 also are "available" on the VIC-20, and possibly many more like $22-$25, $61-66, $69-$6E and so on.
Anders Carlsson
![Image](http://www.cbm.sfks.se/i/compact.gif)
![Image](http://www.cbm.sfks.se/i/vic20.gif)
![Image](http://www.cbm.sfks.se/i/c64.gif)
![Image](http://www.cbm.sfks.se/i/electron.gif)
![Image](http://www.cbm.sfks.se/i/bbc-b.gif)
![Image](http://www.cbm.sfks.se/i/compact.gif)
Another interesting ROM call, learned from comp.sys.cbm:
$D08B (53887): Find a variable, return indirect address in A/Y
"Indirect" means it is a handle to the variable which tells its size (1 byte) and location (2 bytes) in memory. Basic calls the routine above from $CF28 (53032) and stores the address on the FAC mantissa ($64/65, dec. 100/101).
This extended example is inspired by Ryan Sherwood's demonstration program. Notice that there is no comma after the SYS, since the Basic routine does not expect one:
This program both changes the content of one of the strings (line 30) and swaps the two strings (lines 35-40) in memory. The strings in this program are static, but it should work for dynamically created strings too, as well as for integers and floating point numbers etc.
I think someone (Boray?) asked a question how to pick up a string value in machine code and got a workaround answer. This information was new to me an hour ago, and I'm sure it could have some use to you too!
By the way: Don't use string variable E$ in the SYS above. You'll have to figure out yourselves why SYS 53387E$ will make Basic say ?SYNTAX ERROR. In the newsgroup, Ian and Cameron both wrote a small ML routine which would make the ROM call instead of calling it directly. By doing that, one can get around the E$ bug.
$D08B (53887): Find a variable, return indirect address in A/Y
"Indirect" means it is a handle to the variable which tells its size (1 byte) and location (2 bytes) in memory. Basic calls the routine above from $CF28 (53032) and stores the address on the FAC mantissa ($64/65, dec. 100/101).
This extended example is inspired by Ryan Sherwood's demonstration program. Notice that there is no comma after the SYS, since the Basic routine does not expect one:
Code: Select all
10 D$="HELLO":F$="LOPEZ"
15 PRINT"D$=";D$;" AND F$=";F$
20 SYS 53387D$:V1=1+PEEK(780)+PEEK(781)*256
25 SYS 53387F$:V2=1+PEEK(780)+PEEK(781)*256
30 VL=PEEK(V1):VH=PEEK(V1+1):POKE VL+VH*256,74
35 POKE V1,PEEK(V2):POKE V1+1,PEEK(V2+1)
40 POKE V2,VL:POKE V2+1,VH
45 PRINT"D$=";D$;" AND F$=";F$
I think someone (Boray?) asked a question how to pick up a string value in machine code and got a workaround answer. This information was new to me an hour ago, and I'm sure it could have some use to you too!
By the way: Don't use string variable E$ in the SYS above. You'll have to figure out yourselves why SYS 53387E$ will make Basic say ?SYNTAX ERROR. In the newsgroup, Ian and Cameron both wrote a small ML routine which would make the ROM call instead of calling it directly. By doing that, one can get around the E$ bug.
Anders Carlsson
![Image](http://www.cbm.sfks.se/i/compact.gif)
![Image](http://www.cbm.sfks.se/i/vic20.gif)
![Image](http://www.cbm.sfks.se/i/c64.gif)
![Image](http://www.cbm.sfks.se/i/electron.gif)
![Image](http://www.cbm.sfks.se/i/bbc-b.gif)
![Image](http://www.cbm.sfks.se/i/compact.gif)
- Mike
- Herr VC
- Posts: 4842
- Joined: Wed Dec 01, 2004 1:57 pm
- Location: Munich, Germany
- Occupation: electrical engineer
Small routines for saving and loading blocks of memory:
SAVE memory block:
'start' is inclusive, 'end' is exclusive. 'N$' contains the file name. Other device numbers than 8 can be used (also 1 for tape), but the additional ',1' must be kept.
LOAD memory block:
Loads the file 'N$' to its correct address without restarting the BASIC program.
Michael
Edit: Removed unnecessary POKE147,0 instruction in LOAD memory block.
SAVE memory block:
Code: Select all
SYS57809(N$),8,1:POKE193,start_lo:POKE194,start_hi
POKE780,193:POKE781,end_lo:POKE782,end_hi:SYS65496
LOAD memory block:
Code: Select all
SYS57809(N$),8,1:POKE780,0:SYS65493
Michael
Edit: Removed unnecessary POKE147,0 instruction in LOAD memory block.
Last edited by Mike on Fri Mar 13, 2009 5:08 pm, edited 1 time in total.
On the topic of loading, here are another few calls that are not useful for programming, but for .. eh, getting the most out of tape files, if you see what I mean.
SYS 63407 = Reads from tape until the header of a program is found
SYS 62980 = Reads the rest of the program
It means after the first call, one can modify the contents of the tape buffer to make the program load into another address (prevent auto start etc) and then check what the program does.
I found these calls from a computer magazine in 1990, telling its readers how to hack C64 tapes (or rather, "find hidden messages"). On the C64, the SYSes are 63276 and 62828, so slightly different position than on the VIC.
SYS 63407 = Reads from tape until the header of a program is found
SYS 62980 = Reads the rest of the program
It means after the first call, one can modify the contents of the tape buffer to make the program load into another address (prevent auto start etc) and then check what the program does.
I found these calls from a computer magazine in 1990, telling its readers how to hack C64 tapes (or rather, "find hidden messages"). On the C64, the SYSes are 63276 and 62828, so slightly different position than on the VIC.
Anders Carlsson
![Image](http://www.cbm.sfks.se/i/compact.gif)
![Image](http://www.cbm.sfks.se/i/vic20.gif)
![Image](http://www.cbm.sfks.se/i/c64.gif)
![Image](http://www.cbm.sfks.se/i/electron.gif)
![Image](http://www.cbm.sfks.se/i/bbc-b.gif)
![Image](http://www.cbm.sfks.se/i/compact.gif)
Hm. For some of these calls marked by (A/Y), it seems A contains the high byte and Y the low byte, while it should be the opposite according to my source. This applies in particular to $D391, but possibly to many other routines. I never observed this before, but it is worth checking if a call doesn't give the expected result to change order of the parameters.
Anders Carlsson
![Image](http://www.cbm.sfks.se/i/compact.gif)
![Image](http://www.cbm.sfks.se/i/vic20.gif)
![Image](http://www.cbm.sfks.se/i/c64.gif)
![Image](http://www.cbm.sfks.se/i/electron.gif)
![Image](http://www.cbm.sfks.se/i/bbc-b.gif)
![Image](http://www.cbm.sfks.se/i/compact.gif)
- Mike
- Herr VC
- Posts: 4842
- Joined: Wed Dec 01, 2004 1:57 pm
- Location: Munich, Germany
- Occupation: electrical engineer
Recovering from a tape ?LOAD ERROR
Often enough it happened to me, that I stripped off the end of a program saved on tape, because I started the SAVE too early on the tape. This destroys the copy, but the original is often untouched. Still, it causes a ?LOAD ERROR. You then can't RUN the program, because the pointers (45/46) haven't been set.
You can recover these pointers (and your program, and your state of mind) in that case with:
Michael
P.S.: You can use VERIFY to search for the end of a program on tape.
You can recover these pointers (and your program, and your state of mind) in that case with:
Code: Select all
POKE 45,PEEK(174):POKE 46,PEEK(175):CLR
P.S.: You can use VERIFY to search for the end of a program on tape.
You should be able to just open a sequencial file, save two address bytes and then save the rest of the data. Also check this: http://sleepingelephant.com/ipw-web/bul ... php?t=1270Schlowski wrote:Is there any (easy) possibility to save a certain area of RAM with another load address?
Saving $1000-$1800 with load address $1C00 for example.
PRG Starter - a VICE helper / Vic Software (Boray Gammon, SD2IEC music player, Vic Disk Menu, Tribbles, Mega Omega, How Many 8K etc.)
- Mike
- Herr VC
- Posts: 4842
- Joined: Wed Dec 01, 2004 1:57 pm
- Location: Munich, Germany
- Occupation: electrical engineer
BASIC auto-tokenizer for SEQ files
If the listing in charge doesn't have any control-characters inside (as it's the case here) you can 'type in' the listing as follows, provided you have VICE available:
1. Copy and paste the listing from your browser into your favorite editor. Make sure it saves with carriage returns (i.e. ASCII 13).
2. Save it to the VICE main directory, i.e. as 'text.txt'
3. Start xvic, and drag the file into the VICE main window.
This needs some explanation: the file is still ASCII, and will not work. But dragging the file into the main window is the easiest way to mount the surrounding directory as drive.
4. Do a hard-reset (Ctrl-Alt-R).
5. Type in this program (you should save it for later uses):
6. RUN this program. It will type in 'TEXT.TXT' for you.
7. delete lines 63996 to 63999.
8. done. You can now mount a *.d64 file as usual, and save the program as real *.prg file.
One point to note are the POKE812,x commands. They disable the CLALL vector at the start, and re-enable it, when the program finishes. Otherwise the channel would be closed upon entering a new line.
The tokenizer (of course) also works with SEQ files. In that case the ',P,R' inside the OPEN commands must be replaced with ',S,R'. Finally, it can be used to reverse the effect of PETSCII LIST output to a file with:
except that the final 'READY.' will either lead to an '?OUT OF DATA' or '?SYNTAX' error. Just type in another 'GOTO63997' + RETURN to finish the process correctly (i.e. with restoring the CLALL vector, and closing channel #2).
Michael
1. Copy and paste the listing from your browser into your favorite editor. Make sure it saves with carriage returns (i.e. ASCII 13).
2. Save it to the VICE main directory, i.e. as 'text.txt'
3. Start xvic, and drag the file into the VICE main window.
This needs some explanation: the file is still ASCII, and will not work. But dragging the file into the main window is the easiest way to mount the surrounding directory as drive.
4. Do a hard-reset (Ctrl-Alt-R).
5. Type in this program (you should save it for later uses):
Code: Select all
63996 POKE812,238:OPEN2,8,2,"TEXT.TXT,P,R"
63997 PRINT"{CLR}";:IFST<>0THENPOKE812,239:CLOSE2:END
63998 GET#2,A$:PRINTA$;:IFA$<>CHR$(13)THEN63998
63999 PRINT"GOTO63997":POKE631,19:POKE632,13:POKE633,13:POKE198,3
7. delete lines 63996 to 63999.
8. done. You can now mount a *.d64 file as usual, and save the program as real *.prg file.
One point to note are the POKE812,x commands. They disable the CLALL vector at the start, and re-enable it, when the program finishes. Otherwise the channel would be closed upon entering a new line.
The tokenizer (of course) also works with SEQ files. In that case the ',P,R' inside the OPEN commands must be replaced with ',S,R'. Finally, it can be used to reverse the effect of PETSCII LIST output to a file with:
Code: Select all
OPEN2,8,2,"TEXT,S,W":CMD2:LIST
PRINT#2:CLOSE2
Michael
Last edited by Mike on Tue Jan 13, 2009 1:33 am, edited 2 times in total.