Pitto
Pitto

Reputation: 8579

How to enter commands programmatically in c64's basic?

I want to write a very simple basic program in commodore 64 that enters other basic commands.

Here's an example:
10 print"list"+chr$(13)

This prints list but doesn't hit enter.

My expectation is that I get the result of the list command.

How can I achieve this?

Upvotes: 1

Views: 1026

Answers (3)

Bo Zimmerman
Bo Zimmerman

Reputation: 131

One way to execute BASIC commands built from a string is to manipulate the keyboard buffer. Consider the following BASIC subroutine that executes any BASIC command that you put into CM$ before GOSUBing to it:

100 PRINT CHR$(147)CHR$(17)CHR$(17)CHR$(17);CM$;
110 POKE631,13:POKE632,67:POKE633,79:POKE634,78:POKE635,84:POKE636,13
120 POKE198,6
130 PRINTCHR$(19):END
140 RETURN

100 Clear the screen, cursor down a few times, and then print your command, in CM$, onto the blank screen.

110 Poke RETURN, followed by 'C' 'O' 'N' 'T' followed by another RETURN into the keyboard buffer.

120 Tell the system that there are 6 new keystrokes in the buffer.

130 Move the cursor to the top of the screen and end the program.

This is where the magic happens. The C64 will begin processing the characters in the keyboard buffer.

140 Here is where the BASIC program will return to after your command is executed. Since this is a routine meant for GOSUBing, I just put a RETURN command here.

To test the subroutine, add the following lines:

10 CM$="LIST":GOSUB100
20 PRINT"MY PROGRAM CONTINUED RUNNING!": END

Here's an interesting page about it: Commodore 64 keyboard buffer tricks: deleting and creating BASIC lines from BASIC

Upvotes: 5

Pitto
Pitto

Reputation: 8579

The solution is incredibly easy:

10 list

You can basically type any basic command and it will just work:

10 print"Loading..."
20 load"*",8,1

This is actually all I needed to tinker with some small automations.

Upvotes: 0

Jerry Stratton
Jerry Stratton

Reputation: 3466

The short answer is, it can’t be done. The long answer is that it is possible, but not the way you’re doing it, and it’s likely to be very difficult. Most old-school BASICs, including the Commodore 64, don’t have an eval function, which is basically what you’re talking about. (According to David Lien, BBC BASIC had an EVAL command, and one of the Apple BASICs had an EXEC command that could read text from a file as if it had been typed from a keyboard, which would allow a slower emulation of an EVAL command.)

What the Commodore 64 and most old-school BASICs do have are calls to existing machine language routines. The BASIC commands are in memory somewhere, and you can transfer control to those commands if you know the memory address where the routine resides. In Microsoft variants, this is often the EXEC command. On the Commodore 64, it’s the SYS command.

The syntax is SYS <ADDRESS>, where ADDRESS is the memory location you want to transfer control to. As long as that address contains a routine that has a return code, it will do its job and then transfer control back to your BASIC program.

Often, you’ll combine the SYS call with some POKEs (to provide data to the machine language subroutine) and/or some PEEKs (to look at what the routine has done).

Here’s an example inspired by the C64 Wiki:

9 rem clear screen
10 print chr$(147)
19 rem random cursor column and line
20 co = int(rnd(1)*40)
30 ln = int(rnd(1)*25)
39 rem position cursor
40 poke 211,co
50 poke 214,ln
60 sys 58640
70 print "x";
79 rem wait for keypress and quit
80 get i$
90 if i$="" then 80
91 if i$="r" then 20
99 end

This program creates a random number from 0 to 39 for CO, and then from 0 to 24 for LN. It pokes the value of C into memory location 211, the value of LN into memory location 214, and then calls the machine language routine at memory location 58640.

That routine interprets memory location 211 as the column, and location 214 as the line, to place the cursor at. So what this program does is randomly print an “x” somewhere on the screen; if you press “r”, it does it again, until you press some other key.

The program is in lower case because I used the VICE emulator for testing, and VICE (at least on macOS) automatically converts lowercase to uppercase, and uppercase to graphics characters, when pasting.

In your example, it’s much more difficult. While the entry point for the LIST command’s routine is easily discovered (42652, or hex $A69C), how you provide the line number or range to that routine is less easily discovered. Judging from this Commodore 64 disassembly, it may need to be provided as text. (Follow the LIST routine to the LINGET routine in the disassembly.)

And then you’d need to do this for every command you wanted to emulate.

It might also be possible to run your string through the BASIC evaluator routine at $AD9E for a true eval, but that would likely be an even more involved task.

If I were forced to do something like this, I would look into these options:

  1. Use PEEK to locate a dummy line in a subroutine, and then POKE to rewrite that dummy line as the line to evaluate; then, GOSUB to that subroutine.
  2. Use the LOAD command to load the line to be evaluated after first writing it as a BASIC file.
  3. Because BASIC on the C64 is interactive, there should be a routine to call that evaluates individual lines. Find that, and determine how to provide that call the text you want evaluated.
  4. Scour the magazines and bulletin boards to see if anyone wrote an EVAL command for C64 BASIC back in the day.

Here, for example, is a very rough example of option 1:

10 rem example of how to rewrite a line of code
20 ev$ = "list"
30 gosub 100
99 end

100 rem subroutine to create eval code
109 rem locate dummy line 1010;ls=line start;nl=next line
110 ls = 2049:rem start of basic in ram
120 nl = peek(ls)+peek(ls+1)*256
130 if peek(ls+2)+peek(ls+3)*256 <> 1010 then ls=nl:goto 120
139 rem found location, start writing
140 ls=ls+5
150 for i=1 to len(ev$)
160 poke ls+i,asc(mid$(ev$,i,1))
170 next i
180 poke ls+i,0
199 return

1000 rem subroutine to place eval code
1010 rem dummy line with lots of text to make it possible to put code here
1020 return

If you run this, you will see that line 1010 changes from a long remark to a remark that contains what is in EV$. There would still be a lot of work to do get this to be a real eval, however:

  1. It does not verify that EV$ is short enough to fit on line 1010; if EV$ is long, it will continue past the end of line 1010 and overwrite 1020 as well.
  2. It does not replace the text of EV$ with the tokenization necessary for the code to actually run. You would need a means of converting the word “LIST” to the tokenization of LIST (most likely either an array or a SYS call) for this to actually run the LIST (or whatever else is in EV$).
  3. It doesn’t update the length of line 1010, although this may not be necessary. Updating the length of line 1010 would also mean having to move line 1020 in memory, whereas leaving the length of line 1010 alone means not having to move line 1020.

Upvotes: 4

Related Questions