Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, Swift, BASIC, and whatever else I happen to feel like hacking at.

Create Color Computer binaries from hex values

Jerry Stratton, December 30, 2020

The December 1987 issue of Rainbow has a very interesting program from John Mosley that purports to be “a four-voice music and graphics program” that works with the CoCo 1 and 2. It consists of a short BASIC program to create the graphics, and a shorter BASIC program to allow the reader to type in the hexadecimal values for the music, one value at a time.

Screw up one number, and you have to start over, this time not screwing up elsewhere. Or you have to understand POKEing enough to rePOKE just the numbers you screwed up, and know where they are—probably by writing a BASIC program to display values in memory and ask you if they’re correct, one by one.

It seemed more reasonable to type all of the hexadecimal numbers into a text file, and then use a script to create the BIN file; if there’s an error, fix only that error in the text file instead of retyping everything.

The format of BIN files turns out to be fairly simple. From Walter Zydhek’s Disk BASIC Unravelled II, there is a preamble and a postamble, both five bytes long. The preamble contains the length of the data and the address the data should be loaded to:

ByteValuePurpose
000Preamble flag
1-2XXXXLength of data block
3-4XXXXLoad address

The postamble contains the execution (EXEC) address:

ByteValuePurpose
0FFPostamble flag
1-20000Dummy value
3-4XXXXExecution address

The script accepts the following arguments:

load addressdecimal or hex address for location of binary data in CoCo RAM
exec addressdecimal or hex address for starting execution; defaults to load address
filenamesfile[s] to pull data from; data can also be piped
--basicthe text is a BASIC file; pull hex values from DATA lines
--columns <column count>verify that each line contains a specific column count
--helpprint this text
--quietdo not output bin data
--verboseprovide information about the binary program

There’s some ambiguity in specifying the load and execution addresses. If the address is four characters long, the script assumes it is a hexadecimal number. I did this because my sense is that these numbers are usually provided as hexadecimal numbers. The script will provide a warning if it makes this assumption on a number that is all digits with no letters.

If you’re specifying a decimal number that happens to be four characters long, prepend the number with a zero to make it five characters long, to ensure that it is converted from decimal to hexadecimal.

The files consist of hexadecimal numbers. If you don’t specify a column count, the lines can each contain any number of numbers.

20220E8E400EC844454ED
818C43FE2FF5863F1F8BB6FF
184F7B7FF1B6FF384F7B7
FF3863CB7FF233411A508E
440A680B73FCDEC8197C0D7
C3EC8197C6D7C9201681FE26
10EC8497C0D7C3EC297C6D7
C9A6497CD308A6802BE627

The file can also contain comments, denoted by pound symbols. The script skips over any line beginning with “#”.

You can specify files by filenames (and it will accept multiple filenames, using them in order), or by piping data to the script.

If a number of columns is provided, the script verifies that each line (except the final line) contains that many hexadecimal numbers. If any line except the final line contains a different number of items, the script dies with an error message. The final line is allowed to contain a different number, because there often aren’t enough numbers to fill out the final line.

The script outputs to STDOUT, so to save it to a file use the redirect symbol “>” to redirect to the desired filename.

Here’s how I created the BIN file for Mosley’s program using this script, and copied it to a TEST.DSK file using Trash Tools:

  • cocobin ml\ song.txt 16128 > "ml song.bin"
  • decb kill test.dsk,ML\ SONG.BIN
  • decb copy -b -2 ml\ song.bin test.dsk,ML\ SONG.BIN

You could also send it to XRoar (which is what I used to create the video) using xroar ml\ song.bin.

Mosley’s ml song.bin loads to and executes from 16128. That’s 3F00 in hexadecimal:

Load address:3F00 (16128)
EXEC address:3F00 (16128)
Length:102C (4140)

If you have the hex values in DATA lines in a BASIC program, you can use that BASIC file as your source; add the --basic argument to the script and it will ignore non-DATA lines, and will assume that all DATA elements are hex values.

Here’s the full script:

