Understanding TRS-80 .CMD Files

I had originally published this text on Sunday, November 8, 2009. It’s seen a couple of revisions. There’s some C code in this post, but the main point of the post is to provide a way to dump TRS-80 binary CMD files. The source code for all files can be found here:

https://github.com/jimlawless/readcmd

I’ve begun to use TRS-80 emulators to recapture some of the programming experiences of my younger days. The emulator I’m currently using under Windows is trs80gp which can be found here:

http://48k.ca/trs80gp.html

My initial goal was to write Z-80 code and run it on the emulator.

My first task was to find a cross-assembler for the Z-80 that would run under Windows. I found a utility called Pasmo. You can find a copy here:

https://pasmo.speccy.org/

Like some of the others I tried, Pasmo basically generates a binary machine-image file. I chose Pasmo because of the -d option which shows an assembly listing on the standard output device.

I would like to be able to package up anything I write into a standard /CMD file so that it can be used with other emulators or on the real hardware itself. In order to do that, I was going to have to determine how to convert a machine-image file into a /CMD file.

As a test program, I would use and example routine fills the video memory with the all-white character (191) and then returns.

I have several /CMD images of games that I have on cassette and began looking through them. I could see some control-codes and such, but my initial stab at trying to interpret them was not successful. After a little searching on the web, I found a reference to an article from The LDOS Journal volume 1, issue 4. Tim Mann has copies of this issue and others on his site here:

http://www.tim-mann.org/misosys.html

The article that describes the format is in the column Roy’s Technical Corner.

Roy describes the record formats permissible in a /CMD file. The format is not unlike the binary tag-based system used in the TIFF graphical image file format. The first byte one encounters is a record-type byte. The next byte is a length of bytes that will follow … sort of. The remainder of bytes should match the length specified in the length byte. The next record in sequence should be another record ID / length / payload sequence, but that doesn’t seem to hold true either. I wrote a short C program readcmd.exe that lists each record in a /CMD file. While number of my /CMD files parsed correctly just fine, some did not.

Roy explains that the 01 record indicates a loadable block of binary data. The length byte in many of my /CMD files was zero, which I correctly assumed would yield a 256-byte block. However some of the /CMD files in my possession use a value of two in the length byte and seem to have a payload bigger than two bytes in length.

The article further explains that each loadable block of data first contains a load-address and states that zero and one are special values that indicate a two-byte load-address will be followed by 254 and 255 bytes of data respectively. The article doesn’t mention the value two, but I assume that since the 01 record will always have a load-address, two bytes will always follow. The values zero, one, and two are then used for machine-images of size 254, 255, and 256 respectively. The value three is a complete mystery to me. I have seen a small block with a length of four and the payload that follows the load-address is four bytes in length. I’ll tinker later and see how the emulators load a record with a length value of three.

I should state that my readcmd program is dependent on the Intel representation of a 16-bit integer ( Least Significant Byte followed by Most Significant Byte ). An unsigned short integer must be 16-bits in width in order for the program below to run correctly.

// readcmd
// Dump the record information for a TRS-80 /CMD
// executable file
//
// License: MIT / X11
// Copyright (c) 2009, 2015, 2023 by James K. Lawless
// See license at https://github.com/jimlawless/readcmd/blob/main/LICENSE
// Source: https://github.com/jimlawless/readcmd
// https://jimlawless.net
//
 
#include <stdio.h>
 
int main(int argc,char **argv) {
    FILE *fp;
    unsigned char buff[258];
    unsigned int len;
    unsigned short address;
    printf("readcmd v1.20 by Jim Lawless\n");
    printf("https://jimlawless.net/posts/trs80-cmd/\n\n");
    fp=fopen(argv[1],"rb");
    if(fp==NULL) {
        fprintf(stderr,"Cannot open file %s\n",argv[1]);
       return 1;
    }
    for(;;) {
        if(!fread(buff,1,1,fp))
            break;
            // record type is "load block"
        if(*buff==1) {
            fread(buff,1,1,fp);
            len=*buff;
                // compensate for special values 0,1, and 2.
            if(len<3)
                len+=256;
                // read 16-bit load-address
            fread(&address,1,2,fp);
            printf("Reading 01 block, addr %x, length = %u.\n",address,len-2);
            fread(buff,1,len-2,fp);
        }
        else
            // record type is "entry address"
        if(*buff==2) {
            fread(buff,1,1,fp);
            len=*buff;
            printf("Reading 02 block length = %u.\n",len);
            fread(&address,1,len,fp);
            printf("Entry point is %d %x\n",address,address);
            break;
        }
        else
            // record type is "load module header"
        if(*buff==5) {
            fread(buff,1,1,fp);
            len=*buff;
            printf("Reading 05 block length = %u.\n",len);
            fread(buff,1,len,fp);
        }
        else {
            printf("Unknown code %u at %lx\n",*buff,ftell(fp)-1L);
            break;
        }
    }
    fclose(fp);
}

