Vaughn Cato’s bitmap scaling demo.

In a follow-up to my recent post about Vaughn Cato’s 3-D vector maze test for the Radio Shack Color Computer 3, there was another bit of code he sent me: a bitmap scaling test. He created a routine that would take a bitmap graphic then render it on the screen at a different width and/or height. I forget why I was interested in this topic back then, but I do know I have blogged about “scaling” on the CoCo in recent years on this site.

Here is a video of his mapgraph test program running:

The routine appears to use 6809 assembly language and C code. The idea would be you could have a bitmap image then place it on the screen at different sizes. This demo just loops “stretching” a test pattern image, but the graphic could have been anything.

One more post to come…

I may never use a #define in C again. Except when I need to.

I have had a few glorious weeks of C coding at my day job. It is some of the best C code I have ever written, and I am quite proud of it.

During this project, I learned something new. In the past, I would commonly use #define for values such as:

#define NONE 0
#define WARNING 1
#define ERROR 2

There might be some code somewhere that made us of those values:

void HandleError (int error)
{
   case NONE:
      // All fine.
      break;

   case WARNING:
      // Handle warning.
      break;

   case ERROR:
      // Handle error
      break;

    default:
      // Handle unknown error.
      break;
}

When you use #define like that, the C pre-processor will just do simple substitutions before compiling the code — “NONE” will become “0”. Because of this, those labels mean nothing special to the compiler — they are just numbers.

But, I had learned you could use an enum and ensure a function only allows passing the enum rather than “whatever int”:

typedef enum
{
   NONE = 0,
   WARNING = 1,
   ERROR = 2
} ErrorEnum;

Now the routine can take an “ErrorEnum” as a parameter type.

void HandleError (ErrorEnum error)
{
   case NONE:
      // All fine.
      break;

   case WARNING:
      // Handle warning.
      break;

   case ERROR:
      // Handle error
      break;

    default:
      // Handle unknown error.
      break;
}

This looks nicer, since it is clear what is being passed in (whatever “ErrorEnum” is). It does not necessarily give any extra compiler warnings if you pass in an int instead, since an enum is just an int, by default.

In C, an enum is a user-defined data type that represents a set of named values. By default, the underlying data type of an enum is int, but it can be explicitly specified to be any integral type using a colon : followed by the desired type.

– ChatGPT

Thus, the compiler I am using for this test (Visual Studio, building a C program) does not complain if I do something like this:

int error = 1;
HandleError (error);

However, I just learned of an interesting benefit to using an enum for a switch/case. The compiler can warn you if you don’t have a case for all the items in the enum!

typedef enum {
	NONE,
	WARNING,
	ERROR
} ErrorEnum;

void HandleError(ErrorEnum error)
{
	switch (error)
	{
	case NONE:
		// All fine.
		break;

	case WARNING:
		// Handle warning.
		break;

}

Building that with warnings enabled reports:

enumerator 'ERROR' in switch of enum 'ErrorEnum' is not handled	CTest	

How neat! I actually ran in to this when I had added some extra error types, but a routine that converted them to a text string did not have cases for the new errors.

const char *GetErrorString (ErrorEnum error)
{
   const char *errorStringPtr = "Unknown";

   switch NONE:
      errorStringPtr = "None";
      break;
}

That is a useful warning, and it found a bug/oversight.

Because of this, I will never use #defines for things like this again. Not only does a data type look more obvious in the code, the compiler can give you extra warnings.

Until next time…

Vaughn Cato’s “Toast” 3-D maze engine for CoCo OS-9 Level 2

Wayback in the 1990s, Vaughn Cato and I were corresponding, and somehow some topics came up about rendering a 3-D maze on the CoCo.

Vaughn wrote a demo which was called toast for reasons I no longer remember. It parsed a text file representing the maze, and would let you “walk” through it using the arrow keys. The code was written in C (though there may be some assembly helper routines in there, I haven’t checked) for the Radio Shack Color Computer 3 under the OS-9 operating system.

Here is a quick demo of what toast looked like:

Here is the maze data file:

18 16
# ##########
# #
# #
########### #
# #
# #
# #
# #
# #
# #
##### ######
# #
# #
# #
# #
############

The readme included with this version had the following notes:

Here is the latest version of the maze program.  I think that it now
always displays the walls correctly, but let me know if you find a problem.

I have also included a demo of the bitmap scaling routine. Just run
mapgraph to try it out.

Until Later,
Vaughn Cato ([deleted]@st6000.sct.edu)

I will share what mapgraph does in a future post ;-)

I plan to reach out to Vaughn and ask if he would be okay with sharing the source code for this.

Until then…

Parsing message bytes in to a C structure (and back)

