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:
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:
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!