/* h8t2sys ** ** Converts a Heathkit H8 tape image (H8T) file into ** a CP/M 3 compatible .SYS file. This allows older ** programs originally written for tape loading (from ** the H8 front panel) to be stored on USB Flash drive ** and loaded/run directly using the VDIP boot capability ** of recently-developed Z80 4.0 CPU board/ROM. ** ** After opening the input and output files the program reads the ** contents of the H8T file into RAM, storing each 128-byte record ** in an indexed array. Next the size in pages of the image is computed ** and size and location information is stored in the header record, ** which is written to the output file, along with a 128-byte print ** record. Finally the records are written to the output file but in ** reverse order. ** ** The H8T file format is documented in "Software Reference ** Manual, Digital Computer System, Model H8", Document 595-2048, ** p. 0-12, Heath Company, Benton Harbor MI, 1977. ** ** The SYS file format is defined in "CP/M Plus(CP/M Version 3.0) ** Operating System System Guide", p. 115, Digital Research Inc., ** Pacific Grove CA, 1983. ** http://www.cpm.z80.de/manuals/cpm3-sys.pdf ** ** Glenn Roberts ** July 2021 */ #define PAGESIZE 256 #define MAXR 128 /* maximum number of pages */ #define TRUE 1 #define FALSE 0 #define NUL 0 #define LF 012 #define CR 015 #define SYN 026 #define STX 002 #define EOF -1 #include "printf.h" char *strncpy(); /* Header (first) record of CP/M 3 .SYS file */ struct hrec { char restop; /* top of resident code page + 1 */ char ressize; /* length (pages) of resident code */ char bnktop; /* top of banked code page + 1 */ char bnksize; /* length (pages) of banked code */ int entry; /* cold boot entry point */ char reserved[10]; char copyright[36]; /* copyright notice */ char xxx1; /* reserved */ char serno[6]; /* serial no */ char xxx2[69]; /* reserved */ } header; char printrec[128]; /* A fixed-size array of pointers to dynamically- ** allocated RECSIZE-byte records. This is hard wired ** to a maximum size which limits the executable ** to MAXR*RECSIZE bytes in size. */ char *record[MAXR]; /* the .SYS file needs the number of PAGESIZE-byte pages ** loaded. */ int np; /* loading and entry addresses from the file */ int loadaddr, entaddr; /* input and output channels */ int inchan, outchan; /* openf - open files. ** ** infile is an ASCII string with the input file name. ** The routine will attempt to construct a valid name ** for the output file, then open both files. ** ** Return values: ** 0 = success ** 1 = input file not named ".H8T" ** 2 = error opening input file ** 3 = error opening output file ** ** Sets globally-defined channel numbers inchan and outchan. */ openf(infile) char *infile; { int rc, p; char outfile[16]; rc = 0; if ((p=index(infile, ".H8T")) == -1) { printf("*** Error: input must be H8T file\n"); rc = 1; } else if ((inchan = fopen(infile, "r")) ==0) { printf("*** Error opening input file %s\n", infile); rc = 2; } else { printf("Input file: %s\n", infile); /* construct output file name from input file ** name, converting ".H8T" to ".SYS". Start by ** making a copy. To be safe only copy 15 bytes, ** which should be enough for any valid filespec. */ strncpy(outfile, infile, 15); /* stick in ".SYS" where the ".H8T" was */ strcpy(outfile+p, ".SYS"); /* now try to open the file */ if ((outchan = fopen(outfile, "wb")) == 0) { printf("*** Error operning output file %s\n", outfile); rc = 3; } else { printf("Output file: %s\n", outfile); } } return rc; } /* getbyte - get the next byte of data from the input ** file. This routine uses a simple state machine to ** parse the data of an H8T file. ** ** states: ** 1 - gobbling "SYN" looking for "STX" ** 2 - reading byte count ** 3 - reading address field high byte ** 4 - reading address field low byte ** 5 - reading record type ** 6 - reading data ** ** returns byte value or -1 if error/EOF ** ** updates global 'curraddr' with the most recently ** identified record address. ** */ int getbyte(chan) int chan; { int result, ch, eof, havebyte; /* declare static any values that must persist ** across multiple calls to getbyte() but still ** have only local scope. */ static int state = 1; static int bytecnt = 0; static int nread = 0; eof = FALSE; havebyte = FALSE; while (!eof && !havebyte) { switch (state) { /* state 1: expecting ':" ** read until we find ':' or hit EOF */ case 1: do { ch=getc(chan); } while ((ch!=':') && (ch!=EOF)); if (ch==EOF) eof = TRUE; else /* success - go to next state */ state = 2; break; /* state 2: reading byte count */ case 2: if ((ch = gethexb(chan)) == -1) eof = TRUE; else { /* success - save value and ** go to next state */ bytecnt = ch; /* treat 0 bytecount as last record (eof) */ if (bytecnt == 0) eof = TRUE; else state = 3; } break; /* state 3: reading address field hi */ case 3: if ((ch = gethexb(chan)) == -1) eof = TRUE; else { /* success - save value and ** go to next state */ curraddr = ch << 8; state = 4; } break; /* state 4: reading address field lo */ case 4: if ((ch = gethexb(chan)) == -1) eof = TRUE; else { /* success - save value and ** go to next state */ curraddr = curraddr | (ch & 0xFF); state = 5; } break; /* state 5: reading record type (ignore) */ case 5: if ((ch = gethexb(chan)) == -1) eof = TRUE; else { /* do nothing with this byte */ state = 6; } break; /* state 6: reading data byte */ case 6: if ((ch = gethexb(chan)) == -1) eof = TRUE; else { /* have a byte value! increment ** counter, update state and ** return value */ havebyte = TRUE; if (++nread == bytecnt) { /* record completed */ state = 1; nread = 0; bytecnt = 0; } result = ch; } break; } } return eof ? -1 : result; } /* gethexb - read a hexadecimal byte from ** the specified channel. Return the value (0..255) or ** -1 on EOF or bad data. */ int gethexb(chan) int chan; { int c, result, nib; result = -1; /* read and process first nibble */ if (ishex(c = getc(chan))) { /* valid hex. strip ASCII offset ** and adjust if A-F */ if ((result = c-'0') > 9) result -= 7; /* read and process second nibble */ if (ishex(c = getc(chan))) { if ((nib = c-'0') > 9) nib -= 7; /* have two valid nibbles. compute ** the byte. */ result = (result << 4) | (nib & 0xF); } else { /* treat case where only first ** nibble was valid. */ result = -1; } } return result; } /* strncpy - copies at most n characters from s2 to s1 ** ** Copyright (c) 1999 Apple Computer, Inc. All rights reserved, see: ** https://www.opensource.apple.com/source/Libc/Libc-262/ppc/gen/strncpy.c */ char *strncpy(s1, s2, n) char *s1, *s2; int n; { char *s; s = s1; /* copy n characters but not terminating NUL */ while ((n > 0) && (*s2 != NUL)) { *s++ = *s2++; --n; } /* if necessary, pad destination with NUL */ while (n > 0) { *s++ = NUL; --n; } return s1; } /* hexdump - dump a buffer to stdout in hexadecimal format with ** ascii equivalent shown to the right */ hexdump(b,n) char *b; int n; { int i; /* print header */ printf("\n\n\t0 1 2 3 4 5 6 7 8 9 A B C D E F"); printf(" 0123456789ABCDEF\n"); printf("\t\b-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); printf(" ----------------\n"); for (i=0; i0x1F) && (c<0x7F)); } /* ishex - return TRUE if character represents ** a hex number: 0-9 or A-F. Only uppercase ** is considered valid. */ int ishex(c) { return ((c>='0') && (c<='9')) || ((c>='A') && (c<='F')); } main(argc,argv) int argc; char *argv[]; { int eof, error, i, rc, ch, haveload; char *newrec; /* grab load address on first record */ haveload = FALSE; if (argc < 2) { printf("Usage: %s \n", argv[0]); rc = 4; } else if ((rc=openf(argv[1])) == 0) { /* input and output channels opened, proceed ...*/ /* Read data one byte at a time, allocating buffer ** space as we go along */ error = FALSE; eof = FALSE; for (np = 0; (!error) && (!eof); np++) { /* allocate a page */ if ((newrec = alloc(PAGESIZE)) == 0) { printf("Out of memory space!\n"); error = TRUE; } else { /* now read PAGESIZE bytes into this buffer */ for (i=0; (i> 8 & 0xFF) + np; header.ressize = np; header.bnktop = 0x40; header.bnksize = 0; header.entry = loadaddr; header.copyright[0] = 'U'; write(outchan, &header, 128); printrec[0] = '$'; write(outchan, printrec, 128); /* output the 128-byte buffers in reverse */ for (i=np-1; i>=0; i--) { write(outchan, record[i]+128, 128); write(outchan, record[i], 128); } fclose(outchan); } }