In a recent article, I discussed methods to copy variables (or structure elements) in to a packed buffer, as well as parse buffer bytes back in to variables.

Doing it manually was clunky. Using my GetPutData routines made it easier to be clunky.

But today, we’ll make it super simple and far less clunky.

Defining the message bytes

Let’s start with a simple message. It will be represented by a C structure that we’d use in the program. For example, here are the elements that might be part of a “Set Date and Time” message:

typedef struct
{
    uint16_t year;
    uint8_t month;
    uint8_t day;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
    uint8_t timeZone;
    bool isDST;
} SetDateTimeMessageStruct;

The packed message would be nine bytes and look like this:

[ 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 ]
  \  /    |   |   |   |   |   |   |
  year   mon day hr  min sec tz  dst

To automate this process, a lookup table is created that represents the size of each element and the offset to where it is located within the structure.

offsetof()

Inside stddef.h is a macro that is used to calculate where in the structure’s memory a particular element exists. It is a relative offset from wherever the structure variable starts in memory. Here is an example showing offsetof():

#include <stdio.h>
#include <stdlib.h>  // for EXIT_SUCCESS
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>  // for offsetof()

typedef struct
{
    uint8_t     a;
    uint16_t    b;
    bool        c;
    uint32_t    d;
    float       e;
    double      f;
} MyStruct;

int main()
{
    MyStruct test;
    
    printf ("sizeof(test) = %ld\n", sizeof(test));
    
    printf ("a is %lu bytes at offset %lu\n",
            sizeof(test.a), offsetof(MyStruct, a));
    printf ("b is %lu bytes at offset %lu\n",
            sizeof(test.b), offsetof(MyStruct, b));
    printf ("c is %lu bytes at offset %lu\n",
            sizeof(test.c), offsetof(MyStruct, c));
    printf ("d is %lu bytes at offset %lu\n",
            sizeof(test.d), offsetof(MyStruct, d));
    printf ("e is %lu bytes at offset %lu\n",
            sizeof(test.e), offsetof(MyStruct, e));
    printf ("f is %lu bytes at offset %lu\n",
            sizeof(test.f), offsetof(MyStruct, f));

    return EXIT_SUCCESS;
}

Try it: https://onlinegdb.com/GDvhi9TLq

It produces the following output:

sizeof(test) = 24
a is 1 bytes at offset 0
b is 2 bytes at offset 2
c is 1 bytes at offset 4
d is 4 bytes at offset 8
e is 4 bytes at offset 12
f is 8 bytes at offset 16

This is an example of how elements in a structure may be padded to ensure each one starts on an even-byte in memory. ‘a’ is one byte starting at offset 0, but ‘b’ doesn’t start at 1. As a 16-bit value, it has to skip a byte and start at offset 2. There are more skipped bytes after ‘c’ at offset 4. Since the next value is a 32-bit value, it skips three bytes.

If we just wrote the structure memory out (in this architecture), it would look like this:

[ 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|16|17|18|19|20|21|22|23]
  |     |     |           |           |           |
  a     b--b  c           d--d--d--d  e--e--e--e  f--f--f--f--f--f--f--f

That would waste 4 bytes in that 24 byte message. Plus, if the message were parsed by a system that had different padding/alignment requirements, they wouldn’t be able to read the buffer back in to their structure variable and get the correct data.

Using offsetof() to determine where in a structure an element exists, a lookup table could be created. A C function could be written to use that table to know where to copy the data. Here’s a rough concept:

typedef struct
{
    size_t size;   // size of structure element
    size_t offset; // offset from start of structure to that element
} ElementOffsetStruct;

MyStruct test;

ElementOffsetStruct testTable[] =
{
    { sizeof(test.a), offsetof(MyStruct, a) },
    { sizeof(test.b), offsetof(MyStruct, b) },
    { sizeof(test.c), offsetof(MyStruct, c) },
    { sizeof(test.d), offsetof(MyStruct, d) },
    { sizeof(test.e), offsetof(MyStruct, e) },
    { sizeof(test.f), offsetof(MyStruct, f) },
    { 0, 0 } // end of table
};

for (int idx=0; testTable[idx].size != 0; idx++)
{
    printf ("? is %u bytes at offset %u\n",
            testTable[idx].size, testTable[idx].offset);
}

Knowing where items are in the structure allows the specific element bytes to be copied to a buffer. Something like this:

uint8_t buffer[80];
unsigned int offset;

offset = 0;
for (int idx=0; testTable[idx].size != 0; idx++)
{
    printf ("Copy %lu bytes at structure offset %lu to buffer offset %u\n",
            testTable[idx].size, testTable[idx].offset, offset);

    memcpy (buffer + offset,
        (uint8_t*)&test + testTable[idx].offset,
        testTable[idx].size);
        
    offset = offset + testTable[idx].size; 
}

