Understanding TRS-80 CMD Files

I had originally published this text on Sunday, November 8, 2009. Reworking it for the newer blog reminded me that I need to pick up the Z-80 assembler and write more for the venerable Model I!

There is 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.

Here’s the older post with a few updates to the C source code.

I’ve begun to use TRS-80 Model I 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:


I invoke the emulator with the command-line parameters -m1 to force Model I emulation and -na for non-authentic display.

While trs80gp does not support any of the Disk-Operating-Systems of yesteryear, it does provide a menu option that will load /CMD executable files and BASIC files. You really can’t save any information back to the Windows filesystem, but that’s okay. For my purposes, I want to write some programs in a cross-assembler or cross-compiler environment and will just load the files to try them out. At some point, I will either look for or will build a utility that will tokenize a text listing to a BASIC listing so that I can create BASIC programs as well.

My initial goal, however, is 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. The trs80gp site references ZMac which sounds like a great editor, but it doesn’t appear to compile in its current form under Windows.

I found a utility called Pasmo 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 the example routine I published in my post http://www.mailsend-online.com/blog?p=54. That 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:


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 by James K. Lawless
// See http://www.mailsend-online.com/license2015.php
// http://jiml.us

#include <stdio.h>

int main(int argc,char **argv) {
   FILE *fp;
   unsigned char buff[258];
   unsigned int len;
   unsigned short address;
	printf("readcmd v1.10 by Jim Lawless\n");
   if(fp==NULL) {
      fprintf(stderr,"Cannot open file %s\n",argv[1]);
      return 1;
   for(;;) {
			// record type is "load block"
      if(*buff==1) {

				// compensate for special values 0,1, and 2.
				// read 16-bit load-address
         printf("Reading 01 block, addr %x, length = %u.\n",address,len-2);
				// record type is "entry address"
      if(*buff==2) {
         printf("Reading 02 block length = %u.\n",len);
         printf("Entry point is %d %x\n",address,address);
			// record type is "load module header"
      if(*buff==5) {
         printf("Reading 05 block length = %u.\n",len);
      else {
         printf("Unknown code %u at %lx\n",*buff,ftell(fp)-1L);

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 slightly modified source code from my prior post:


    ORG 6c00H
    LD HL,3C00H ; 15360 in hex
    LD A, 191
    LD [HL],A
    LD DE,3C01H
    LD BC,1023

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
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.

I manually created the /CMD file using the Windows console DEBUG utility.

debug fill.cmd
e 100 01 11 00 6C 21 00 3C 3E BF 77 11 01 3C 01 FF 03
e 110 ED B0 C9 02 02 00 6C

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

readcmd fill.cmd

readcmd v1.0 by Jim Lawless

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.

I had expected the RET instruction to drop me back into BASIC, but it did not. I’m not sure if that’s an idiosyncrasy of trs80gp or if all emulators and the actual hardware/software will behave in this manner. ( NOTE: after fixing my code to use 1023 bytes to move instead of 4095 as George Phillips, author of trs80gp notes in the comments to the post, I did indeed drop into BASIC, but received a ?SN ERROR message at the prompt. I may try George’s suggestion of JP-ing to 1A19H at a later time. )

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.

The source and EXE files for readcmd can be found here, along with the source and /CMD file for the fill program.



About Jim Lawless

I've been programming computers for about 37 years ... 31 of that professionally. I've been a teacher, I've worked as a consultant, and have written articles here and there for publications like Dr. Dobbs Journal, The C/C++ Users Journal, Nuts and Volts, and others.
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

4 Responses to Understanding TRS-80 CMD Files

  1. nitrofurano says:

    at “01 11 00 6C”, 11h seems to be stored in just 1 byte – if the .cmd file is larger than 256 bytes, it would only consider the less significant byte? or do we need to add record bytes after each 256 bytes in that file? and is there any kind of bin2cmd converter for that?

  2. Knut Roll-Lund says:

    Other entries:
    07h LDOS patch name similar to 5.
    1Fh LDOS and DOSPLUS comment used for copyright notice, also works like 5.

    The LDOS patch system would just add to the file just before the 02 ‘run’ entry one 07 entry then a 01 entry with replacement data for example my FORMAT/CMD contains a patch ‘CLP’ which replaces 2 bytes at 630Bh with 00h 00h (replacing LDIR with NOP NOP). Thus you can yank a patch without the patch file in LDOS.

    I guess load read 01 data, 02 run and everything else assume next byte is size then ignore.

  3. Pingback: Galaxy Invasion – 80’s Style | robdobson.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s