7/27/2022 NOTE: William Astle left a comment pointing out that my RAM hook example should be using a JSR instead of a JMP. This has not been updated or corrected yet, but I will.
When the CoCo was released in 1980, it came with an 8K ROM containing Color BASIC. If the CoCo was expanded to at least 16K of RAM, a second 8K ROM could be added which contained Extended Color BASIC. If a disk controller were plugged in, that controller added a third 8K ROM containing Disk Extended Color BASIC.
Each ROM provided additional features and commands to what was provided in the original 8K Color BASIC.
To allow this, Color BASIC has a series of RAM hooks that initially point to routines inside Color BASIC, but can be modified by additional ROM code to point somewhere else. For example, Extended Color BASIC would modify these RAM hooks to point to new routines provided by that ROM.
According to the disassembly in Color BASIC Unravelled, there are 25 RAM hooks starting in memory at $15E (350). Each hook is 3 bytes long, which allows it to contain a JMP instruction followed by a 16-bit address.
As an example, there is a vector for “CONSOLE OUT” at $167 (359). On a Color BASIC system, that RAM hook contains $39 $39 $39 (57 57 57). That is the opcode for an RTS instuction, so effectively it looks like this:
rts
rts
rts
In Color BASIC is a subroutine called PUTCHR. Any time BASIC wants to output a character to the device (such as the screen), it loads the character in register A then calls this routine. Here is an example that outputs a question mark:
LB9AF LDA #'? QUESTION MARK TO CONSOLE OUT
LB9B1 JMP PUTCHR JUMP TO CONSOLE OUT
The first thing this PUTCHR routine does is JMP to the RAM hook location, which is named RVEC3 (RAM hook vector 3) to run any extra code that might be needed. It looks like this:
* CONSOLE OUT
PUTCHR JSR RVEC3
...rest of routine...
RTS
Since Color BASIC just had “RTS” there, PUTCHR would JMP to the RAM hook bytes and immediately return back, then continue with the output.
The code in Color BASIC knows about outputting to several device numbers. Device #0 (default) is the screen. Device #-1 is the cassette. Device #-2 is the printer.
When Extended Color BASIC came along, it added device #-3 for use with the DLOAD command. (I don’t think I ever knew this, but I did use DLOAD to download my first CoCo terminal program.) Since Color BASIC knew nothing about this device number, these RAM hooks were used to add the new functionality,
Extended Color BASIC modifies the three bytes of this RAM hook to be $7e $82 $73. That represents:
jmp $8273
This jumps to a routine in the Extended Color BASIC ROM called XVEC3 (Extended Vector 3?). This is new code to check to see if we are using the DLOAD command (which outputs over the serial port, but not as a printer).
* CONSOLE OUT RAM HOOK
XVEC3 TST DEVNUM CHECK DEVICE NUMBER
LBEQ L95AC BRANCH IF SCREEN
PSHS B SAVE CHARACTER
LDB DEVNUM *GET DEVICE NUMBER AND
CMPB #-3 *CHECK FOR DLOAD
PULS B GET CHARACTER BACK
BNE L8285 RETURN IF NOT DLOAD
LEAS $02,S *TAKE RETURN OFF STACK & GO BACK TO ROUTINE
*THAT CALLED CONSOLE OUT
RTS
When Disk Extended Color BASIC is added, the vector is modified to point to a new routine called DVEC3 (Disk Vector 3?) located at $cc1c. (For Disk BASIC 1.0, it is at $cb4a.) That code will test to see if we are outputting to a disk device and, if not, it will long branch to XVEC3 in the Extended ROM. I find this curious since it would seem to imply that this location could never change, else Disk BASIC would break.
DVEC3 TST DEVNUM CHECK DEVICE NUMBER
LBLE XVEC3 BRANCH TO EX BASIC IF NOT A DISK FILE
...rest of routine...
RTS
Thus, with Color, Extended and Disk BASIC ROMs installed, a program wanting to output a character, such as “?”, is doing something like this:
LDA #'?
JMP PUTCHR (Color BASIC ROM)
\
PUTCHR JSR RVEC3 (RAM hook)
\
RVEC3 JMP DVEC3 (in Disk BASIC)
\
DVEC3 TST DEVNUM
LBLE XVEC3 (in Extended BASIC)
\
XVEC3 ...handle ECB
/
...handle rest of DECB...
/
/
...handle rest of CB...
…or something like that. There’s alot of branching and jumping going on, to be sure.
So what?
This means we should also be able to make use of these RAM hooks and patch our own code in to the process. Since the only thing we can alter is the hook itself, our code has to save the original RAM hook, then point the hook to our new code. At the end of our new code, we jump to where the original RAM hook went, allowing normal operations to continue. (Or, we could have made it jump to the ROM hook first, and after the normal ROM things are done, then run our new code…)
To test this, I made a simple assembly routine to hijack the CONSOLE OUT RAM hook located at $167.
RVEC3 equ $167 console out RAM hook
org $3f00
init:
lda RVEC3 get op code
sta saved save it
ldx RVEC3+1 get address
stx saved+1 save it
lda #$7e op code for JMP
sta RVEC3 store it in RAM hook
ldx #new address of new code
stx RVEC3+1 store it in RAM hook
rts done
new:
inc $400
saved rmb 3
In the init routine, the first thing we do is load the first byte (which would be an RTS or a JMP) from the RAM hook and save it in a 3-byte buffer.
We then load the next two bytes (which would be a 16-bit address, or RTS RTS for Color BASIC).
Next we load A with the value of a JMP op code ($7e). We store it in the first byte of the RAM hook vector.
We then do the same thing for the two byte address.
The RTS is the end of the code which hijacked the RAM hook. We have now pointed the RAM hook to our “new” routine.
At new, all we do is increment whatever is in memory location $400 (the top left character of the 32-column screen). Right after that INC is our 3-byte “saved” buffer where the three bytes that used to be in the RAM hook are saved. This make our code do the INC and then the same three bytes that would have been done by the original RAM hook before we hijacked it.
On Color BASIC, the RAM hook starts out with 57 57 57 (RTS RTS RTS), so the new routine would appear as:
new:
inc $4000
rts
rts
rts
For Extended Color BASIC, where the RAM hook is turned in toJMP $827e, it would become:
new:
inc $4000
jmp $827e
When we EXEC the init code, out INC routine is patched in. From that point on, any output causes the top left character of the screen to increment. This will happen for ANY output, even to a printer or cassette file, since this code does not bother checking for the device type.
Here is a BASIC loader program, generated by LWTOOLS:
10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 16128,16154,182,1,103,183,63,27,190,1,104,191,63,28,134,126,183,1,103,142,63,24,191,1,104,57,124,4,0,-1,-1
Load that in to a CoCo (or emulator), and RUN it to get the code in memory starting at $3f00.
After RUN, you should be able to EXEC &H3f00 and the hook will be installed.
Now, for something real, you’d want to find a safe place to store the new hook code. At the very least, we should have a CLEAR 200,&H3f00 or similar in this program to ensure BASIC doesn’t overwrite the assembly code. This should be enough for a simple proof-of-concept.
The RAM hooks we have available include:
OPEN COMMAND
DEVICE NUMBER VALIDITY CHECK
SET PRINT PARAMETERS
CONSOLE OUT
CONSOLE IN
INPUT DEVICE NUMBER CHECK
PRINT DEVICE NUMBER CHECK
CLOSE ALL FILES
CLOSE ONE FILE
PRINT
INPUT
BREAK CHECK
INPUTTING A BASIC LINE
TERMINATING BASIC LINE INPUT
EOF COMMAND
EVALUATE AN EXPRESSION
RESERVED FOR ON ERROR GOTO CMD
ERROR DRIVER
RUN
ASCII TO FLOATING POINT CONV.
BASIC’S COMMAND INTERP. LOOP
RESET/SET/POINT COMMANDS
CLS
SECONDARY TOKEN HANDLER
RENUM TOKEN CHECK
EXBAS’ GET/PUT
CRUNCH BASIC LINE
UNCRUNCH BASIC LINE
There are many possibilities here, including adding new commands.
At my day job, I work on a Windows application that supervises high power solid state microwave generators. (The actual controlling part is done by multiple embedded PIC24-based boards, which is a good thing considering the issues Windows has given us over the years.)
At some point, we switched from building a 32-bit version of this application to a 64-bit version. The compiler started complaining about various things dealing with “ints” which were not 64-bits, so the engineer put in #ifdef code like this:
That took care of the warnings since it would now use either a native “int” or a 64-bit int, depending on the target.
Today I ran across this and wondered why C wasn’t just taking care of things. A C library routine that returns an “int” should always expect an int, whether that int is 16-bits (like on Arduino), 32-bits or 64-bits on the system. I decided to look in to this, and saw the culprits were things like this:
length = strlen (pathName);
Surely if strlen() returned an int, it should not need to be changed to an “unsigned __int64” to work.
And indeed, C already does take care of this, if you do what it tells you to do. strlen does NOT return an int:
size_t strlen ( const char * str );
size_t is a special C data type that is “whatever type of number it needs to be.” And by simply changing all the #ifdef’d code to actually use the data type the C library call specifies, all the errors go away and the #ifdefs can be removed.
size_t length = 0;
A better, more stricter compiler might have complained about using an “int” to catch something coming back as “size_t.”
Oh wait. It did. We just chose to solve it a different way.
In this article, I want to talk a bit about how Color BASIC variables are stored in memory. This will help explain what the VARPTR (“variable pointer”) command is used for, and also be useful in the future when I get around to exploring how the Extended BASIC GET and PUT graphics commands work.
But first…
What’s in a (variable) name?
In Color BASIC, variable names (both string and numeric) start with a letter and may be followed by a second letter or a number. Valid variable names are:
A to Z
A0 to A9 through Z0 to Z9.
AA to ZZ
String variables follow the same convention, just with a $ after the name (A$, AB$, F5$, etc.).
What’s in a (long variable) name?
While BASIC to allow you to type longer variable names, only the first two characters are used. Thus, a variable such as:
LONGVARIABLE=1
…will actually let you…
PRINT LONGVARIABLE
…and see that the value is 1. However, any other variable starting with the first two characters (“LO”) would overwrite this variable (since it would be the same variable).
Color BASIC variables may be very long, but only the first two characters are used.
Each time you declare a new variable, you will see memory decrease by seven bytes:
Each Color BASIC numeric variable takes 7 bytes of memory.
Without looking at the Color BASIC Unravelled BASIC ROM disassembly, we can speculate that two of those bytes are probably for the variable name, with the other five used for … whatever it takes to have a variable. (And with looking at those books, we can see exactly how this works. But exploring is more fun, so let’s do that instead…)
Thanks for the (variable) memory.
The Microsoft BASIC found in the Radio Shack Color Computer has an interesting command called VARPTR. It returns the memory address of a specified variable. In an earlier series on interfacing BASIC with assembly, I discussed VARPTR to some detail, but all we need to know here is that a standard numeric variable is stored as five bytes of memory.
Thus, if you use VARPTR to get the location of a numeric variable, you can print the five bytes at that location and see the raw data that BASIC uses to represent that floating point value:
Color BASIC VARPTR on a numeric variable.
Above, we see that the numeric variable “A” was located in memory at 9868 at that moment in time. The five bytes that represent “A=1” appear to be 129, 0, 0, 0, 0. All numbers in Color BASIC are floating point values, with the bytes representing an Exponent, Mantissa and Sign. I’d try to explain, but then my head would explode. (Though I do plan to figure it out and write about it at some point.)
For now, let’s just go with “numbers take up five bytes” and move on.
String me along.
String variables (variable names that end in $) are a bit different. The text of the string is stored elsewhere in string memory, and the five bytes that VARPTR points you to contain information on how to get to where the actual string lives:
Above you see the five bytes are 7, 0, 127, 248 and 0. The first byte is the length of the string. In this case, “ABCDEFG” is 7 bytes long, so the first byte is 7. The next byte is not used for a string, and is always 0. The third and fourth bytes are the memory location where the string text is actually stored. The fifth byte is not used and is always 0.
We know that each variable takes up 7 bytes of memory, and that VARPTR returns the location of the 5 bytes that are the actual variable. So where is the name? Directly before the 5-bytes. If you look at the two bytes before the VARPTR address, you will see the name bytes:
The 65 is the ASCII character for “A”, and since it was only one digit, the second location is 0. If the variable had been named “AA”, those two bytes would be 65 65.
And, if the variable is a string, the second byte will have the high bit set (128 added to it).
If the variable had been AA$, those two bytes would have been 65 193 (65+128).
Hip, hip array!
Arrays are another variable type that I have not explored until shortly before writing this article. The DIM command can be used to declare an array of 1 to n numbers (or strings). It is a base-0 value (meaning elements count from 0 on up) so if you wanted ten entries, you could DIM A(9):
10 DIM A(9)
20 FOR I=0 TO 9
30 A(9)=RND(100)
40 NEXT
50 FOR I=0 TO 9
60 PRINT I,A(I)
70 NEXT
The above code would declare room for 10 entries in the A array, then load each entry (0 to 9) with a random number. It then does another loop to print the contents of all ten array entries, showing the random numbers that were stored there.
When I first looked at this, I noticed declaring an array seemed to take 7 bytes of memory plus 5 bytes per entry, versus a normal variable taking just 7 bytes (2 bytes for the name plus 5 bytes of the number).
Above, you see we started with 22823 bytes free. We declared DIM A(0) to hold one entry and memory went down to 22811 — 12 bytes. Then doing a CLEAR to erase all variables and declaring the A array to hold two entries consumed 17 bytes. We know that 2 bytes is the variable name, and each value would take 5 bytes, so it looks like the array has 2 bytes for the name, 5 bytes for “something special to an array”, then 5-bytes for each entry.
CLEARing memory then declaring the A array to hold three entries (0-2) confirmed this, since it now took 22 bytes (7 bytes plus 15 for the three numbers).
But VARPTR still ends up pointing to something that looks just like a normal variable – 5 bytes that represent that variable or string. Here’s that normal variable again:
Color BASIC VARPTR on a numeric variable.
And here’s the same, but for an array variable:
So where are the other bytes? Just like the name, they are just before the value that VARPTR returns. To see them, we’d look at the 7 bytes just before what VARPTR returns.
A WARNING ABOUT VARPTR AND ARRAYS
The BASIC program is stored in memory, followed by variables, and then arrays. At the very end of memory is string storage. If you get a VARPTR to an array, and then create a new variable, the new variable will be inserted in variable memory, and array storage will be relocated. This can and will render the VARPTR value incorrect! To avoid this, make sure you declare any variables you plan to use before doing the VARPTR!
In this example, notice the use of DIM to declare “I”, since “I” will be used in a FOR/NEXT loop AFTER the VARPTR is obtained. If it had not been re-declared, the VARPTR would have been printed, then “I” would have been dynamically created, causing arrays to be moved up in memory. Everything would have been off by the 7 bytes that got inserted by allocating the new variable “I”.
65 is the ASCII value for “A”, the name of our variable. Just like normal variables, if the array is a string, the second byte of the name will have the high bit set, which adds 128 to it. DIM AA(0) would be 65 65, but DIM AA$(0) would be 65 193 (65+128).
Byte 1 – first letter of variable name
Byte 2 – second letter of variable name (add 128 if it’s a string)
Next is a 0, then 12. These values increased based on how large the array was, going up by 5 each time a new element was added. With only one entry, 12 seemed to be the size of the 7-byte header, plus a 5-byte variable. Therefore…
Bytes 3 and 4 – memory used by the array, from start of header to end of array data.
The next byte seems to increase based on the number of DIMensions. DIM A(0) would show 1. DIM A(0,0) would show two. Therefore…
Byte 5 – number of dimensions. DIM A(0)=1, DIM A(0,0)=2, DIM A(0,0,0)=3
The next two bytes seemed to change based on the size of the array — as in, DIM A(0) would give 1, and DIM A(99) would give 100:
Bytes 6 and 7 – number of elements in the array.
And then I realized this information was only correct for a single dimension array, such as DIM A(9).
What happens with a multi-dimensioned array, like DIM A(9,9)? Each dimension gets another two bytes added to the header. DIM A(0) would have a 7 byte header. DIM A(0,0) would have a 9 byte header. DIM A(0,0,0) would have an 11 byte header, and so on.
Here is what I see when I do DIM A(1,2,3):
We see three sets of two bytes each — 0 4, 0 3, and 0 2. That corresponds to our DIM A(1,2,3) since using base-0 that is 2 (0-1), 3 (0-2) and 4 (0-3). But, it’s in reverse order.
That tells us after the 2 byte name, and 2 byte size, the 1 byte number of dimensions will be 2 bytes containing the size of each array in the reverse order they were declared.
Bytes 6 and 7, 8 and 9, 10 and 11, etc. – number of elements in each array, in reverse order for some reason.
Good to know. Using that above example, maybe it looks like this:
DIM A(1,2,3)
NAME SIZE #D third second first data....
| | | | | | | | | | | |
[65][0] [x][x] [3] [0][4] [0][3] [0][2] [.....][.....]
The order of the data bytes was the next thing I wanted to figure out. If it was just a single dimension array, it would be simple. But with multiple dimensions I was curious what order they were stored.
I did a test where I made a two dimensional array, each holding two elements. This 2×2 array would hold four numbers. By checking the address of each entry, I was able to determine the order:
This showed me that the elements for the first array would be first, which surprised me because the order of the “how big is each array” entries was reversed.
I tested with a three dimensional array, each with two elements. This 2x2x2 array could hold eight elements. Again I saw that the first dimension was stored, then the second, and so on:
Looking at a two-element array looked quite like binary, representing 000, 100, 010, 110, 001, 101, 011, 111. That’s opposite of how binary counting is normally represented, where it starts with the right-most bit. But still interesting to see the familiar pattern.
Here is the program I used to show the VARPTR of each dimension in the array:
0 ' showdims.bas
10 SZ=1
20 DIM DV,I,J,K,A(SZ,SZ,SZ)
30 ' 0=SCREEN, -2=PRINTER
40 DV=0
50 PRINT #DV,"ENTRIES:";(SZ+1)^3
60 FOR K=0 TO SZ
70 FOR J=0 TO SZ
80 FOR I=0 TO SZ
90 PRINT #DV,"VARPTR(A(";
100 PRINT #DV,USING ("# # #");I;J;K;
110 PRINT #DV,")",;
120 PRINT #DV,VARPTR(A(I,J,K))
130 NEXT:PRINT#DV
140 NEXT
150 NEXT
Since I could only fit all the entries from a small array on the 32 column screen’s 16 lines, and only a few more on a CoCo 3’s 24 column 40/80 screen, I made the program support outputting to the printer. Using XRoar’s “print to a file” feature, I was able to capture larger dimensions:
I really did start with 1000 lines of output in this article. I get paid per line ;-)
If you get bored, maybe you can figure out a program that would dump the bytes for each array element.
So how big is it?
Here is a short program which will calculate how much room an array will take. If it is a string array, it will be this size plus the size of all the bytes in the string data portion.
0 ' dimsize.bas
10 INPUT "NUMBER OF DIMENSIONS";ND
20 M=1
30 FOR I=0 TO ND-1
40 PRINT "ENTRIES FOR DIM";I;
50 INPUT J
60 M=M*J
70 NEXT
80 M=5+(2*ND)+(M*5)
90 PRINT "MEMORY USED:";M
In conclusion…
And that is about all I have to say on VARTPR. For now. Until I started writing about it, I knew nothing about how it worked with arrays. I had used it a few times to pass in a string to an assembly language routine, but that was the extent of my knowledge.
I am posting this for the search engines to find, in case others have found themselves in this situation.
Google and Apple created situations where you ended up with multiple accounts that could not be merged together.
Apple
2000 – iTools
Apple introduces iTools, which included a free e-mail address “for life.” This service was renamed to .Mac in 2002, and later MobileMe in 2008. It was replaced by iCloud in 2012.
I signed up for my free @mac.com e-mail address back then, and still have that original e-mail address. I never used that e-mail, since I had been using the same pobox.com service for that since 1995.
As iTools/.Mac/MobileMe/iCloud evolved, they remained linked with my original 2000 account. When I purchases extra iCloud Drive space, that had to be done through my iCloud (.mac) e-mail address, since that was the account all the services were on.
2003 – iTunes Store
Apple introduces the iTunes program in 2001, which was used to sync music to the brand new iPod MP3 player. In 2003, the iTunes Store launched, and I signed up for an account there and purchased my first music digital download.
These were two completely different services and accounts. I signed up to the iTunes Store using the e-mail address I’d been using since 1995.
Over the years, all my iTunes purchases were made under my iTunes account. This includes any iPhone apps purchased from the App Store, introduced in 2008.
Apple ID
At some point, the Apple ID was introduced. One of the features is:
Users can use different Apple IDs for their store purchases and for their iCloud storage and other uses. This includes many MobileMe users who have always had difficulties as they were forced to use more than one Apple ID, because on signing-up to the MobileMe service a new Apple ID was automatically created using the me.com email address being created at the time, meaning users could not change their previous Apple ID email address to be their me.com email address and has always remained so. Apple does not permit different accounts to be merged.[13] …
Google launched a free web e-mail service on April 1, 2004. It claimed to give every user 1 gigabyte of e-mail storage, for free. That was such a huge amount of storage that many suspected this was an April Fool’s Day joke. Like many, I signed up for a gmail.com e-mail address.
I never used it, preferring to stick with my pobox.com alias I had been using since 1995.
2005 – YouTube
YouTube was not created by Google. It was purchased by Google in late 2006. I signed up for a YouTube account in those early days, before Google was involved. Thus, not a Google account. After Google acquired YouTube, they rolled the accounts over to Google accounts, but could not merge them.
Thus, I ended up with one Google account, which was for my gmail, and another Google account, which was my YouTube. As Google introduced more services, they were added to both my accounts.
Today
Today, I have two Apple IDs — one that I had always used for purchases, and the other was just a free e-mail account. As more and more Apple services were added, they were added to both accounts. Anything with an iCloud login, such as an iPhone, iPad, Apple Watch, Mac, etc., I would use my original iTools account. Anything associated with the Apple Stores would use my original iTunes account.
Fortunately, iOS devices have these areas separated — you can log in to your iCloud account, and log in to a separate account for purchases and subscriptions.
Meanwhile, as Google introduced new services, they rolled in to all accounts. I used them with my original YouTube account, and kept my Gmail account just as e-mail.
Which means today I have two Google Sheets, two Google Docs, two Google Voice, etc. I log in to one if using e-mail, and the other if using “everything else.”
Neither company has a way to merge these accounts. With Google, I had created a free Google Voice voice mail number in the mid-2000s to use with the podcasts I was making at the time, Then, several years later I had to give up my cell phone for awhile. I ported my old Sprint-then-AT&T number over to my other account’s Google Voice. If Google ever did allow merging accounts, I’d have to lose one of those numbers.
As to Apple, I initially liked having the thing connected to money (credit cards on file to make purchases or pay for subscriptions) separated from the thing that was just e-mail and free Apple services. But that created a problem, as I somehow have been in different accounts when I’ve activated different things, such as AppleCare on a device, or a subscription to some service. Now I have to log out and re-log to the other account just to see what all I am paying for.
It’s quite a mess.
YouTube Channels makes things even worse
To make things even worse, at some point Google introduced Channels to YouTube. This allowed you to have one main YouTube account (hooked to you Google login) but create sub-channels that could have their own videos and playlists. My channel has always been an odd mix of theme park home movies, Halloween haunted house interviews, Renaissance festival footage, retro computer videos, and dogs skateboarding. Channels should be a solution, but YouTube does not allow you to move a video from a legacy account to a new Channel. They advise you to download the video from YouTube, then re-upload to the new channel. That loses all play history, comments, etc.
Is there a solution?
I am posting this here in case anyone finds it via search, and discovers any type of solution to these issues.
I spent about an hour with various Apple Support folks last night — initially as a Chat, and then via phone as I was forwarded from department to department. Obviously, changing billing credit cards is a different division than the one that handles iCloud Disk subscriptions, so I don’t feel I was ever put in the wrong place. There were just a bunch of different places that are all part of this ecosystem. I expect, if I could reach a Google support person on the phone, I would have a similar experience there.
I offer no tips. No solutions. And no advice. But if you end up here with a similar story, please share it in the comments. I’d like to hear what you have discovered.
Until next time… Remember: Anything simple today may become a huge pain in the arse decades later ;-)
Just as I thought I had reached the conclusion to my epic masterpiece about classic CoCo game startup screens, Robert Gault made this post to the Color Computer mailing list:
Robert Gault robert.gault at att.net Sat Jul 2 09:56:21 EDT 2022
An author of games for Tandy, James Garon, put some lines in the Basic game WAR which is on colorcomputerarchive.com . The code is in lines 60000 and up which can’t be LISTed with a CoCo3 but can be read with a CoCo2. The lines in question contain PRINT commands which when listed with a CoCo2, look like the data inside the quotes have been converted into Basic commands. You can also see this if you use imgtool or wimgtool to extract the program from the .dsk image. These lines generate PMODE graphics for the title screen.
Do anyone have a clue as to how these Basic lines work?
– Robert Gault, via CoColist
The game in question is available in tape or disk format from the Color Computer Archive:
And, it’s in BASIC! Well, almost. It contains an assembly language routine that rotates those colors, and it does it the same way I figured out in this article series. I am just thirty years too late with my solution.
As Robert mentioned, there’s some weirdness in the program starting at line 60000. When you LIST it, you get a rather odd output full of BASIC keywords and such:
WAR.BAS line 60000
The first bit looks okay… CLS to clear the screen, then A$ is created to be 28 spaces — CHR$(32). But that PRINT looks a bit … weird.
I’ve seen this trick before, and even mentioned it recently when talking about typing in an un-typable BAISC pogram. The idea is you can create a program that contains something in a string or PRINT statement, like:
PRINT "1234567890"
…and then you alter the bytes between those quotes somehow, such as locating them and using POKE to change them. Let’s figure out an easy way to do this.
First, I’ll start with a print statement that has characters I can look for later, such as the asterisk. I like that because it is ASCII 42. If you don’t know why I like forty-two, you must be new here. This wikipedia page will give you the details…
100 PRINT "**********"
Now we need to alter those bytes and change them to something we couldn’t normally type, such as graphics blocks (characters 128-255). To do this, we can use some code that scans from the start of the BASIC program to the end, and tries to replace the 42s with something else.
This is dangerous, since 42 could easily appear in a program as part of a keyword token or line number or other data. But, I know that a quote is CHR$(34), so I could specifically look for a series of 42s that is between two 34s.
Numbers.
So many numbers.
In Color BASIC, memory locations 25 and 26 contains the start of the BASIC program in memory. Locations 27 and 28 is the end of the program. Code to scan that range of memory looking for a quote byte (34) that has an asterisks byte (42) after it might look like this:
0 ' CODEHACK.bas
10 S=PEEK(25)*256+PEEK(26)
20 E=PEEK(27)*256+PEEK(28)
30 L=S
40 V=PEEK(L)
50 IF V=34 THEN IF PEEK(L+1)=42 THEN 80
60 L=L+1:IF L<E THEN 40
70 END
...
We could then add code to change all the 42s encountered up until the next quote (34).
80 L=L+1:IF PEEK(L)=34 THEN END
90 POKE L,128:GOTO 80
100 PRINT "**********"
Line 80 moves to the next byte and will end if that byte is a quote.
In line 90, it uses POKE to change the character to a 128 — a black block. It continues to do this until it finds the closing quote.
If you load this program and LIST it, it looks like the code shown. But after you RUN, listing it reveals a garbled PRINT statement similar to the WAR line 60000. But, if you run that garbled PRINT statement, you get the output of black blocks:
CoCo code hack!
As you can see, line 100 changes ten asterisks to the keyword FOR ten times. I am guessing that the numeric token for “FOR” is 128.
Color BASIC Unravelled
…and my guess was correct! According to Color BASIC Unravelled, the token for FOR is hex 80 — which is 128. Perfect. So the BASIC “LIST” routine is dump, and tries to detokenize things even if they are surrounded by quotes. Interesting.
At this point, if you were to EDIT that line, it would detokenize it to be…
100 PRINT "FORFORFORFORFORFORFORFORFORFOR"
…and if you saved your edit, you’d now have a line that would print exactly that, no longer ten black blocks for the word FOR ten times as if you’d typed them all in.
This makes these changes uneditable. BUT, once the modification code has been ran, you can delete it, then SAVE/CSAVE the modified program. When it loads back up, it will have those changes.
In the case of WAR line 60000, it’s a PRINT that shows a series of color blocks used for the top of the attract screen.
Here is the garbled output of lines 60000 on in the WAR.BAS program:
Look familiar? It’s just like the PRINT in line 60000, though the pattern of the blocks is different, and it is only printing 31. Since the one prints on the bottom of the screen, if it printed all the way to the bottom right position, the screen would scroll up one line.
WAR.BAS line 60010
Line 60020
60020 POKE1535,175:T$="WAR!":PRINT@99,"A YOUNG PERSON'S CARD GAME";:PRINT@80-LEN(T$)/2,T$;:PRINT@175,"BY";:PRINT@202,"JAMES GARON";:PRINT@263,"COPYRIGHT (C) 1982";:PRINT@298,"DATASOFT INC.";:PRINT@389,"LICENSED TO TANDY CORP.";:SCREEN0,1
More normal code… The POKE 1535,175 is what fills in the bottom right block of the screen, where PRINT did not go to. 175 is a blue block.
After this are just normal PRINT@ statements to put the text on the screen.
At the end, SCREEN 0,1 puts the CoCo in to its alternate screen color of pink/red/orange/whatever color that is.
This line is normal code, but the use of EXEC tells us some assembly language is being used.
The first thing it does is GOSUB 60030, then it gets any waiting keypress in I$. I don’t see I$ used so I’m unsure of this purpose.
Next it does two FOR/NEXT loops. The first “FOR I” appears to be the number of times to do this routine. The second “FOR J/NEXT” is just a timing delay.
After this is a direct check for any waiting key by using INKEY$ directly. If no key is pressed, “EXEC V” is done… This would execute whatever machine language routine is loaded in to memory at wherever V is set to. But what is V? That must be the GOSUB 60030, which we will discuss after this.
After the NEXT (FOR I) is “RETURN ELSE RETURN”. That way it does a return whether or not the IF INKEY$ is true. Since either way will RETURN, with nothing executed after this line, this might have also worked (extra spaces added for readability):
60025 GOSUB 60030:I$=INKEY$:FOR I=1 TO 300:FOR J=1 TO 30:NEXT:IF INKEY$=""THEN EXEC V:NEXT
6026 RETURN
But James Garon seems to know his stuff. Each line number takes up 5 bytes of space. The three keywords “RETURN ELSE RETURN” (without spaces) only takes up four (I’m guessing RETURN is a one byte token and ELSE is a two byte token.)
So indeed, the odd “RETURN ELSE RETURN” is less memory than putting one RETURN on the next line. (In my Benchmarking BASIC articles, I’ve focused on speed versus space, so perhaps I’ll have to do a series on “Making BASIC Smaller” some time…)
Line 60030 creates a string, but the string seems reminiscent of those PRINT lines seen earlier. Since we don’t see anything using this string, it’s probably not for displaying block graphics characters.
We do see the use of VARPTR(A$) on the next line. VARPTR returns the “variable pointer” for a specified variable. I’ve discussed VARPTR in earlier articles, but the Getting Started with Color BASIC manual describes it as:
VARPTR (var) Returns addresses of pointer to the specified variable.
– Getting Started with Color BASIC
When using VARPTR on a numeric (floating point) variable, it returns the location of the five bytes that make up the number, somewhere in variable space.
But strings are different. Strings live in separate string memory (reserved by using the CLEAR command, with a default of 200 bytes), or they could be contained in the program code itself. See my String Theory series for more on that. With a string, the VARPTR points to five bytes that point to where the string data is contained.
This tells me A$ contains a machine language routine. The GOSUB to this routine returns the address of the bytes between the quotes of the A$=”…” then that location is EXECuted to run whatever the routine does.
To figure this one out is much simpler, since no PEEKing of BASIC code is needed. It’s in a string, so we can just print the ASC() value of each byte in the string:
60030 A$="RUN!SUBELSE,NEXTENDFORTHENFORDIM/!9"
60031 FOR I=1 TO LEN(A$):PRINT ASC(MID$(A$,I,1));:NEXT:END
By doing a RUN 60030 I then get a list of bytes that are inside of that string:
I recognize 57 as the op code for an RTS instruction, so this does look like it’s machine code. And while I could use some 6809 data sheet and look up each of those bytes to figure out what they are, I’d rather have something else do the work for me.
Online 6809 Simulator to the rescue!
At www.6809.uk is an incredible 6809 simulator that I recently wrote about. It lets you paste in assembly code and run it in a debugger, showing the registers, op codes, etc. To get these bytes in to the emulator, I just turned them in to a stream of DATA using the “fcb” command in the simulator’s assembler:
By pasting that in to the simulator’s “Assembly language input” box and then clicking “Assemble source code“, the code is built and then displays on the left side in the debugger, complete with op codes:
6809.uk
Now I can see the code is:
4000: 8E 03 FF LDX #$03FF
4003: 30 01 LEAX $01,X
4005: A6 84 LDA ,X
4007: 2C 04 BGE $400D
4009: 8B 10 ADDA #$10
400B: 8A 80 ORA #$80
400D: A7 80 STA ,X+
400F: 8C 06 01 CMPX #$0601
4012: 2F F1 BLE $4005
4014: 39 RTS
Since I did not give any origination address for where this code should go, the simulator used hex 4000. There are two branch instructions that refer to memory locations, so I’ll change those to labels and convert this just to the source code. I’ll even include some comments:
LDX #$03FF * X points to 1023, one byte before screen memory
LEAX $01,X * Increment X by 1 so it is now 1024
L4005
LDA ,X * Load A with the byte pointed to by X
BGE L400D * If that byte is not a graphics char, branch to L400d
ADDA #$10 * Add hex 10 (16) to the character, next color up
ORA #$80 * Set the high bit (in case value rolled over)
L400D
STA ,X+ * Store (possibly changed) value back at X and increment X.
CMPX #$0601 * Compare X to two bytes past end of screen
BLE L4005 * If X is less than that, branch to L4005
RTS * Return
This code scans each byte of the 23 column screen. If the character there has the high bit set, it is a graphics character (128-255 range). It adds 16 to the value, which moves it to the next color (16 possible combinations of a 2×2 character block for 8 colors). It stores the value back to the screen (either the original, or one that has been shifted) and then increments X to the next screen position. If X is less than two bytes past the end of the screen, it goes back and does it again.
Hmm, it seems this routine would actually write to one byte past the end of the screen memory if it contained a value 128-255. Hopefully nothing important is stored there. (Am I right?)
And, for folks more used to BASIC, here is the same code with BASIC-style comments:
LDX #$03FF * X=&H3FF (1023, byte before screen)
LEAX $01,X * X=X+1
L4005
LDA ,X * A=PEEK(X)
BGE L400D * IF A<128 GOTO L400D
ADDA #$10 * A=A+&H10:IF A>&HFF THEN A=A-&HFF
ORA #$80 * A=A OR &H80
L400D
STA ,X+ * POKE X,A:X=X+1
CMPX #$0601 * Compare X to &H601 (two bytes past end)
BLE L4005 * IF X<=&H601 GOTO L4005
RTS * RETURN
My BASIC-style comments don’t exactly match what happens in assembly since. For example, “BGE” means “Branch If Greater Than”, but there was no compare instruction before it. In this case, it’s branching based on what was just loaded in to the A register, and would be compared to 0. That looks odd, but BGE is comparing the value based on it being signed — instead of the byte representing 0-255, it represents -127 to 128, with the high bit set to indicate the value is negative. So, if the high bit is set, it’s a negative value, and the BGE would NOT branch. Fun.
If you run this BASIC program, it will BASICally do the same thing … just much slower:
100 X=&H3FF
110 X=X+1
120 A=PEEK(X)
130 IF A<128 GOTO 160
140 A=A+&H10:IF A>&HFF THEN A=A-&HFF
150 A=A OR &H80
160 POKE A,X:X=X+1
170 'Compare X to &H601
180 IF X<=&H601 GOTO 120
190 RETURN
So that is the magic to how this attract screen works so fast. It uses POKEd PRINT statements to quickly print the top and bottom of the screen, FOR/NEXT loops to print the sides, then this assembly routine to shift the colors and make them rotate around the screen.
And, this code is very similar to the routine I came up with earlier in this series:
start
ldx #1024 X points to top left of 32-col screen
loop
lda ,x+ load A with what X points to and inc X
bpl skip if not >128, skip
adda #16 add 16, changing to next color
ora #$80 make sure high gfx bit is set
sta -1,x save at X-1
skip
cmpx #1536 compare X with last byte of screen
bne loop if not there, repeat
sync wait for screen sync
rts done
My routine starts with X at 1024, then uses BPL instead of BGE. I also increment X after I load the character, and I only update it if it gets modified by storing it 1 before where X is then.
At first, I thought mine was more clever. But I decided to see what LWASM said.
Counting cycles for fun and profit. Or just more speed.
LWASM can be made to display a listing of the compiled program and, optionally, list how many cycles each line will take. And, optionally optionally, keep a running total of cycles between places in the code you mark. (I have another article about how to use this.)
Here is my version, with (cycles) in parens, and running totals in the column next to it.
This one appears to use one cycle less — 22 — in it’s loop. Nice! Even though I thought it would be worse, once again, James Garon appears to know his stuff.
I think his routine may be a bit larger, and I wondered why he started X one byte before the screen memory and then incremented it. That seems wasteful.
However, William “Lost Wizard” Astle saw exactly why in a reply on the CoCo mailing list:
He uses that sequence instead of the more obvious one to avoid having a NUL byte in the code. A NUL would cause the interpreter to think it’s the end of the line and break things.
– William Astle via CoCo mailing list
It took me a moment, but I think I understand now. If he had done “LDA #$4000”, the byte sequence would have been whatever byte is LDA, followed by $04 and $00. You can’t put a 0 in a string, or BASIC will think that is the end of the string. Any assembly encoded this way must avoid using a zero. This is also the reason he doesn’t compare to the byte past the end of the screen, which is $6000. Though, I expect comparing to 1535 and using “Branch if less than OR equal to” would have worked and avoided the zero.
But James Garon knows his stuff, so I had to see if my way was larger or slower:
(3) cmpx #1535
(3) ble loop
(3) CMPX #$0601
(3) BLE L4005
Well, they look the same speed, and neither generates a zero byte in the machine code. I don’t know why he does it that way.
Any thoughts?
BONUS!
If one were to patch the Color BASIC “UNCRUNCH” routine to show things between quotes, here is what those lines would look like… (And if one did such a patch, I expect they’d be writing a future article about it…)
Conclusion
For some reason, James Garon chose to embed assembly code and graphics characters like this, rather than using DATA statements and building strings or POKEing assembly bytes in to memory somewhere.
It’s cool to see. But unless someone knows James Garon, I guess we don’t know why this method was done.
Other than “because it’s cool,” which is always a good reason to do something when programming.
Today Sebastian Tepper submitted a solution to the “draw black” challenge. He wrote:
I think this is much faster and avoids unnecessary SETs. Instruction 100 will do the POKE only once per character block.
– Sebastian Tepper, 7/5/2022
The routine he presented (seen in lines 100 and 101) looked like this:
10 CLS 20 FOR A=0 TO 31 30 X=A:Y=A:GOSUB 100 40 NEXT 50 GOTO 50 100 IF POINT(X,Y)<0 THEN POKE 1024+Y*16+X/2,143 101 RESET(X,Y):RETURN
It did see the criteria of the challenge, correctly drawing a diagonal line from (0,0) down to (31,31) on the screen. And, it was fast.
POINT() will return -1 if the location is not a graphics character. On the standard CLS screen, the screen is filled with character 96 — a space. (That’s the value you use to POKE to the screen, but when printing, it would be CHR$(32) instead.) His code would simply figure out which screen character contained the target pixel, and POKE it to 143 before setting the pixel.
So I immediately tried to break it. I wondered what would happen if it was setting two pixels next to each other in the same block. What would RESET do?
I added a few lines to the original test program so it drew the diagonal line in both directions PLUS draw a box (with no overlapping corners). My intent was to make it draw a horizontal line on an even pixel line, and odd pixel line, and the same for verticals. It looks like this (and the original article has been updated):
10 CLS
20 FOR A=0 TO 15
30 X=A:Y=A:GOSUB 100
31 X=15-A:Y=16+A:GOSUB 100
32 X=40+A:Y=7:GOSUB 100
33 X=40+A:Y=24:GOSUB 100
34 X=39:Y=8+A:GOSUB 100
35 X=56:Y=8+A:GOSUB 100
40 NEXT
50 GOTO 50
And this did break Sebastian’s routine… and he immediately fixed it:
100 IF POINT(X,Y)<0 THEN POKE 1024+INT(Y/2)*32+INT(X/2),143
101 RESET(X,Y):RETURN
I haven’t looked at what changed, but I see it calculates the character memory location by dividing Y by two (and making sure it’s an integer with no floating point decimals — so for 15 becomes 7 rather than 7.5), and then adds half of X. (Screen blocks are half as many as the SET/RESET pixels).
And it works. And it works well — all cases are satisfied.
And if that wasn’t enough, some optimizations came next:
And for maximum speed you could change line 100 from:
100 IF POINT(X,Y)<0 THEN POKE 1024+INT(Y/2)*32+INT(X/2),143
To time the difference, I added these extra lines:
15 TIMER=0
and:
45 PRINT TIMER
This lowers execution time from 188 to 163 timer units, i.e., down to 87% of the original time.
– Sebastian Tepper, 7/5/2022
Any time I see TIMER in the mix, I get giddy.
Spaces had been removed, 0 was changed to . (which BASIC will see a much faster-to-parse zero), and integer values were changed to base-16 hex values.
Also, in doing speed tests about the number format I verified that using hexadecimal numbers was more convenient only when the numbers in question have two or more digits.
– Sebastian Tepper, 7/5/2022
Awesome!
Perhaps final improvement could be to change the screen memory location from 1024/&H400 to a variable set to that value, the multiplication value of 32/&h20, as well as the 143/&H8F. Looking up a variable, if there are not too many of them in the list before the ones you’re looking up, can be even faster.
Using the timer value of 163 for our speed to beat, first I moved that extra space just to see if it mattered. No change.
Then I declared three new variables, and used DIM to put them in the order I wanted them (the A in the FOR/NEXT loop initially being the last):
11 DIM S,C,W,A
12 S=1024:W=32:C=143
...
100 IFPOINT(X,Y)<.THENPOKES+INT(Y/2)*W+INT(X/2),C
101 RESET(X,Y):RETURN
No change. I still got 163. So I moved A to the start. A is used more than any other variable, so maybe that will help:
11 DIM A,S,C,W
No change — still 163.
Are there any other optimizations we could try? Let us know in the comments.
Thank you for this contribution, Sebastian. I admire your attention to speed.
In part 4 of this series, Jason Pittman provided several variations of creating the attract screen:
Jason Pittman variation #1
If those four corners bother you, then my attempt will really kick in that OCD when you notice how wonky the colors are moving…
Jason Pittman
10 CLS0:C=143:PRINT@268,"ATTRACT!";
20 FOR ZZ=0TO1STEP0:FORX=0TO15:POKEX+1024,C:POKEX+1040,C:POKE1535-X,C:POKE1519-X,C:POKE1055+(32*X),C:POKE1472-(32*X),C:GOSUB50:NEXT:GOSUB50:NEXT
50 C=C+16:IF C>255 THEN C=143
60 RETURN
Jason Pittman variation #2
Also, another option using the substrings might be to fill the sides by printing two-character strings on the 32nd column so that a character spills over to the first column of the next line:
Jason Pittman
10 CLS 0:C=143:OF=1:CH$=""
20 FOR X=0TO40:CH$=CH$+CHR$(C):GOSUB 90:NEXT
30 FOR ST=0TO1STEP0
40 PRINT@0,MID$(CH$,OF,31):GOSUB 120
50 FORX=31TO480STEP32:PRINT@X,MID$(CH$,OF,2);:GOSUB 120:NEXT
60 PRINT@481,MID$(CH$,OF,30);:GOSUB120
70 NEXT
80 REM ADVANCE COLOR
90 C=C+16:IF C>255 THEN C=143
100 RETURN
110 REM ADVANCE OFFSET
120 OF=OF+2:IF OF>7 THEN OF=OF-8
130 RETURN
Jason Pittman variation #3
One more try at O.C.D-compliant “fast”:
Jason Pittman
10 DIM CL(24):FORX=0TO7:CL(X)=143+(X*16):CL(X+8)=CL(X):CL(X+16)=CL(X):NEXT
20 CLS0:FORXX=0TO1STEP0:FORYY=0TO7:FORZZ=1TO12:READPO,CT,ST,SR:FOR X=SRTOSR+CT-1:PO=PO+ST:POKE PO,CL(X+YY):NEXT:NEXT:RESTORE:NEXT:NEXT
180 REM POSITION,COUNT,STEP,START
190 DATA 1024,8,1,0,1032,8,1,0,1040,8,1,0,1048,6,1,0,1055,8,32,6,1311,6,32,6,1535,8,-1,4,1527,8,-1,4,1519,8,-1,4,1511,6,-1,4,1504,8,-32,2,1248,6,-32,2
The #3 variation using DATA statements is my favorite due to its speed. Great work!
The need for speed: Some assembly required.
It seems clear that even the fastest BASIC tricks presented so far are still not as fast as an attract screen really needs to be. When this happens, assembly code is the solution. There are also at least two C compilers for Color BASIC that I need to explore, since writing stuff in C would be much easier for me than 6809 assembly.
Shortly after part 4, I put out a plea for help with some assembly code that would rotate graphical color blocks on the 32 column screen. William “Lost Wizard” Astle answered that plea, so I’ll present the updated routine in his LWASM 6809 compiler format instead of as an EDTASM+ screen shot in the original article.
* lwasm attract32.asm -fbasic -oattract32.bas --map
org $3f00
start
ldx #1024 X points to top left of 32-col screen
loop
lda ,x+ load A with what X points to and inc X
bpl skip if not >128, skip
adda #16 add 16, changing to next color
ora #$80 make sure high gfx bit is set
sta -1,x save at X-1
skip
cmpx #1536 compare X with last byte of screen
bne loop if not there, repeat
sync wait for screen sync
rts done
END
The code will scan all 512 bytes of the 32-column screen, and any byte that has the high bit set (indicating it is a graphics character) will be incremented to the next color. This would allow us to draw our attract screen border one time, then let assembly cycle through the colors.
How it works:
The X register is loaded with the address of the top left 32-column screen.
The A register is loaded with the byte that X points to, then X is incremented.
BPL is used to skip any bytes that do not have the high bit set. This optimization was suggested by William. An 8-bit value can be treated as an unsigned value from 0-255, or as a signed value of -127 to 128. A signed byte uses the high bit to indicate a negative. Thus, a positive number would not have the high bit set (and is therefor not in the 128-255 graphics character range).
If the high bit was set, then 16 is added to A.
ORA is used to set the high bit, in case it was in the final color range (240-255) and had 16 added to it, turning it in to a non-graphics block. Setting the high bit changes it from 0-16 to 128-143.
The modified value is stored back at one byte before where X now points. (This was another William optimization, since originally I was not incrementing X until after the store, using an additional instruction to do that.)
Finally, we compare X to see if it has passed the end of screen memory.
If it hasn’t, we do it all again.
Finally, we have a SYNC instruction, that waits for the next screen interrupt. This is not really necessary, but it prevents flickering of the screen if the routine is being called too fast. (I’m not 100% sure if this should be here, or at the start of the code.)
The LWASM compiler has an option to generate a BASIC program full of DATA statements containing the machine code. You can then type that program in and RUN it to get this routine in memory. The command line to do this is in the first comment of the source code above.
10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 16128,16147,142,4,0,166,128,42,6,139,16,138,128,167,31,140,6,0,38,241,19,57,-1,-1
The program loads at $3f00 (16128), meaning it would only work on a 16K+ system. There is no requirement for that much memory, and it could be loaded anywhere else (even on a 4K system). The machine code itself is only 20 bytes. Since the code was written to be position independent (using relate branch instructions instead of hard-coded jump instructions), you could change where it loads just by altering the first two numbers in the DATA statement (start address, end address).
For instance, on a 4K CoCo, memory is from 0 to 4095. Since the assembly code only uses 20 bytes, one could load it at 4076, and use CLEAR 200,4076 to make sure BASIC doesn’t try to overwrite it. However, I found that the SYNC instruction hangs the 4K CoCo, at least in the emulator I am using, so to run on a 4K system you would have to remove that.
Here is the BASIC program modified for 4K. I added a CLEAR to protect the code from being overwritten by BASIC, changed the start and end addresses in the data statements, and altered the SYNC code to be an RTS (changing SYNC code of 19 to a 57, which I knew was an RTS because it was the last byte of the program in the DATA statements). This means it is wasting a byte, but here it is:
5 CLEAR 200,4076
10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 4076,4095,142,4,0,166,128,42,6,139,16,138,128,167,31,140,6,0,38,241,57,57,-1,-1
Using the code
Lastly, here is an example that uses this routine. I’ll use the BASIC loader for the 32K version, then add Jason’s variation #1 to it, modified by renaming it to start at line 100, and removing the outer infinite FOR Z loop so it only draws once. I’ll then add a GOTO loop that just executes this assembly routine over and over.
5 CLEAR 200,16128
10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 GOTO 100
80 DATA 16128,16147,142,4,0,166,128,42,6,139,16,138,128,167,31,140,6,0,38,241,19,57,-1,-1
100 CLS0:C=143:PRINT@268,"ATTRACT!";
120 FORX=0TO15:POKEX+1024,C:POKEX+1040,C:POKE1535-X,C:POKE1519-X,C:POKE1055+(32*X),C:POKE1472-(32*X),C:GOSUB150:NEXT:GOSUB150
130 EXEC 16128:GOTO 130
150 C=C+16:IF C>255 THEN C=143
160 RETURN
And there you have it! An attract screen for BASIC that uses assembly so it’s really not a BASIC attract screen at all except for the code that draws it initially using BASIC.
I think that about covers it. And, this routine also looks cool on normal 32-column VDG graphics screens, too, causing the colors to flash as if there is palette switching in use. (You can actually palette switch the 32-column screen colors on a CoCo 3.)
Addendum: WAR by James Garon
On 7/2/2022, Robert Gault posted to the CoCo list a message titled “Special coding in WAR“. He mentioned some embedded data inside this BASIC program. You can download it as a cassette or disk image here:
You can even go to that link and click “Play Now” to see the game in action.
I found this particularly interesting because this BASIC program starts with one of the classic CoCo attract screens this article series is about. In the program, the author did two tricks: One was to embed graphics characters in a PRINT statement, and the other was to embedded a short assembly language routine in a string that would cycle through the screen colors, just like my approach! I feel my idea has been validated, since it was already used by this game in 1982. See it in action:
And if you are curious, the code in question starts at line 60000. I did a reply about this on the CoCo mailing list as I dug in to what it is doing. That sounds like it might make a part 6 of this series…
In 1994, Sub-Etha Software released Invaders09 – a Space Invaders-style game for the CoCO 3.
Invaders09 by Sub-Etha SoftwareInvaders09 by Sub-Etha Software
The game was written in 6809 assembly, and ran under Microware OS-9 Level 2.
Jamie Cho took on the task of porting the game to the MM/1, a “CoCo 4” system that ran OS-9/68000.
Years later, he ported the MM/1 version to run on classic MacOS (on the original 68000 series of processors).
This led to the game being re-ported to the Mac OS for PowerPC, then Intel x86, and finally Apple’s ARM-based M1 series of processors.
Invaders 09 was originally written in 6809 assembly language for the Tandy Color Computer 3 running OS-9 Level 2. I ported it to C on the MM/1 running OS-9 68K sometime around 1995 or so and eventually to the Mac in the early 2000s or so. This means the game has successfully run more or less natively on 5 different platforms – the 6809, 68K, PowerPC, x86 and ARM. This also explains the kind of weird way the bitmaps are drawn to the screen…
– Jamie Cho on his GitHub page.
Downloading MacInvaders09
You can download the source code for the current release from his GitHub:
(Note, this leads to the current 1.0.6 build. If that link doesn’t work, check his main page for a later release.)
Running MacInvaders09
After opening the archive file, you will see the “MacInvaders09.app” application. If you try to run it, you will get this warning:
Click OK on that box to dismiss it. Go in to “System Preferences”, then in to the “Security & Privacy” section. It will look like this:
You can then click “Open Anyway” to allow this program to run. You should then see the same warning box, but now you can “Open” the program to run it.
Playing MacInvaders09
The game will open in a tiny size, matching its 1994 CoCo 3 release:
You can go full screen if you actually want to see it on a modern sized monitor:
Differences from the CoCo 3 original
Jamie’s port is more of an update/rewrite than a straight port. Trying to port 6809 assembly to C doesn’t make a lot of sense. Instead, the game graphics were brought over, and likely some of the logic. There are some significant differences:
The laser shots were enhanced to vertical lines instead of just dots. Nice.
There are new sound effects added. Also nice.
The Power level seems to be missing. (I’m not sure if the game increases the number of simultaneous shots you get as levels progress.)
There is no joystick support.
As far as I know, the undocumented “cheat mode” is not implemented, nor is the “zoop mode” which made the game play too fast on the CoCo 3.
The Invader graphics are upside down from the original. You will notice they have some dots at the top of them. In the CoCo 3 original, I looked for a “hit” by seeing is my bullet dot encountered a non-empty screen byte. I put those dots there just to give it a target at the bottom of the invader. I didn’t like the way they looked, but I didn’t know a better way to do it at the time.
The UFO in the original would always drop a bomb the moment it was above the player, forcing the player to NEVER sit still as the UFO passes. In this version, the UFO seems to drop bombs in a more random pattern.
The UFO bombs can be stopped by the shields. In the original, the UFO bomb would go THROUGH the shields, forcing the player to not hide under shields. I always through it was unfair that the player could just sit in one spot and nothing could hurt them there.
The player is allowed to move while shooting. In the original, any time you fired the laser, your movement was stopped. This prevented the player from doing “run and shoot” moves, making it a bit harder since you couldn’t take a pot shot as you scooted across the screen.
The damage taken to the shields is different. In the original, the blocks kind of pushed outwards, with the first hit taking a small dent out of the shield, and another hit in that location making the dent wider. This version takes longer bits out of the shield, matching the longer laser bolts.
I think the Invaders may move down at a slower rate. It seems much easier to clear them all out and have one left that is still very high, but I’d have to play them side by side to really compare that.
I am quite impressed and honored that Jamie has taken time to do these ports. I can’t express how thrilled I was the first time I saw this on an MM/1, and even more so on an Apple Macintosh.
Thank you, Jamie Cho, for your efforts to make defending the Earth a cross-platform activity!
I did some updates to this site a year or so back, and found out it broke images in all my older posts. I think I fixed those last week, so if you find any broken images, please drop a comment on that post so I can look at them.
Code listings are still quite busted, and I have to fix them manually — if I can. Some seem to have lost data and I have no idea what was there. But, if you catch any of those that are broken, leave a comment as well and I’ll fix if I can.
I hate it when this happens… It looks like a 4TB drive in my 5-bay Drobo has gone out. Drobo cannot detect it. I have dual-drive redundancy enabled, so two drives can fail and I’d still have my data… Fortunately.
Drobo 5C showing a 4TB drive failure.
Hopefully, I won’t have two drives fail between now and the time my replacement drive arrives. :)
On the plus, drive prices have dropped since I bought these drives in 2019. I’ll begin the process of upgrading drives to 6TB models over coming months, money permitting.