“And it’s just that easy.” Running the following test program dumps the memory contents of the C structure “test” and then writes them out to a buffer using this code and then dumps the memory contents of the buffer.

#include <stdio.h>
#include <stdlib.h>  // for EXIT_SUCCESS
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>  // for offsetof()
#include <string.h>  // for memcpy()

typedef struct
{
    uint8_t     a;
    uint16_t    b;
    bool        c;
    uint32_t    d;
    float       e;
    double      f;
} MyStruct;

typedef struct
{
    size_t size;   // size of structure element
    size_t offset; // offset from start of structure to that element
} ElementOffsetStruct;

int main()
{
    MyStruct test = { 0x11, 0x2222, true, 0x33333333, 42.42, 42.42 };
    
    ElementOffsetStruct testTable[] =
    {
        { sizeof(test.a), offsetof(MyStruct, a) },
        { sizeof(test.b), offsetof(MyStruct, b) },
        { sizeof(test.c), offsetof(MyStruct, c) },
        { sizeof(test.d), offsetof(MyStruct, d) },
        { sizeof(test.e), offsetof(MyStruct, e) },
        { sizeof(test.f), offsetof(MyStruct, f) },
        { 0, 0 } // end of table
    };
    
    for (int idx=0; idx<sizeof(test); idx++)
    {
        printf ("%02x ", ((uint8_t*)&test)[idx]);
    }
    printf ("\n");

    uint8_t buffer[80];
    unsigned int offset;
    
    offset = 0;
    for (int idx=0; testTable[idx].size != 0; idx++)
    {
        printf ("Copy %lu bytes at structure offset %lu to buffer offset %u\n",
                testTable[idx].size, testTable[idx].offset, offset);
    
        memcpy (buffer + offset,
            (uint8_t*)&test + testTable[idx].offset,
            testTable[idx].size);
            
        offset = offset + testTable[idx].size; 
    }

    for (int idx=0; idx<offset; idx++)
    {
        printf ("%02x ", buffer[idx]);
    }
    printf ("\n");

    return EXIT_SUCCESS;
}

I get the following output:

11 00 22 22 01 00 00 00 33 33 33 33 14 ae 29 42 f6 28 5c 8f c2 35 45 40 
Copy 1 bytes at structure offset 0 to buffer offset 0
Copy 2 bytes at structure offset 2 to buffer offset 1
Copy 1 bytes at structure offset 4 to buffer offset 3
Copy 4 bytes at structure offset 8 to buffer offset 4
Copy 4 bytes at structure offset 12 to buffer offset 8
Copy 8 bytes at structure offset 16 to buffer offset 12
11 22 22 01 33 33 33 33 14 ae 29 42 f6 28 5c 8f c2 35 45 40 

As I discovered when I figured this out, there are a few gotchas. First, the sample code here requires the structure variable to exist. This is because the macro sizeof() needs to have something to work on to determine the size:

sizeof(test.a)

I asked ChatGPT about this, and it provided me a clever workaround that worked on the structure itself rather than a variable:

sizeof(((MyStruct*)0)->a)

Looking at that, I think I get how it works. First, it is taking a memory location of 0 and casting that to be a pointer to a MyStruct. No such structure exists at memory 0, but you can cast a pointer to anywhere (even 0) as any kind of pointer you want. (Actually using a pointer that point to 0 would be bad, however.)

Then, it gets the offset of the “a” element from the structure we pretend is located at memory location 0. Let’s break that apart a bit:

sizeof(                 )
       (            )->a
        (MyStruct*)0

And this is enough to allow creating a table based on a structure, and not on a variable declared using that structure.

typedef struct
{
    uint8_t u8;
    uint32_t u32;
} SmallStruct;

ElementOffsetStruct smallTable[] =
{
    { sizeof(((SmallStruct*)0)->u8), offsetof(SmallStruct, u8) },
    { sizeof(((SmallStruct*)0)->u32), offsetof(SmallStruct, u32) },
    { 0, 0 }
};

This would allow an easy way to define the structure and a table of offsets for later use. I’d probably make the table structure const since it won’t need to change.

With this framework figured out, a reverse function could be done that would copy bytes from a buffer in to the correct offset inside the structure.

unsigned int offset;
    
offset = 0;
for (int idx=0; testTable[idx].size != 0; idx++)
{
    printf ("Copy %lu bytes at buffer offset %u to structure offset %lu\n",
            testTable[idx].size, offset, testTable[idx].offset);
    
    memcpy ((uint8_t*)&test + testTable[idx].offset,
            buffer + offset,
           testTable[idx].size);
            
    offset = offset + testTable[idx].size; 
}