[toggle code]

  • #! /usr/bin/perl
  • # Convert text file of HEX values to Color Computer .BIN file
  • # Jerry Stratton astoundingscripts.com
  • # BIN file format from Walter K. Zydhek's Disk BASIC Unravelled II
  • # Preamble:
  • # BYTE 0 00 Preamble Flag
  • # BYTEs 1-2 Length of data block
  • # Bytes 3-4 Load address
  • # Postamble:
  • # Byte 0 FF Postamble flag
  • # Byte 1-2 0000
  • # Byte 3-4 EXEC address
  • while ($option = shift) {
    • if (-f $option) {
      • $files[$#files+1] = $option;
    • } elsif ($option =~ /^[0-9a-f]{4}/i || $option =~ /^[0-9]+$/) {
      • print STDERR "Assuming $option is a hexadecimal number\n" if length($option) == 4 && $option =~ /^[0-9]+$/;
      • $option = sprintf("%04X", $option) if length($option) != 4 && $option =~ /^[0-9]+$/;
      • if (!$loadAddress) {
        • $loadAddress = $option;
      • } elsif (!$execAddress) {
        • $execAddress = $option;
      • } else {
        • die("Unknown address $option");
      • }
    • } elsif ($option eq '--basic') {
      • $basicCode = 1;
    • } elsif ($option eq '--columns') {
      • die('--columns requires a column count') if $#ARGV < 0 || $ARGV[0] !~ /[1-9][0-9]*/;
      • $columnCount = shift;
    • } elsif ($option eq '--help') {
      • help();
    • } elsif ($option eq '--quiet') {
      • $quiet = 1;
    • } elsif ($option eq '--verbose') {
      • $verbose = 1;
    • } else {
      • die("Unknown option $option");
    • }
  • }
  • die("Load and Exec addresses required.") if !$loadAddress;
  • $execAddress = $loadAddress if !$execAddress;
  • @ARGV = @files;
  • while (<>) {
    • chomp;
    • $lineCount = 0 if $ARGV ne $file;
    • $file = $ARGV;
    • $lineCount++;
    • next if /^#/;
    • next if /^$/;
    • if ($basicCode) {
      • next if !/^[1-9][0-9]* DATA/;
      • s/^[1-9][0-9]* DATA *//;
    • }
    • #verify that the *previous* line contained the correct amount of columns
    • #this ensures that the final line does not get checked, as it often contains fewer columns
    • die($#hexes+1, " columns in line $lineCount ($file) with text: $_") if $#hexes >= 0 && $columnCount && $#hexes != $columnCount-1;
    • @hexes = split(/[ ,\t]+/);
    • foreach $hex (@hexes) {
      • next if $hex =~ /^$/;
      • die("Invalid hexadecimal: $hex in line $lineCount ($file) with text: $_") if $hex !~ /^[0-9a-f]{1,2}$/i;
      • $code .= dehexByte($hex);
    • }
  • }
  • $length = sprintf("%04X", length($code));
  • if ($verbose) {
    • print STDERR "Load address: $loadAddress (${\hex($loadAddress)})\n";
    • print STDERR "EXEC address: $execAddress (${\hex($execAddress)})\n";
    • print STDERR "Length: $length (${\hex($length)})\n";
  • }
  • $length = twoByteDehexer($length);
  • $load = twoByteDehexer($loadAddress);
  • $exec = twoByteDehexer($execAddress);
  • $preamble = chr(0) . $length . $load;
  • $postamble = chr(255) . chr(0) . chr(0) . $exec;
  • $code = $preamble . $code . $postamble;
  • print $code if !$quiet;
  • sub twoByteDehexer {
    • my $hex = shift;
    • my $hex1 = dehexByte(substr($hex, 0, 2));
    • my $hex2 = dehexByte(substr($hex, 2, 2));
    • return "$hex1$hex2";
  • }
  • sub dehexByte {
    • my $hex = shift;
    • $hex = "0$hex" if length($hex) < 2;
    • my $char1 = substr($hex, 0, 1);
    • my $char2 = substr($hex, 1, 1);
    • $char1 = dehexChar($char1);
    • $char2 = dehexChar($char2);
    • $dec = $char1*16+$char2;
    • return chr($dec);
  • }
  • sub dehexChar {
    • my $char = shift;
    • $char = uc($char);
    • if ($char == 0 && $char ne '0') {
      • $char = ord($char) - ord('A') + 10;
    • }
    • return $char;
  • }
  • sub help {
    • print "$0 <load address> [exec address] [filenames] [--columns <column count>] [--help] [--verbose]\n";
    • print "\tload address: decimal or hex address for location of binary data in CoCo RAM\n";
    • print "\texec address: decimal or hex address for starting execution; defaults to load address\n";
    • print "\tfilenames: file[s] to pull data from; data can also be piped\n";
    • print "\t--basic: data file is a text BASIC file; pull hex values from DATA lines\n";
    • print "\t--columns <column count>: verify that each line contains a specific column count\n";
    • print "\t--help: print this text\n";
    • print "\t--quiet: do not output bin data\n";
    • print "\t--verbose: provide information about the binary program\n";
    • exit();
  • }

Currently I’ve used this on all of two files—Mosley’s and a program for adding 8k to BASIC’s available RAM1. If I find more uses for it, it is likely to change considerably to meet whatever new needs come up. If it turns out to be too annoying to have to preface four-digit decimal numbers with a zero, I might require that hexadecimal numbers be prefaced with an ‘H’ or ‘x’, for example. It’s also likely that some binaries were provided as decimal instead of hexadecimal numbers.

In response to TRS-80 Color Computer Programming Tools: The TRS-80 Color Computer was a fascinating implementation of the 6809 computer chip, and was, from the Color Computer 1 through 3, possibly the longest-running of the old-school personal computers.

February 13, 2021: Color Computer binaries from decimal values
Space Hawk: Screenshot of Rodger Smith’s Space Hawk game from Hot CoCo, February, 1985.; Color Computer; CoCo, TRS-80 Color Computer; retro computer games; 8-bit computer games; Hot CoCo

At the end of my post on the first version of cocobin, I wrote:

It’s also likely that some binaries were provided as decimal instead of hexadecimal numbers.

And only a few weeks later, here I am. I found a really nice Galaxian/Space Invaders-style game by Rodger Smith in the February 1985 Hot CoCo.1

He used decimal numbers for his DATA statements, so I added that feature to cocobin. If a file or BASIC program contains decimal rather than hexadecimal numbers, add the option --decimal to the cocobin command line.

Also as expected, adding this feature also highlighted another common custom of the era: the DATA items often included a marker to denote the end of the data. Most commonly, as I recall, this was the word “END”, or (for machine code) a negative 1. Smith used the number 999. So I’ve added the ability to recognize “END”, “999”, or “-1” at the end of a line of DATA in a BASIC program. If the program sees any of those key words at the end of a DATA line, it assumes that that is the end of the data to be read.

Either of those keywords not at the end of a DATA line will still be seen as an error, since none of them represent valid POKEable numbers.

The script now accepts the following arguments:

load addressdecimal or hex address for location of binary data in CoCo RAM
exec addressdecimal or hex address for starting execution; defaults to load address
filenamesfile[s] to pull data from; data can also be piped
--basicthe text is a BASIC file; pull hex values from DATA lines
--columns <column count>verify that each line contains a specific column count
--decimalthe numbers are decimal numbers, not hexadecimal
--helpprint this text
--quietdo not output bin data
--verboseprovide information about the binary program

Also, while this is not a change, I did finally verify that the script works when used with multiple files. Charles Husak’s “The Little Runner” from the March 1984 Rainbow uses three BASIC programs to POKE the binary into memory. This command line worked to create a working binary from those three files:

  • cocobin --basic 13000 RUNNER*.BAS > RUNNER.BIN
  1. The latter is Ray Gauvreau’s “A Bigger Byte for Basic” from the January 1984 Rainbow, which is a bit tricky since it’s designed specifically for non-Disk BASIC. This program is why I added the option to pull hex values from BASIC DATA lines.

  1. <- Hunt the Wumpus
  2. Read BASIC ->