I found that after the 02 record is encountered, all kinds of garbage data can follow. I assume that most /CMD loaders halt interpretation of the file after the 02 record is encountered. You’ll notice a break out of the main input loop when readcmd encounters this record.

My readcmd program was able to parse through all of the /CMD files in my possession. Now that I have a way to verify the correctness of a /CMD file, it’s time to try and build my own.

I really can’t remember where the first bytes of free memory really start on a Model I. Address 17129 looks to be the spot where BASIC begins, but I’ve never owned a DOS on a Model I, so I don’t know if that address can change.

I noticed that a lot of the games I have begin at address 6C00H, so I chose that as the starting-address for my program.

Here is the Z-80 code that should fill the screen with white space:

fill.asm

; pasmo -d fill.asm fill.out
ORG 6c00H
LD HL,3C00H ; 15360 in hex
LD A, 191
LD [HL],A
LD DE,3C01H
LD BC,1023
LDIR
RET

I then assembled it with the command

pasmo -d fill.asm fill.out

The output from Pasmo is as follows:

                ORG 6C00
6C00:21003C     LD HL, 3C00
6C03:3EBF       LD A, BF
6C05:77         LD (HL), A
6C06:11013C     LD DE, 3C01
6C09:01FF03     LD BC, 03FF
6C0C:EDB0       LDIR
6C0E:C9         RET
Emiting raw binary from 6C00 to 6C0E

My load-module is fifteen bytes in size. I need to create a load-record that accommodates fifteen plus two bytes for the load address. My 01 record will have a length of seventeen ( or 11H ) bytes. Here’s how the 01 record should look in hex:

01 11 00 6C 21 00 3C 3E BF 77 11 01 3C 01 FF 03
ED B0 C9

The total space occupied by the 01 record is nineteen bytes.

I then needed to add a 02 record to state the transfer address of 6C00H:

02 02 00 6C

The total size of the two records is twenty-three (17H) bytes in length.

Originally, I had manually created the /CMD file using the Windows console DEBUG utility but I’m afraid that poor old DEBUG is no longer provided with Windows. I wrote a Powershell script to create the CMD file instead:

makecmd.ps1

# license https://github.com/jimlawless/readcmd/blob/main/LICENSE
[byte[]] $bytes= 0x01,0x11,0x00,0x6C,0x21,0x00,0x3C,0x3E,0xBF,0x77,
                 0x11,0x01,0x3C,0x01,0xFF,0x03,0xED,0xB0,0xC9,
                 0x02,0x02,0x00,0x6C 
                  
[System.IO.File]::WriteAllBytes("fill.cmd",$bytes)

I first used readcmd to ascertain that the records looked reasonable:

readcmd fill.cmd

readcmd v1.20 by Jim Lawless
https://jimlawless.net/posts/trs80-cmd/

Reading 01 block, addr 6c00, length = 15.
Reading 02 block length = 2.
Entry point is 27648 6c00 

I loaded fill.cmd in trs80gp and it correctly filled the screen with whitespace … and then it went back to the TRSDOS prompt.

trs80gp fill.cmd

Side note: When I had originally posted this fourteen years ago, I’d had some difficulties getting this to run and exit they way that I thought it should. George Phillips, the author of trs80gp, was kind enough to comment on my approach. I don’t have any issues with the newest version of trs80gp.

In the near future, I would like to write a program that will convert a larger memory-image file ( such as the one produced by Pasmo ) into a /CMD file.

You can download the binaries with source for the above here: https://jiml.us/downloads/readcmd.zip

Happy hacking!