And now, by creating a structure and a table of size/offsets, any structure can be written to a buffer, or decoded from a buffer.

Too. Much. Typing.

For the final touch, macros can be made to simplify building the table.

#define ENTRY(s, v) { sizeof(((s*)0)->v), offsetof(s, v) }
#define ENTRYEND    {0, 0}

Now, instead of having to type all that gibberish out, the table can be simplified:

ElementOffsetStruct testTable[] =
{
    ENTRY(MyStruct, a),
    ENTRY(MyStruct, b),
    ENTRY(MyStruct, c),
    ENTRY(MyStruct, d),
    ENTRY(MyStruct, e),
    ENTRY(MyStruct, f),
    ENTRYEND
};

And this is just what I have posted to my GitHub:

https://github.com/allenhuffman/StructureToFromBuffer

You can find a short README there that explains the two functions and how to use them. It greatly simplifies the process of creating message buffers or parsing them in to C structures that can easily be used in a program.

If you try it and find it useful, please let me know.

Until then…

The last time I saw Steve Bjork…

On this date (4/10) in 2023, Steve Bjork passed away. I still haven’t quite come to grips with him being gone. The last time I saw him was during a visit to California in 2018. He and his wife joined us for the day at Knott’s Berry Farm. I regret not visiting again when I was in California in 2022 — but I guess I am not the only one that lost touch with folks during the Covid era.

Steve was not real fond of being in photos, which may be why I cannot find any of him from that visit … but he is in this one ;-)

Regrets are easy to accomplish.

I miss my friend. Now that there has been some time since I learned of his passing, I will try to share some stories of my Adventures with Steve.

Until then…

CLS 100 CoCo 3 easter egg explored.

The most famous CoCo easter egg has to be the hidden photo of the CoCo 3 programmers that shows up if you hold down CTRL-ALT while turning the machine on. (Alternatively, you can hold down CTRL-ALT and hit the reset button on the back of the machine to also display it.)

But, there’s another, which I’d bet came first, since it followed in the footsteps of a pre-existing easter egg that was in the original 1980 Color BASIC 1.0 ROM.

CLS 100

The CLS command is used to clear the screen. On the 32-column text screen, specifying a number from 0-8 will fill the screen with color blocks. That functionality was extended on the CoCo 3’s 40 and 80 column text screens, except there it could clear the screen to a true background color.

For the original CoCos, Microware included an easter egg in the CLS command. If you used a number greater than 8, instead of filling the screen with colored blocks it would display the word “MICROSOFT”.

CLS 9 through 255 present a Microsoft easter egg.

When Microware (not Microsoft) did the BASIC enhancements for the CoCo 3, they included a similar easter egg for the 40 and 80 column screens. If you did a CLS number outside of the range of 0-8, it would display “Microware Systems Corp.”

BUT, they added one special easter egg that you can only see once, then it disables itself (until the next power cycle or cold start). By typing CLS 100 you get to see the names of two of the programmers that worked on the project;

From that point on, if you type CLS 100 again you will only see the “Microware Systems Corp.” message.

In addition to making this easter egg a “one shot”, the programmers also took steps to hide their names so they would not be easily found by looking at the ROM code.

From Super Extended BASIC Unravelled, this note can be found in the memory map:

On startup, the ROMs are copied in to RAM, and these zero bytes will be in RAM at that range.

Then, during the CoCo 3 initialization code, there is a routine that copies the author names to that location. BUT, the data it copies is not the authors’ names — it’s an encoded version of the authors’ names:

Each of those bytes has its bits flipped. If the value in binary was 11110000, it would be encoded as 00001111. That effectively hides what those bytes represent from anyone just PEEKing around memory looking for text. There is a routine in the ROM that will start copying those bytes in to the other location, inverting the bits as it does so. It looks like this:

Here is the CLS code, which has special check for the value of 100 that will branch to a different routine:

The routine that is called when 100 is specified will display the easter egg and then wipe out the “Branch If Equal” instruction that calls it by replacing it with the NOP (no operation) instruction. Below is the code that does displays the egg and then disables it:

LF730 gets a pointer to the decoded author name message and displays it, then it starts in memory at F6F4 and stores a NOP byte there, and after it. That changes the two bytes the start out being “BEQ LF730” to “NOP NOP” effectively making the “CMP #100” useless since there is no longer an operation after it to make use of the results of that compare.

After those two bytes are changed to NOP, there is a loop that starts at memory location where AUTHORMS (F71B) is located and starts putting NOP bytes there until it gets to F74D. That destroys the evidence :) by wiping out everything in this table…

