2021-02-15 21:10:40 -08:00

652 lines
20 KiB
C

/* sds_9pal.c - Extract file from 9-track PAL library tape */
/* Created by Ken Rector, Feb. 3 2020 */
// Extract file or list contents of an SDS Pal Library Tape.
//
// The PAL library tape file is in SIMH Magnetic Tape format copied from
// a 9 track SDS tape. Thanks to Al Kassow of the CHM and bitsavers.org
// for doing that.
//
// Each file from the Pal tape contains labels and data. Labels contain
// EBCDIC characters, (the tapes were made on a Sigma 7) and data following
// these contain packed 6bit data such that threee 8bit bytes contain four
// 6bit characters or 1 24bit 9 Series word.
//
// There is one :LBL file at the beginning of the tape with the PAL identifier.
//
// The :LBL file is followed by the program files preceeded by :BOF labels,
// containing the PAL program catalog number to identify data file.
//
// The data file following the :BOF label consists of multiple blocks, usually
// 2048 bytes. Data files are organized as follows:
//
// int number of records
// int record sequence number within file
// int length of record data to follow, usually 160 bytes, but
// some times less if record spans blocks
// char[length] packed data bytes
//
// Records may span across blocks. A partial packed set will be continued
// as a new record with the same sequence number in the next block. A
// packed sequence of bytes will be continued in the next block and remaining
// packed data will be present to complete the record, to make 160 bytes.
//
// Catalog numbers are suffixed by two digits to indicate the data type.
// The Pal manual defines the types as:
// 2x Relocatable Binary
// 3x Source cards
// 4x Compressed Source (encoded)
// 5x Listing
// 8x Absolute Binary
// where x = 4 indicates card medium.
//
// A type -34 file contains Hollerith coded source records.
// The hexdump for an example (890548-34) is:
// 0000000 10 62 10 62 10 62 10 62 10 62 10 62 10 62 10 62
// *
// 0000070 10 62 10 62 10 62 10 62 10 62 10 62 10 62 40 40
// 0000080 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
// etc.
// which converts to ascii characters:
// **************************************** blank ...
//
//
// A type -44 file contains encoded data that was called
// 'compressed' by the SDS Metasymbol assembler. This can
// be uncompressed by the 'encode to symbol' program (850647).
//
// The 'encode to symbol' program expects a keyboard input command to
// define it's input output configuration. This command takes the form
// of '^XX' where the first X indicates the input device and the
// second, the output device. Errors can be deleted by typing '/' and
// retyping the command from the beginning '^'.
//
// Devices can be any of CPM, card, papertape and magtape, respecively.
// Specify card input and mag tape output by '^CM'.
// The mag tape output can be read using the simh listtape utility.
//
//
//
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
char ebcdic_to_ascii[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x09, 0x06, 0x07, /* 00 - 1F */
0x08, 0x05, 0x15, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x0A, 0x16, 0x17, //10
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 20 - 3F */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 30
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
' ', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 40 - 5F */
0x00, 0x00, '`', '.', '<', '(', '+', '|',
'&', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //50
0x00, 0x00, '!', '$', '*', ')', ';', '~',
'-', '/', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 - 7F */
0x00, 0x00, '^', ',', '%', '_', '>', '?',
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //70
0x00, 0x00, ':', '#', '@', '\'', '=', '"',
0x00, 'a', 'b', 'c', 'd', 'e', 'f', 'g', /* 80 - 9F */
'h', 'i', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 'j', 'k', 'l', 'm', 'n', 'o', 'p', // 90
'q', 'r', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 's', 't', 'u', 'v', 'w', 'x', /* A0 - BF */
'y', 'z', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, '\\', '{', '}', '[', ']', 0x00, 0x00, //b0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 'A', 'B', 'C', 'D', 'E', 'F', 'G', /* C0 - DF */
'H', 'I', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 'J', 'K', 'L', 'M', 'N', 'O', 'P', //d0
'Q', 'R', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 'S', 'T', 'U', 'V', 'W', 'X', /* E0 - FF */
'Y', 'Z', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
'0', '1', '2', '3', '4', '5', '6', '7', // f0
'8', '9', 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
};
unsigned int parity7[64] = {
0100, 0000, 0000, 0100, 0000, 0100, 0100, 0000 /* 00 - 07 */
,0000, 0100, 0100, 0000, 0100, 0000, 0000, 0100 /* 10 - 17 */
,0000, 0100, 0100, 0000, 0100, 0000, 0000, 0100 /* 20 - 27 */
,0100, 0000, 0000, 0100, 0000, 0100, 0100, 0000 /* 30 - 37 */
,0000, 0100, 0100, 0000, 0100, 0000, 0000, 0100 /* 40 - 47 */
,0100, 0000, 0000, 0100, 0000, 0100, 0100, 0000 /* 50 - 57 */
,0100, 0000, 0000, 0100, 0000, 0100, 0100, 0000 /* 60 - 67 */
,0000, 0100, 0100, 0000, 0100, 0000, 0000, 0100}; /* 70 - 77 */
char bcd_ascii[64] = {
' ', /* 0 - space */
'1', /* 1 1 - 1 */
'2', /* 2 2 - 2 */
'3', /* 3 21 - 3 */
'4', /* 4 4 - 4 */
'5', /* 5 4 1 - 5 */
'6', /* 6 42 - 6 */
'7', /* 7 421 - 7 */
'8', /* 8 8 - 8 */
'9', /* 9 8 1 - 9 */
'0', /* 10 8 2 - 0 */
'=', /* 11 8 21 - equal */
'\'', /* 12 84 - apostrophe */
':', /* 13 84 1 - colon */
'>', /* 14 842 - greater than */
'"', /* 15 8421 - radical 017 {? */
' ', /* 16 A - substitute blank */
'/', /* 17 A 1 - slash */
'S', /* 18 A 2 - S */
'T', /* 19 A 21 - T */
'U', /* 20 A 4 - U */
'V', /* 21 A 4 1 - V */
'W', /* 22 A 42 - W */
'X', /* 23 A 421 - X */
'Y', /* 24 A8 - Y */
'Z', /* 25 A8 1 - Z */
'#', /* 26 A8 2 - record mark */
',', /* 27 A8 21 - comma */
'(', /* 28 A84 - paren */
'`', /* 29 A84 1 - word separator */
'\\', /* 30 A842 - left oblique */
'{', /* 31 A8421 - segment mark */
'-', /* 32 B - hyphen */
'J', /* 33 B 1 - J */
'K', /* 34 B 2 - K */
'L', /* 35 B 21 - L */
'M', /* 36 B 4 - M */
'N', /* 37 B 4 1 - N */
'O', /* 38 B 42 - O */
'P', /* 39 B 421 - P */
'Q', /* 40 B 8 - Q */
'R', /* 41 B 8 1 - R */
'!', /* 42 B 8 2 - exclamation */
'$', /* 43 B 8 21 - dollar sign */
'*', /* 44 B 84 - asterisk */
']', /* 45 B 84 1 - right bracket */
';', /* 46 B 842 - semicolon */
'_', /* 47 B 8421 - delta */
'+', /* 48 BA - ampersand or plus */
'A', /* 49 BA 1 - A */
'B', /* 50 BA 2 - B */
'C', /* 51 BA 21 - C */
'D', /* 52 BA 4 - D */
'E', /* 53 BA 4 1 - E */
'F', /* 54 BA 42 - F */
'G', /* 55 BA 421 - G */
'H', /* 56 BA8 - H */
'I', /* 57 BA8 1 - I */
'?', /* 58 BA8 2 - question mark 032 */
'.', /* 59 BA8 21 - period */
')', /* 60 BA84 - paren */
'[', /* 61 BA84 1 - left bracket 035 */
'<', /* 62 BA842 - less than 036 */
'}' /* 63 BA8421 - group mark 037 */
};
FILE *infile, *outfile;
int upkx;
void usage() {
fprintf(stderr,"Usage: sds_9pal [-l] [-x nnnn-tt] <tapefile> <outfile> [>stdout]\n");
fprintf(stderr," -l : List all catalog numbers \n");
fprintf(stderr," -x nnnnn-tt : Extract catalog number nnnnn ");
fprintf(stderr,"element type tt\n");
fprintf(stderr," if type = 34 file, extracted ascii data is ");
fprintf(stderr,"written to stdout\n");
exit(1);
}
/* Returns the BCD of the hollerith code or 0x7f if error */
uint8_t
sim_hol_to_bcd(uint16_t hol) {
uint8_t bcd;
/* Convert 10,11,12 rows */
switch (hol & 0xe00) {
case 0x000:
bcd = 0;
break;
case 0x200:
if ((hol & 0x1ff) == 0)
return 10;
bcd = 020;
break;
case 0x400:
bcd = 040;
break;
case 0x600: /* 11-10 Punch */
bcd = 052;
break;
case 0x800:
bcd = 060;
break;
case 0xA00: /* 12-10 Punch */
bcd = 072;
break;
default: /* Double punch in 10,11,12 rows */
return 0x7f;
}
hol &= 0x1ff; /* Mask rows 0-9 */
/* Check row 8 punched */
if (hol & 0x2) {
bcd += 8;
hol &= ~0x2;
}
/* Convert rows 0-9 */
while (hol != 0 && (hol & 0x200) == 0) {
bcd++;
hol <<= 1;
}
/* Any more columns punched? */
if ((hol & 0x1ff) != 0)
return 0x7f;
return bcd;
}
// get int32_t value from input file
uint32_t getint() {
char tmp[4];
uint32_t val;
for (int j = 3; j>= 0;j--) {
fread(&tmp[j],1,1,infile);
}
val = *(uint32_t *)&tmp[0];
return val;
}
// return 4 6 bit bytes packed into a word
int getwd(int icnt) {
static int res; // unpack 3-4 index
static char upkchr; // unpack temp byte
static int upkbytctr; // unpack byte counter
int ch;
//for (int i = 0; i < 4;i++) {
while(icnt) {
switch (upkx) {
case 0:
fread(&upkchr, sizeof(unsigned char), 1, infile);
upkbytctr++;
icnt--; // used first char
ch = (upkchr >> 2) & 077;
upkx++;
res = 0;
break;
case 1:
ch = (upkchr & 03) << 4;
fread(&upkchr, sizeof(unsigned char), 1, infile);
upkbytctr++;
icnt--; // used first and second char
ch = ch | ((upkchr >> 4) & 017);
upkx++;
break;
case 2:
ch = ((upkchr & 017) << 2);
fread(&upkchr, sizeof(unsigned char), 1, infile);
upkbytctr++;
ch = ch | ((upkchr >> 6) & 03); // used second and third char
upkx++;
break;
case 3:
ch = upkchr & 077;
icnt--; // used third character again
upkx = 0;
break;
}
res = res<<6 | ch;
}
return res;
}
// read simh length word
int rdlen() {
char xlen[4];
int sz;
if ((sz = fread(xlen, sizeof(unsigned char), 4, infile)) != 4)
return 0;
/* Convert to number */
sz = (xlen[0] & 0xff);
sz |= ((xlen[1]) & 0xff)<< 8;
sz |= ((xlen[2]) & 0xff)<< 16;
sz |= ((xlen[3]) & 0xff) << 24;
/* Check for EOM */
if (sz == 0xffffffff) {
return -2;
}
/* Check for EOF */
if (sz == 0) {
return -1;
}
return sz;
}
// Read the label at the beginning of the tape
int getbot(void) {
int sz;
char lbl[60];
int i;
sz = rdlen();
if (sz != 12) {
fprintf(stderr,":LBL size error\n");
return 0;
}
if (fread(&lbl,1,sz, infile) != sz) {
fprintf(stderr,":LBL read error\n");
return 0;
}
if ((ebcdic_to_ascii[lbl[0]&0xff] != ':') ||
(ebcdic_to_ascii[lbl[1]&0xff] != 'L') ||
(ebcdic_to_ascii[lbl[2]&0xff] != 'B') ||
(ebcdic_to_ascii[lbl[3]&0xff] != 'L')) {
fprintf(stderr,":LBL missing\n");
return 0;
}
fread(&lbl, sizeof(unsigned char), 4, infile);
sz = rdlen();
if (sz != 28) {
fprintf(stderr,":ACN size error\n");
return 0;
}
if (fread(&lbl,1,sz, infile) != sz) {
fprintf(stderr,":ACN read error\n");
return 0;
}
if ((ebcdic_to_ascii[lbl[0]&0xff] != ':') ||
(ebcdic_to_ascii[lbl[1]&0xff] != 'A') ||
(ebcdic_to_ascii[lbl[2]&0xff] != 'C') ||
(ebcdic_to_ascii[lbl[3]&0xff] != 'N')) {
fprintf(stderr,":ACN missing\n");
return 0;
}
fread(&lbl, sizeof(unsigned char), 4, infile);
// should be an EOF here
sz = rdlen();
if (sz != -1) {
fprintf(stderr,"Missing EOF following ACN\n");
return 0;
}
return sz;
}
int getbof(char *buf) {
int sz;
char lbl[60];
int i;
sz = rdlen();
if (sz != 52) {
if (sz == 12) {
if (fread(&lbl,1,sz, infile) != sz) {
fprintf(stderr,":BOF read error\n");
return 0;
}
if ((ebcdic_to_ascii[lbl[0]&0xff] != ':') ||
(ebcdic_to_ascii[lbl[1]&0xff] != 'E') ||
(ebcdic_to_ascii[lbl[2]&0xff] != 'O') ||
(ebcdic_to_ascii[lbl[3]&0xff] != 'R')) {
fprintf(stderr,":BOF/EOR missing end of tape?\n");
return 0;
}
fread(&lbl, sizeof(unsigned char), 4, infile);
//fprintf(stderr,":EOR at end of tape\n");
return 0;
}
fprintf(stderr,":BOF size error %d %x\n",sz,sz);
//return 0;
}
if (fread(buf,1,sz, infile) != sz) {
fprintf(stderr,":BOF read error\n");
return 0;
}
if ((ebcdic_to_ascii[buf[0]&0xff] != ':') ||
(ebcdic_to_ascii[buf[1]&0xff] != 'B') ||
(ebcdic_to_ascii[buf[2]&0xff] != 'O') ||
(ebcdic_to_ascii[buf[3]&0xff] != 'F')) {
fprintf(stderr,":BOF missing %x %x %x %x\n",buf[0]&0xff,buf[1]&0xff,buf[2]&0xff,buf[3]&0xff);
return 0;
}
fread(&lbl, sizeof(unsigned char), 4, infile);
// should be an EOF here
if (rdlen() != -1) {
fprintf(stderr,"Missing EOF following BOF\n");
return 0;
}
return sz;
}
int geteof(void) {
int sz;
char lbl[60];
int i;
sz = rdlen();
if (sz != 12) {
fprintf(stderr,":EOF size error\n");
return 0;
}
if (fread(&lbl,1,sz, infile) != sz) {
fprintf(stderr,":EOF read error\n");
return 0;
}
if ((ebcdic_to_ascii[lbl[0]&0xff] != ':') ||
(ebcdic_to_ascii[lbl[1]&0xff] != 'E') ||
(ebcdic_to_ascii[lbl[2]&0xff] != 'O') ||
(ebcdic_to_ascii[lbl[3]&0xff] != 'F')) {
fprintf(stderr,":EOF missing\n");
return 0;
}
fread(&lbl, sizeof(unsigned char), 4, infile);
// should be an EOF here
if (rdlen() != -1) {
fprintf(stderr,"Missing EOF following :EOF\n");
return 0;
}
return sz;
}
// Extract data
// output card images suitable for sim_card reader and sds_cr
void extract(int pnum,int ptype) {
int32_t blksz; // leading length in simh block
int32_t finsz; // final length in simh block
int32_t remsz;
int32_t bytcnt; // counter of input bytes from block
int32_t length; // number bytes in record data to unpack
int mod; // number bytes of incomplete unpack dat
int mrkflg;
unsigned char tmp;
int j;
int32_t data;
int32_t numrec; // number records in block
int32_t totrec; // total number records in deck
int32_t reccnt; // counter of records in block
int32_t recnum = -1; // record sequence number
totrec = 0;
mod = 0;
// read blocks to end of file marker
while ((blksz = rdlen()) > 0) { // leading length
reccnt = 0;
numrec = getint(); // record count
bytcnt = 4;
//printf("numrec 0x%x \n",numrec);
// unpack records
while (bytcnt < blksz) {
remsz = blksz - bytcnt;
data = getint() & 0xffffff; // record number
bytcnt += 4;
if (data == recnum) { // continued record
totrec--;
mrkflg = 0; // no record mark
}
else {
if (ptype == 34)
printf("\n");
mrkflg = 1;
}
recnum = data;
length = getint() & 0xffffff; // length of record data
bytcnt += 4;
if (mod) {
data = getwd(3-mod); // unpack remainder of word
if (ptype == 34) {
printf("%c",bcd_ascii[sim_hol_to_bcd(data>>12 &0xfff)]);
printf("%c",bcd_ascii[sim_hol_to_bcd(data &0xfff)]);
}
bytcnt += 3-mod;
for (int i = 18; i >= 0;i-=6) {
tmp = ((data >> i ) & 0x3f);
tmp = tmp | parity7[tmp];
fwrite(&tmp,1,1,outfile);
}
length -= (3-mod);
mod = 0;
}
mod = length % 3;
for (j = 0; j < length - mod; j+=3,bytcnt+=3) {
upkx = 0;
data = getwd(3); // output full words
if (ptype == 34) {
printf("%c",bcd_ascii[sim_hol_to_bcd(data>>12 &0xfff)]);
printf("%c",bcd_ascii[sim_hol_to_bcd(data &0xfff)]);
}
for (int i = 18; i >= 0;i-=6) {
tmp = ((data >> i ) & 0x3f);
tmp = tmp | parity7[tmp];
if ((j == 0) & (i == 18) & mrkflg)
tmp |= 0x80; // Set record mark
fwrite(&tmp,1,1,outfile);
}
}
if (mod) {
tmp = getwd(mod); // unpack partial word
bytcnt+=mod;
}
reccnt++;
totrec++;
}
finsz = rdlen();
if (finsz != blksz)
printf(" end length not equal %x %x\n",blksz,finsz);
}
fflush(stdout);
fprintf(stderr,"\n\ncatalog number %d-%d %d cards\n",pnum,ptype,recnum+1);
return;
}
int main(int argc, char *argv[]) {
int lst = 0;
int ext = 0;
int cat = 0;
int elm = 0;
char bof[120];
char asc[120];
char tmp;
int pnum;
int ptype;
int sz;
int blksz;
if ((argc < 2) || (argc > 5)){
usage();
return 0;
}
while(--argc && **(++argv) == '-') {
switch(tolower((*argv)[1])) {
case 'l':
lst = 1;
break;
case 'x':
if (sscanf(argv[1],"%d-%d",&cat,&elm) != 2)
usage();
if ((elm != 24) && (elm != 34) &&
(elm != 44) && (elm != 84)) {
fprintf(stderr,"Unsupported element type\n");
return 0;
}
ext = 1;
--argc;
++argv;
break;
default:
usage();
return 0;
}
}
infile = fopen(argv[0], "rb");
if (!infile) {
printf("Cannot open PAL file: %s\n", argv[0]);
return 1;
}
outfile = fopen(argv[1], "w");
if (!outfile) {
printf("Cannot open output file: %s\n", argv[1]);
return 1;
}
getbot();
while (1) {
sz = getbof(bof);
if (sz == 0)
break; // end of input tape
for (int i = 0; i < sz; i++) {
asc[i] = ebcdic_to_ascii[bof[i]&0xff];
}
sscanf(&asc[9],"%6d-%2d",&pnum,&ptype);
if (lst)
fprintf(outfile,"catalog number %d-%d\n",pnum,ptype);
else if (ext && (pnum == cat) && (ptype == elm)) {
extract(pnum,ptype);
return 0;
}
while ((blksz = rdlen()) != -1) {
if (feof(infile) != 0) {
printf("Unexpected EOF on infile\n");
return 0;
}
for (int j = 0; j < blksz; j++) {
fread(&tmp,1,1,infile);
}
rdlen();
}
geteof();
}
if (ext)
fprintf(stderr,"Missing file: %d-%d\n",cat,elm);
}