and everything after it (the code) up to the LF74D label:

Thus, after CLS 100, the code that would have jumped to the routine is gone, the routine it would have jumped to is gone, and the data that the now gone routine would have displayed is also gone.

Nice.

Here is a short BASIC program that will dump the hex values of the name bytes on up through the code that displays them, then prints the name bytes out as text.

0 ' COCO3NAMES.BAS
10 WIDTH 40
20 FOR L=&HF71B TO &HF74C
30 PRINT HEX$(PEEK(L));" ";
40 NEXT
50 PRINT
60 FOR L=&HF71B TO &HF72F
70 PRINT CHR$(PEEK(L));
80 NEXT

If you boot a CoCo 3 and run this program, you will see it contains the bytes for the name text and the 6809 code:

Then if you type CLS 100 and run the program again you will see that those bytes are now all the NOP byte (&H12). Since this is not a printable character, nothing is printed after the hex values:

And that is more than we probably ever wanted to know about how the CLS 100 CoCo 3 Easter Egg works.

So let’s do a bit more…

Restoring (and customizing) the egg

Just for fun, I wrote this short BASIC program that does three things:

  1. Restore the easter egg text to the memory from &HF71B-&HF72F.
  2. Restore the display routine at &HF730 that prints the easter egg text and then deletes the easter egg text and the routine.
  3. Restore the “branch if equal” that is supposed to happen after comparing the value of CLS to be 100.

And, as an added bonus, there is a line that will insert a “return from subroutine” RTS instruction in the display routine so it will not run the code that wipes out the easter egg text and display routine.

If you have already done a CLS 100, this code would restore the easter egg and let you run it again:

10 ' COCO3EGG.BAS
20 WIDTH 40
30 ' RESTORE EGG TEXT
40 EG$="T.Harris & T.Earles"
50 ' ROOM FOR UP TO 19 CHARS
60 LN=LEN(EG$)
70 IF LN>19 THEN LN=19
80 ' STORE THE EGG TEXT
90 FOR I=0 TO LN-1
100 POKE &HF71B+I,ASC(MID$(EG$,I+1))
110 NEXT
120 ' ADD CR and 0 BYTES
130 POKE &HF71B+I,13
140 POKE &HF71B+I+1,0
150 ' RESTORE DISPLAY ROUTINE
160 FOR L=&HF730 TO &HF756
170 READ V$:POKE L,VAL("&H"+V$)
180 NEXT
190 ' RESTORE CMP #100 "BEQ"
200 POKE &HF6F4,&H27:POKE &HF6F5,&H3A
210 ' DISABLE EGG KILL (RTS)
220 'POKE &HF73D,57
230 END
240 ' DISPLAY ROUTINE DATA
250 DATA 8D,40
260 DATA 17,FF,57
270 DATA 8D,41
280 DATA 8E,F7,1A
290 DATA BD,B9,9C
300 DATA 34,10
310 DATA 30,8D,FF,B1
320 DATA 86,12
330 DATA A7,80
340 DATA A7,84
350 DATA 30,8D,FF,CE
360 DATA A7,80
370 DATA 8C,F7,4D
380 DATA 25,F9
390 DATA 35,10
400 DATA 39

The DATA statements are organized like that to match the bytes shown in the Unravelled book’s listing of this routine. This helped me find and fix typos.

Line 220 is commented out, but that would place an RTS after the “JSR STRINOUT” in the LF730 display routine, causing it to return rather than do all the code that replaces stuff with the NOP bytes.

And, as an added bonus on top of the added bonus, you can change the easter egg string in line 40 to be anything you want, provided it is 19 characters or less. (Anything longer will just be cropped to the first 19 characters.) I changed mine to read “Sub-Etha Software” and had that message as my CLS 100 easter egg.

Until next time…

Klystronics at the 1994 Atlanta CoCoFest – revealed

At the 1994 Atlanta CoCoFest, the following vendors were present:

Booth 20 was unoccupied, as the vendor did not make it. They did leave a stack of flyers explaining their absence. You can read the text of that flyer in the previous post.

Who was Klystronics? Beyond those that knew about it in 1994, the backstory was explained in my CoCoFest Chronicles book which you can download for free at the Color Computer archive.

First, my fest report text file (included in the book) alluded to it in the opening:

NOTE: Any discrepancies between what is contained in this report and what is real is merely coincidental. This includes, but is not limited to, names, locations, events, and inside jokes (like klystrons).

– Allen Huffman’s 1994 Atlanta CoCoFest report text file

That report also mentioned the booth:

Klystronics — Another new vendor. This group was offering a large supply of “gently used” klystrons, complete with manuals. The minimal water damage their inventory had didn’t seem to effect operation, though the yellow discoloration made them no longer match the CoCo’s case.

– Allen Huffman’s 1994 Atlanta CoCoFest report text file

…but the full backstory was not explained until the book itself came out a few years later. From the book:


MORE BEHIND THE SCENES

The Story About Klystronics

One of the more interesting tales I get to tale is that of the “mysterious” vendor, Klystronics. They were listed in the Fest booklet, and even had a table which was empty except for some flyers and an empty business card holder. To the casual observer (i.e. someone who fell for it) it would appear they just didn’t make it to the show. To the less casual observer, one might have found some of the information on the flyer a bit curious.

Klystronics was a practical joke played for the benefit of one individual, Nick Johnson. At the time, Nick was hanging out on the GEnie online service (thanks to its then-outstanding Star*Services which offered unlimited non-prime access for about five dollars per month). Nick had a macro he would use far too often while in chat sessions which said “PLEASE DO NOT URINATE ON THE KLYSTRON.” Terry Todd and myself found this quite annoying.

At some point, our friend Bob Rutherford got together with us and we came up with the idea of Klystronics. Once the name was created, the joke fell into place. We acquired a booth, printed flyers (using a font I had never used for any Sub-Etha literature), and purchased a business card holder.

At the show, Nick wandered over and said “hey, did you guys know there is a company here called Klystronics?” or something to that effect. We held in laughter. He most likely was suspicious but if so he played along pretty well. The flyer had some meaningless babble about CoCo compatible Klystrons, but embedded in the words were numbers enclosed in brackets. If you put the letter next to the numbers together in numerical order you would decode the hidden message which basically clued one in to the fact that this was just a joke. At first Nick suspected something far more clever for the origin of the numbers, but towards the end of the show I do believe he figured it out. I am not quite sure if we ever told him officially that it really was us.

A few others were in on this joke, most notable Colin McKay. After the Fest, Colin played up the existence of Klystronics on the internet CoCo mailing list after someone wondered just who they were. Even Mike Knudsen chimed in observing that certainly they had nothing to do with the “klystron” tubes he was familiar with! He certainly was right. I have in my possession (thanks to Mike’s archives) a copy of the Klystronics message thread and I must say — it really makes me proud seeing how well this went over.

The following Fest, we planned to do more including having Bob Rutherford build a rather amazing device to attach to a CoCo. We never quire followed through, but Klystronics lived on in my Fest report — even though there was no booth nor mention of them in the official Fest booklet.

And now you know … the rest of the story. Maybe the comment about “water damage” will make a bit more sense.

Or, more likely, it will not.


I decided to post the original Kystronics flyer text on April 1, 2026. This was more involved than I expected. First, the raw text file seemed to use some embedded formatting codes. I could not remember what created them:

.pl1
.lm0
.ll137
.nf

This led me to trying to figure out how to export the document in a formatted version to match what I did back in 1994. I was using the text editor VED under OS-9 at the time, and suspected maybe I was using the companion VPT text formatted. After figuring out how to run it off my old OS-9 hard drive image in an emulator, I learned it did not like these codes.

Eventually, I just decided to feed it into A.I. (Copilot) and let it do it for me. It presented me with an ASCII version of document, correctly figuring out what codes mean blank lines, and what codes meant centered lines. BUT, it was 137 columns wide.

What? That wouldn’t even fit if I printed in landscape mode, which wasn’t even a thing on that printer back then.

A bit more research revealed I likely printed this on my Tandy DMP-132 printer in condensed mode, which would indeed allow 137 characters per line with a bit of margin on each side.

This led me to just simulating it by using a condensed dot matrix font and turning the text into a PDF.

That’s close-ish.

Some things seem obvious now…

As I pointed out, I tried to use a font Sub-Etha Software didn’t use for printing flyers, and change things up so it was not immediately obvious I created this. For instance, it spells “disc” with a C and I never did that. It also uses “Coco” without the second C capitalized. But, as I look at it now, I see a few things that would have tipped folks off.

  • First, back then, I typed DOS as “Dos” as if it was a word. So did Klystronics. (And other folks of that era, but still, a hint.)
  • Also, “S.E.S.” Dos and “B.H.” Dos? I’ve never heard of any RS-DOS replacements with those initials, but there were two companies at the fest that had matching initials.
  • I also misspelled separately the same way I’d be misspelling it today if it wasn’t for auto-correct. And so did Klystronics. My Fest reports were full of that misspelling as well as mixing of where/were.
  • Bored member? “lol”. That misspelling was intentional.

The document also has some things that could further tip off the joke.

  • 16K RAM, but also supporting the Hi-Res Joystick Interface? Seems unlikely.
  • Color Computer 2 but NOT Color Computer 1? Why would that be?
  • Also, the name “Graham Cain” alluded to the two individuals behind it. One may be obvious, the other not at all.

But … the biggest tip was that there is also an encoded message hidden in this document. Look at the full text and see if you can decode it. That was the actual reveal.

Until next time…

Klystronics at the 1994 Atlanta CoCoFest

Fine Klystrones Since 1989

HELLO! We are[14] sorry that we were unable to personally attend this[1] year’s CocoFest. We had some commercial accounts we needed to attend to and thus were unavailable. We hope to see you[9] next year at the 1995 Chicago CocoFest or at the upcoming Klystrone Con[4] ’95 in Aneheim.

WHAT IS KLYSTRONICS?

Klystronics, Inc. is an independent organization dealing in the sale and service of both new and used Klystrones.

WHAT IS A KLYSTRONE?

Klystronics, Inc. is an independent organization dealing in the sale and service of both new and used Klystrones.

WHY IS KLYSTRONICS AT A COCOFEST?

One of our[11] bored members, Graham Cain, got his start in the electronics field with a[3] TRS-80 Color Computer which was part of an in-home study course. His projects led him into P.C. interfacing and, along with other business partners, the formation of Klystronics in 1989. Most of the projects were industrial in nature and involved the usage of Klystrone units with commercial grade P.C. components, but Graham, remembering his roots with the Coco, used his knowledge and spare time to create a Coco Klystrone interface. Thus, Klystronics offers it’s services to both industrial users and Color Computer users.

HOW CAN I ORDER A KLYSTRONE FROM KLYSTRONICS?

Since we are[6] unable to take orders personally, you[13] can mail in your order or use our toll free 1-800 telephone ordering service. We accept personal and business checks, credit cards (Visa, MasterCard, Diner’s Club, Discover[10]), money orders, international postage, and C.O.D.s.

KLYSTRONE SYSTEM REQUIREMENTS:

o Color Computer 2 or 3
o Y-Cable of Multi-Pak Interface
o Cassette Cable (if[8] sound desired)
o Floppy Disc Drive (S.E.S./B.H.[5] Dos)

o 16K of R.A.M. or greater
o Hi-Res Joystick Interface
o Color Monitor recommended
o OS-9 Drivers Sold Seperately

TECHNICAL SUPPORT FOR KLYSTRONE QUESTIONS:

Support is[2] provided 24 hours a day via long distance phone calls, or use our free computer bulletin board service (300-1200 baud). We may also be contacted via Prodigy, though response time may be slow. We are not responsible[7] if the message[12] is lost.

CONTACT

Please use the latest address and phone information as listed on our business cards.

FEST SPECIAL[15]!!!!! USED KLYSTRONES (SLIGHT WATER DAMAGE) 37% OFF!!!!!

strcpy versus strncpy and -Wstringop-truncation

I had originally envisioned this post to be another “Old C dog, new C tricks” article, but it really is more of a “how long has it done this? did I just never read the manual?” article.

In C, strcpy() is used to copy a C string (series of character bytes with a 0 byte marking the end of the string) to a new buffer. Some years ago, I wrote a series about the dangers of strcpy() and other functions that look for that 0 byte at the end. I later wrote a follow-up that discussed strcat().

char buffer[10];

strcpy (buffer, "Hello");

Which means something like this can crash your program:

char buffer[10];

strcpy (buffer, "This will cause a buffer overrun.);

strncpy(), on the other hand, takes a third parameter which is the maximum number of characters to copy. Once it reaches that number, it stops copying. If you are copying to a 20-byte buffer, setting this value to 20 will ensure you do not overwrite that 20 byte buffer.

char buffer[20];

strncpy (buffer, "Hello", sizeof(buffer));

But there’s a problem… While strncpy() will blindly copy the source to the destination and then add the terminating 0 byte (without any care or concern to how much room is available at the destination), strncpy() will only add the terminating 0 if the source is smaller than the maximum size value.

This part I was aware of. In my earlier articles, I suggested this as a workaround to ensure the destination is always 0 terminated, even if the source string is as large or larger than the destination buffer:

char buffer[10];

strncpy (buffer, "1234567890", sizeof(buffer)-1); // subtract one
buffer[sizeof(buffer)-1] = '\0'; // 0 terminate

Above, sizeof(buffer) is 10. The copy would copy up to 9 bytes, so it would copy over “123456789”. Then, in the buffer at position 9 (bytes 0 to bytes 9 is ten bytes) it would place the zero.

Problem solved.

But that’s not important to this post.

Instead, there is another behavioral difference I wanted to mention — one that you likely already know. I must have know this and just forgotten. Surely I knew it. It even explains this in the C resource I use when looking up parameters to various C functions:

strncpy() will copy the bytes of the source buffer, and then pad the destination with zeros up until the maximum size value.

#include <stdio.h>
#include <string.h>

#define BUFFER_SIZE 32

void HexDump (const char *label, void *dataPtr, size_t dataSize);

int main()
{
    char buffer1[BUFFER_SIZE] = { 0 };
    char buffer2[BUFFER_SIZE] = { 0 };

    memset (buffer1, 0xff, sizeof(buffer1));
    memset (buffer2, 0xff, sizeof(buffer1));
    
    HexDump ("buffer1", buffer1, sizeof(buffer1));
    HexDump ("buffer2", buffer1, sizeof(buffer1));

    strcpy (buffer1, "Hello");
    strncpy (buffer2, "Hello", sizeof(buffer2));

    HexDump ("buffer1", buffer1, sizeof(buffer1));
    HexDump ("buffer2", buffer2, sizeof(buffer2));
    
    return 0;
}

void HexDump (const char *label, void *dataPtr, size_t dataSize)
{
    printf ("--- %s ---\n", label);
    for (unsigned int idx=0; idx<dataSize; idx++)
    {
        printf ("%02x ", ((unsigned char*)dataPtr)[idx]);
        if (idx % 16 == 15) printf ("\n");
    }
    printf ("\n");
}

Above, this program makes a buffer and sets all the bytes in it to 0xff (just so we can see the results easier).

It then uses strcpy() to copy a string (which will copy up until the 0 byte at the end of the source string), and strncpy() to copy the string to a second butter, with the maximum size specified as the destination buffer size.

Here is the output:

--- buffer1 ---
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

--- buffer2 ---
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

--- buffer1 ---
48 65 6c 6c 6f 00 ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

--- buffer2 ---
48 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

The first “buffer1” and “buffer2” shows the buffers initiated with 0xff’s.

The second “buffer1” shows the result of strcpy() – the five characters of “Hello” copied over, with a 0 byte added.

The second “buffer2” shows the same string copied using strncpy() with the size set to 16, the size of the buffer. You can see it copied the five characters of “Hello” and then filled the rest (up to the size) with 0 bytes.

Was this always the case with strncpy(), or did this get enhanced in later versions of C? I see this is fully documented at places like cplusplus.com:

Copy characters from string

Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.

No null-character is implicitly appended at the end of destination if source is longer than num. Thus, in this case, destination shall not be considered a null terminated C string (reading it as such would overflow).

– cplusplus.com entry on strncpy()

Why bring this up now?

The only reason I bring this up today is because I saw a new compiler warning I had never seen before recently.

warning: ‘strncpy’ output truncated before terminating nul copying X bytes from a string of the same length [-Wstringop-truncation]

That was the first time I’d ever seen this warning. It is a useful warning, since it informs you that the destination string will NOT be 0 terminated:

char buffer[10];
strncpy (buffer, "1234567890", 10);

And that reminded me of that strncpy() behavior, and made me change how I was using it in my program.

I also saw this variation:

warning: 'strncpy' output truncated copying X bytes from a string of length Y [-Wstringop-truncation]

This warning popped up when I had a source buffer that was longer than the destination. That should be fine, since strncpy() is told how much to copy over, max. I was puzzled why this would even be a warning. I mean, you specifically put the number in there that says “copy up to X bytes.”

I find it odd that the first message (exactly length) lets you know you don’t have a 0 terminated destination buffer, but the second just says “hey, we couldn’t copy as much as you request.”

Anyway, I found it interesting.

Until next time…

Farewell, M-Audio iControl!

In 2005, I bought this M-Audio iControl USB interface from the local Apple Store. It was a real-world mirror of the on-screen controls in Apple’s GarageBand recording software. It was super cool and useful.

For the past decade, it has been collecting dust in a storage bin. I decided to get rid of it, and found two of the knobs had been broken due to my improper storage. I ended up designing some 3-D printed replacements. They are functional, but not necessarily pretty.

Now that I have them designed, I have put the item up for sale. I generally take excellent care of my gear, so I still had the original box, manual, packing materials, and even the original receipt and the “backpack” style Apple Store bag ;-) Pity I didn’t take more care I storing the actual unit during a few moves…

I post this here in case anyone ever does a web search because they broke some of their buttons. (Honestly, I should have designed one for the row of track volume knobs on the right as well, just in case, but I really just wanted to make it functional enough to sale.)

You can find the designs on my Tinkercad.com page.

There ya go, search engines. Have fun.