1 /*
2  * Copyright (c) 2021 The DPS8M Development Team
3  *
4  * All rights reserved.
5  *
6  * This software is made available under the terms of the ICU
7  * License, version 1.8.1 or later.  For more details, see the
8  * LICENSE.md file at the top-level directory of this distribution.
9  */
10 
11 #include <stdio.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <ctype.h>
15 #include <unistd.h>
16 #include <stdbool.h>
17 #ifdef _AIX
18 # ifndef USE_POPT
19 #  define USE_POPT
20 # endif /* ifndef USE_POPT */
21 #endif /* ifdef _AIX */
22 #ifdef USE_POPT
23 # include <popt.h>
24 #else
25 # include <getopt.h>
26 #endif /* ifdef USE_POPT */
27 
28 #define CARD_COL_COUNT 80
29 #define NIBBLES_PER_COL 3
30 #define NIBBLES_PER_CARD (CARD_COL_COUNT * NIBBLES_PER_COL)
31 #define BYTES_PER_CARD (NIBBLES_PER_CARD / 2)
32 #define CHAR_MATRIX_BYTES 5
33 #define GLYPHS_PER_CARD 22
34 #define MAX_GLYPH_BUFFER_LEN 1024
35 
36 typedef unsigned char uint8;
37 typedef unsigned short uint16;
38 typedef uint16 word12;
39 typedef unsigned int uint;
40 
41 typedef struct
42 {
43     word12 column[CARD_COL_COUNT];
44 } card_image_t;
45 
46 enum parse_state
47 {
48     Idle,
49     StartingJob,
50     PunchGlyphLookup,
51     EndOfHeader,
52     CacheCard,
53     EndOfDeck,
54     EndOfJob
55 };
56 
57 enum parse_event
58 {
59     NoEvent,
60     BannerCard,
61     EndOfDeckCard,
62     Card,
63     Done
64 };
65 
66 typedef struct card_cache_node CARD_CACHE_ENTRY;
67 
68 struct card_cache_node
69 {
70     card_image_t *card;
71     CARD_CACHE_ENTRY *next_entry;
72 };
73 
74 typedef struct
75 {
76     CARD_CACHE_ENTRY *first_cache_card;
77     CARD_CACHE_ENTRY *last_cache_card;
78 } CARD_CACHE;
79 
80 static bool output_auto   = true;
81 static bool output_glyphs = false;
82 static bool output_mcc    = false;
83 static bool output_cards  = false;
84 static bool output_flip   = false;
85 static bool output_raw    = false;
86 static bool output_debug  = false;
87 
88 enum parse_state current_state = Idle;
89 
90 char glyph_buffer[MAX_GLYPH_BUFFER_LEN];
91 
92 CARD_CACHE banner_card_cache;
93 CARD_CACHE data_card_cache;
94 CARD_CACHE trailer_card_cache;
95 
96 // Card image of the banner card
97 static word12 banner_card[] =
98     {
99         00005,
100         00000,
101         00000,
102         07700,
103         07704,
104         07704,
105         07704,
106         07700,
107         00000,
108         00000,
109         00077,
110         00477,
111         00477,
112         00477,
113         00077,
114         00000,
115         00000,
116         07700,
117         07704,
118         07704,
119         07704,
120         07700,
121         00000,
122         00000,
123         00077,
124         00477,
125         00477,
126         00477,
127         00077,
128         00000,
129         00000,
130         07700,
131         07704,
132         07704,
133         07704,
134         07700,
135         00000,
136         00000,
137         00077,
138         00477,
139         00477,
140         00477,
141         00077,
142         00000,
143         00000,
144         07700,
145         07704,
146         07704,
147         07704,
148         07700,
149         00000,
150         00000,
151         00077,
152         00477,
153         00477,
154         00477,
155         00077,
156         00000,
157         00000,
158         07700,
159         07704,
160         07704,
161         07704,
162         07700,
163         00000,
164         00000,
165         00077,
166         00477,
167         00477,
168         00477,
169         00077,
170         00000,
171         00000,
172         07700,
173         07704,
174         07704,
175         07704,
176         07700,
177         00000,
178         00005,
179 };
180 
181 // Card image of the End Of Deck card
182 static word12 end_of_deck_card[] =
183     {
184         00005,
185         00000,
186         00000,
187         00000,
188         00000,
189         00000,
190         00000,
191         00000,
192         00000,
193         00000,
194         00000,
195         00000,
196         00000,
197         00000,
198         00000,
199         00000,
200         00000,
201         02000,
202         02400,
203         02400,
204         02400,
205         03700,
206         00000,
207         00000,
208         03721,
209         02112,
210         02104,
211         02104,
212         03737,
213         00000,
214         00000,
215         00021,
216         00021,
217         00021,
218         00021,
219         00037,
220         00000,
221         00000,
222         01621,
223         02125,
224         02125,
225         02125,
226         03737,
227         00000,
228         00000,
229         03716,
230         00221,
231         00421,
232         01021,
233         03737,
234         00000,
235         00000,
236         02100,
237         02500,
238         02500,
239         02500,
240         03700,
241         00000,
242         00000,
243         00000,
244         00000,
245         00000,
246         00000,
247         00000,
248         00000,
249         00000,
250         00000,
251         00000,
252         00000,
253         00000,
254         00000,
255         00000,
256         00000,
257         00000,
258         00000,
259         00000,
260         00000,
261         00000,
262         00000,
263         00005,
264 };
265 
266 /*
267  *   MCC Punch Codes -> ASCI Conversion Table
268  * The table entry is the MCC Punch Code and the
269  * index of the entry is the ASCII character code.
270  */
271 
272 static word12 mcc_punch_codes[128] =
273     {
274         05403, // ?   (9-12-0-8-1)  0xb03  2819
275         04401, // ?   (9-12-1)  0x901  2305
276         04201, // ?   (9-12-2)  0x881  2177
277         04101, // ?   (9-12-3)  0x841  2113
278         00005, // ?   (9-7)  0x5  5
279         01023, // ?   (9-0-8-5)  0x213  531
280         01013, // ?   (9-0-8-6)  0x20b  523
281         01007, // ?   (9-0-8-7)  0x207  519
282         02011, // ?   (9-11-6)  0x409  1033
283         04021, // ?   (9-12-5)  0x811  2065
284         02021, // ?   (9-11-5)  0x411  1041
285         04103, // ?   (9-12-8-3)  0x843  2115
286         04043, // ?   (9-12-8-4)  0x823  2083
287         04023, // ?   (9-12-8-5)  0x813  2067
288         04013, // ?   (9-12-8-6)  0x80b  2059
289         04007, // ?   (9-12-8-7)  0x807  2055
290         06403, // ?   (12-11-9-8-1)  0xd03  3331
291         02401, // ?   (9-11-1)  0x501  1281
292         02201, // ?   (9-11-2)  0x481  1153
293         02101, // ?   (9-11-3)  0x441  1089
294         00043, // ?   (9-8-4)  0x23  35
295         00023, // ?   (9-8-5)  0x13  19
296         00201, // ?   (9-2)  0x81  129
297         01011, // ?   (9-0-6)  0x209  521
298         02003, // ?   (9-11-8)  0x403  1027
299         02403, // ?   (9-11-8-1)  0x503  1283
300         00007, // ?   (9-8-7)  0x7  7
301         01005, // ?   (9-0-7)  0x205  517
302         02043, // ?   (9-11-8-4)  0x423  1059
303         02023, // ?   (9-11-8-5)  0x413  1043
304         02013, // ?   (9-11-8-6)  0x40b  1035
305         02007, // ?   (9-11-8-7)  0x407  1031
306         00000, //     ()  0x0  0
307         02202, // !   (11-8-2)  0x482  1154
308         00006, // "   (8-7)  0x6  6
309         00102, // #   (8-3)  0x42  66
310         02102, // $   (11-8-3)  0x442  1090
311         01042, // %   (0-8-4)  0x222  546
312         04000, // &   (12)  0x800  2048
313         00022, // '   (8-5)  0x12  18
314         04022, // (   (12-8-5)  0x812  2066
315         02022, // )   (11-8-5)  0x412  1042
316         02042, // *   (11-8-4)  0x422  1058
317         04012, // +   (12-8-6)  0x80a  2058
318         01102, // ,   (0-8-3)  0x242  578
319         02000, // -   (11)  0x400  1024
320         04102, // .   (12-8-3)  0x842  2114
321         01400, // /   (0-1)  0x300  768
322         01000, // 0   (0)  0x200  512
323         00400, // 1   (1)  0x100  256
324         00200, // 2   (2)  0x80  128
325         00100, // 3   (3)  0x40  64
326         00040, // 4   (4)  0x20  32
327         00020, // 5   (5)  0x10  16
328         00010, // 6   (6)  0x8  8
329         00004, // 7   (7)  0x4  4
330         00002, // 8   (8)  0x2  2
331         00001, // 9   (9)  0x1  1
332         00202, // :   (8-2)  0x82  130
333         02012, // ;   (11-8-6)  0x40a  1034
334         04042, // <   (12-8-4)  0x822  2082
335         00012, // =   (8-6)  0xa  10
336         01012, // >   (0-8-6)  0x20a  522
337         01006, // ?   (0-8-7)  0x206  518
338         00042, // @   (8-4)  0x22  34
339         04400, // A   (12-1)  0x900  2304
340         04200, // B   (12-2)  0x880  2176
341         04100, // C   (12-3)  0x840  2112
342         04040, // D   (12-4)  0x820  2080
343         04020, // E   (12-5)  0x810  2064
344         04010, // F   (12-6)  0x808  2056
345         04004, // G   (12-7)  0x804  2052
346         04002, // H   (12-8)  0x802  2050
347         04001, // I   (12-9)  0x801  2049
348         02400, // J   (11-1)  0x500  1280
349         02200, // K   (11-2)  0x480  1152
350         02100, // L   (11-3)  0x440  1088
351         02040, // M   (11-4)  0x420  1056
352         02020, // N   (11-5)  0x410  1040
353         02010, // O   (11-6)  0x408  1032
354         02004, // P   (11-7)  0x404  1028
355         02002, // Q   (11-8)  0x402  1026
356         02001, // R   (11-9)  0x401  1025
357         01200, // S   (0-2)  0x280  640
358         01100, // T   (0-3)  0x240  576
359         01040, // U   (0-4)  0x220  544
360         01020, // V   (0-5)  0x210  528
361         01010, // W   (0-6)  0x208  520
362         01004, // X   (0-7)  0x204  516
363         01002, // Y   (0-8)  0x202  514
364         01001, // Z   (0-9)  0x201  513
365         05022, // [   (12-0-8-5)  0xa12  2578
366         04202, // \   (12-8-2)  0x882  2178
367         06022, // ]   (12-11-8-5)  0xc12  3090
368         02006, // ^   (11-8-7)  0x406  1030
369         01022, // _   (0-8-5)  0x212  530
370         00402, // `   (8-1)  0x102  258
371         05400, // a   (12-0-1)  0xb00  2816
372         05200, // b   (12-0-2)  0xa80  2688
373         05100, // c   (12-0-3)  0xa40  2624
374         05040, // d   (12-0-4)  0xa20  2592
375         05020, // e   (12-0-5)  0xa10  2576
376         05010, // f   (12-0-6)  0xa08  2568
377         05004, // g   (12-0-7)  0xa04  2564
378         05002, // h   (12-0-8)  0xa02  2562
379         05001, // i   (12-0-9)  0xa01  2561
380         06400, // j   (12-11-1)  0xd00  3328
381         06200, // k   (12-11-2)  0xc80  3200
382         06100, // l   (12-11-3)  0xc40  3136
383         06040, // m   (12-11-4)  0xc20  3104
384         06020, // n   (12-11-5)  0xc10  3088
385         06010, // o   (12-11-6)  0xc08  3080
386         06004, // p   (12-11-7)  0xc04  3076
387         06002, // q   (12-11-8)  0xc02  3074
388         06001, // r   (12-11-9)  0xc01  3073
389         03200, // s   (11-0-2)  0x680  1664
390         03100, // t   (11-0-3)  0x640  1600
391         03040, // u   (11-0-4)  0x620  1568
392         03020, // v   (11-0-5)  0x610  1552
393         03010, // w   (11-0-6)  0x608  1544
394         03004, // x   (11-0-7)  0x604  1540
395         03002, // y   (11-0-8)  0x602  1538
396         03001, // z   (11-0-9)  0x601  1537
397         05000, // {   (12-0)  0xa00  2560
398         04006, // |   (12-8-7)  0x806  2054
399         03000, // }   (11-0)  0x600  1536
400         03400, // ~   (11-0-1)  0x700  1792
401         04005, //    (12-7-9)  0x805  2053
402 };
403 
404 static word12 row_bit_masks[] =
405     {
406         0x800,
407         0x400,
408         0x200,
409         0x100,
410         0x080,
411         0x040,
412         0x020,
413         0x010,
414         0x008,
415         0x004,
416         0x002,
417         0x001,
418 };
419 
mcc_to_ascii(word12 punch_code)420 static int mcc_to_ascii(word12 punch_code)
421 {
422     for (uint i = 0; i < 128; i++)
423     {
424         if (mcc_punch_codes[i] == punch_code)
425         {
426             return i;
427         }
428     }
429 
430     return -1;
431 }
432 
433 // Scans data cards to determine if they contain all valid MCC punch codes
check_for_valid_mcc_cards()434 static bool check_for_valid_mcc_cards()
435 {
436     CARD_CACHE_ENTRY *current_card = data_card_cache.first_cache_card;
437     while (current_card != NULL)
438     {
439         for (uint col = 0; col < CARD_COL_COUNT; col++)
440         {
441             if (mcc_to_ascii(current_card->card->column[col]) == -1)
442             {
443                 return false;
444             }
445         }
446 
447         current_card = current_card->next_entry;
448     }
449     return true;
450 }
451 
convert_mcc_to_ascii(word12 * buffer,char * ascii_string)452 static void convert_mcc_to_ascii(word12 *buffer, char *ascii_string)
453 {
454     for (uint i = 0; i < CARD_COL_COUNT; i++)
455     {
456         int c = mcc_to_ascii(buffer[i]);
457         if (output_debug)
458         {
459             fprintf(stderr, "+++ Punch Code %04o = '%c'\n", buffer[i], c);
460         }
461         if (c == -1)
462         {
463             c = ' ';
464         }
465         ascii_string[i] = c;
466     }
467     ascii_string[CARD_COL_COUNT] = 0;
468 }
469 
470 /*
471  *                  Glyph Pattern Lookup
472  * This is the parsing of the "lace" cards and extracting the ASCII characters
473  * have been punched into the cards (as glphys) so the operator knows how to
474  * deliver the deck.
475  */
476 
477 #define NUM_GLYPH_CHAR_PATTERNS 45
478 
479 static uint8 glyph_char_patterns[NUM_GLYPH_CHAR_PATTERNS][CHAR_MATRIX_BYTES] =
480     {
481         // Asterisk
482         {037, 037, 037, 037, 037},
483         // Space
484         {000, 000, 000, 000, 000},
485         // Period
486         {000, 003, 003, 003, 000},
487         // >
488         {021, 000, 012, 000, 004},
489         // A
490         {037, 024, 024, 024, 037},
491         // B
492         {037, 025, 025, 025, 012},
493         // C
494         {037, 021, 021, 021, 021},
495         // D
496         {037, 021, 021, 021, 016},
497         // E
498         {037, 025, 025, 025, 021},
499         // F
500         {037, 024, 024, 024, 020},
501         // G
502         {037, 021, 021, 025, 027},
503         // H
504         {037, 004, 004, 004, 037},
505         // I
506         {021, 021, 037, 021, 021},
507         // J
508         {003, 001, 001, 001, 037},
509         // K
510         {037, 004, 004, 012, 021},
511         // L
512         {037, 001, 001, 001, 001},
513         // M
514         {037, 010, 004, 010, 037},
515         // N
516         {037, 010, 004, 002, 037},
517         // O
518         {037, 021, 021, 021, 037},
519         // P
520         {037, 024, 024, 024, 034},
521         // Q
522         {037, 021, 025, 023, 037},
523         // R
524         {037, 024, 024, 026, 035},
525         // S
526         {035, 025, 025, 025, 027},
527         // T
528         {020, 020, 037, 020, 020},
529         // U
530         {037, 001, 001, 001, 037},
531         // V
532         {030, 006, 001, 006, 030},
533         // W
534         {037, 002, 004, 002, 037},
535         // X
536         {021, 012, 004, 012, 021},
537         // Y
538         {020, 010, 007, 010, 020},
539         // Z
540         {021, 027, 025, 035, 021},
541         // 0
542         {016, 021, 021, 021, 016},
543         // 1
544         {000, 010, 000, 037, 000},
545         // 2
546         {023, 025, 025, 025, 035},
547         // 3
548         {021, 025, 025, 025, 037},
549         // 4
550         {034, 004, 004, 004, 037},
551         // 5
552         {035, 025, 025, 025, 022},
553         // 6
554         {037, 005, 005, 005, 007},
555         // 7
556         {020, 021, 022, 024, 030},
557         // 8
558         {012, 025, 025, 025, 012},
559         // 9
560         {034, 024, 024, 024, 037},
561         // Underscore
562         {001, 001, 001, 001, 001},
563         // Hyphen
564         {000, 004, 004, 004, 000},
565         // (
566         {000, 004, 012, 021, 000},
567         // )
568         {000, 021, 012, 004, 000},
569         // /
570         {001, 002, 004, 010, 020}};
571 
572 static char glyph_chars[NUM_GLYPH_CHAR_PATTERNS] =
573     {
574         '*', ' ', '.', '>', 'A', 'B', 'C', 'D', 'E', 'F',
575         'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
576         'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
577         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
578         '_', '-', '(', ')', '/'};
579 
580 static uint8 glyph_starting_column[11] =
581     {
582         73, 66, 59, 52, 45, 38, 31, 24, 17, 10, 3};
583 
584 static char row_prefix_chars[] = "&-0123456789";
585 
print_card(FILE * out_file,card_image_t * card,bool flip_card)586 static void print_card(FILE *out_file, card_image_t *card, bool flip_card)
587 {
588     if (output_cards)
589     {
590         fprintf(out_file, " +--------------------------------------------------------------------------------+\n");
591         for (int row = 0; row < 12; row++)
592         {
593             fprintf(out_file, "%c|", row_prefix_chars[row]);
594             if (flip_card)
595             {
596                 for (int col = CARD_COL_COUNT - 1; col >= 0; col--)
597                 {
598                     fprintf(out_file, card->column[col] & row_bit_masks[row] ? "*" : " ");
599                 }
600             }
601             else
602             {
603                 for (int col = 0; col < CARD_COL_COUNT; col++)
604                 {
605                     fprintf(out_file, card->column[col] & row_bit_masks[row] ? "*" : " ");
606                 }
607             }
608             fprintf(out_file, "|\n");
609         }
610         fprintf(out_file, " +--------------------------------------------------------------------------------+\n\n");
611     }
612 }
613 
log_char_matrix_pattern(uint8 * char_matrix)614 static void log_char_matrix_pattern(uint8 *char_matrix)
615 {
616     fprintf(stderr, "\nChar Matrix\n");
617     for (uint col_offset = 0; col_offset < CHAR_MATRIX_BYTES; col_offset++)
618     {
619         fprintf(stderr, " %03o\n", char_matrix[col_offset]);
620     }
621 
622     fprintf(stderr, "\r\n");
623     for (uint row = 0; row < 5; row++)
624     {
625         for (uint col = 0; col < CHAR_MATRIX_BYTES; col++)
626         {
627             if ((char_matrix[col] >> (4 - row)) & 0x1)
628             {
629                 fprintf(stderr, "*");
630             }
631             else
632             {
633                 fprintf(stderr, " ");
634             }
635         }
636         fprintf(stderr, "\r\n");
637     }
638     fprintf(stderr, "\r\n");
639 }
640 
search_glyph_patterns(uint8 * matrix)641 static char search_glyph_patterns(uint8 *matrix)
642 {
643     for (int pattern = 0; pattern < NUM_GLYPH_CHAR_PATTERNS; pattern++)
644     {
645         if (memcmp(matrix, &glyph_char_patterns[pattern], CHAR_MATRIX_BYTES) == 0)
646         {
647             return glyph_chars[pattern];
648         }
649     }
650 
651     fprintf(stderr, "*** Warning: Punch found unknown block character pattern\n");
652     log_char_matrix_pattern(matrix);
653 
654     return ' ';
655 }
656 
get_lace_char(const word12 * buffer,uint char_pos)657 static char get_lace_char(const word12 *buffer, uint char_pos)
658 {
659     if (char_pos >= GLYPHS_PER_CARD)
660     {
661         fprintf(stderr, "*** Error: Attempt to read punch block character out of range (%u)\n", char_pos);
662         exit(4);
663     }
664 
665     bool top = char_pos < 11;                                      // Top or bottom line of characters
666     uint char_offset = (char_pos < 11) ? char_pos : char_pos - 11; // Character number in the line
667     word12 col_buffer[5];                                          // The extracted 5 columns for the character
668 
669     // Extract the five 12-bit words from the main buffer that make up the character
670     // Note that in this process we reverse the character image so it reads normally
671     // (characters are punched in reverse)
672     for (uint col_offset = 0; col_offset < 5; col_offset++)
673     {
674         col_buffer[4 - col_offset] = buffer[glyph_starting_column[char_offset] + col_offset];
675     }
676 
677     // Now shift the characters into the 5x5 matrix buffer
678     uint8 char_matrix[CHAR_MATRIX_BYTES];
679 
680     for (uint col_offset = 0; col_offset < CHAR_MATRIX_BYTES; col_offset++)
681     {
682         char_matrix[col_offset] = (col_buffer[col_offset] >> (top ? 6 : 0)) & 0x1F;
683     }
684 
685     char c = search_glyph_patterns(char_matrix);
686 
687     return c;
688 }
689 
scan_card_for_glyphs(card_image_t * card)690 static void scan_card_for_glyphs(card_image_t *card)
691 {
692     for (uint c_pos = 0; c_pos < 22; c_pos++)
693     {
694         char c = get_lace_char(card->column, c_pos);
695         uint current_length = strlen(glyph_buffer);
696         if (current_length < (sizeof(glyph_buffer) - 1))
697         {
698             glyph_buffer[current_length++] = c;
699             glyph_buffer[current_length] = 0;
700         }
701     }
702 }
703 
allocate_card()704 static card_image_t *allocate_card()
705 {
706     card_image_t *card = malloc(sizeof(card_image_t));
707     if (card == NULL)
708     {
709         fprintf(stderr, "*** Error: Failed to allocate card image!\n");
710         exit(1);
711     }
712 
713     memset(card, 0, sizeof(card_image_t));
714 
715     return card;
716 }
717 
read_card(FILE * in_file)718 static card_image_t *read_card(FILE *in_file)
719 {
720     uint8 byte_buffer[BYTES_PER_CARD];
721 
722     int bytes_read = fread(&byte_buffer, 1, BYTES_PER_CARD, in_file);
723 
724     if (bytes_read == 0)
725     {
726         if (feof(in_file))
727         {
728             // We've hit the end of file so just bail out
729             return NULL;
730         }
731         else if (ferror(in_file))
732         {
733             // Something bad happened, report the error and exit
734             perror("Reading card file\n");
735             exit(2);
736         }
737         else
738         {
739             fprintf(stderr, "*** Error: fread returned zero but failed to set the error or eof flags!\n");
740             exit(2);
741         }
742     }
743 
744     // Make sure we read a full card
745     if (bytes_read != BYTES_PER_CARD)
746     {
747         fprintf(stderr, "*** Error: failed to read a full card (only read %d of %d bytes)\n", bytes_read, BYTES_PER_CARD);
748         exit(3);
749     }
750 
751     // We have a full card so allocate a card and convert the byte buffer into the card image
752     card_image_t *card = allocate_card();
753 
754     for (int current_nibble = 0; current_nibble < NIBBLES_PER_CARD; current_nibble++)
755     {
756         int byte_offset = current_nibble / 2;
757         int nibble_offset = (2 - (current_nibble % 3)) * 4;
758         int col = current_nibble / 3;
759 
760         word12 nibble = (current_nibble & 0x1) ? byte_buffer[byte_offset] : (byte_buffer[byte_offset] >> 4);
761         nibble &= 0x00000F;
762         card->column[col] |= (nibble << nibble_offset);
763     }
764 
765     if (output_debug)
766     {
767         print_card(stderr, card, false);
768     }
769 
770     return card;
771 }
772 
save_card_in_cache(CARD_CACHE * card_cache,card_image_t * card)773 static void save_card_in_cache(CARD_CACHE *card_cache, card_image_t *card)
774 {
775     CARD_CACHE_ENTRY *new_entry = malloc(sizeof(CARD_CACHE_ENTRY));
776 
777     new_entry->card = card;
778     new_entry->next_entry = NULL;
779 
780     if (card_cache->first_cache_card == NULL)
781     {
782         card_cache->first_cache_card = new_entry;
783         card_cache->last_cache_card = new_entry;
784     }
785     else
786     {
787         card_cache->last_cache_card->next_entry = new_entry;
788         card_cache->last_cache_card = new_entry;
789     }
790 }
791 
print_event(enum parse_event event)792 static void print_event(enum parse_event event)
793 {
794     switch (event)
795     {
796     case NoEvent:
797         fprintf(stderr, "[No Event]");
798         break;
799     case BannerCard:
800         fprintf(stderr, "[Banner Card]");
801         break;
802     case EndOfDeckCard:
803         fprintf(stderr, "[End Of Deck Card]");
804         break;
805     case Card:
806         fprintf(stderr, "[Card]");
807         break;
808     case Done:
809         fprintf(stderr, "[Done]");
810         break;
811     default:
812         fprintf(stderr, "[unknown event %d]", event);
813         break;
814     }
815 }
816 
print_state(enum parse_state state)817 static void print_state(enum parse_state state)
818 {
819     switch (state)
820     {
821     case Idle:
822         fprintf(stderr, "[Idle]");
823         break;
824     case StartingJob:
825         fprintf(stderr, "[Starting Job]");
826         break;
827     case PunchGlyphLookup:
828         fprintf(stderr, "[Punch Glyph Lookup]");
829         break;
830     case EndOfHeader:
831         fprintf(stderr, "[End Of Header]");
832         break;
833     case CacheCard:
834         fprintf(stderr, "[Cache Card]");
835         break;
836     case EndOfDeck:
837         fprintf(stderr, "[End Of Deck]");
838         break;
839     case EndOfJob:
840         fprintf(stderr, "[End Of Job]");
841         break;
842     default:
843         fprintf(stderr, "[unknown state %d]", state);
844         break;
845     }
846 }
847 
transition_state(enum parse_event event,enum parse_state new_state)848 static void transition_state(enum parse_event event, enum parse_state new_state)
849 {
850     if (output_debug)
851     {
852         fprintf(stderr, ">>> State Transition: ");
853         print_event(event);
854         fprintf(stderr, " = ");
855         print_state(current_state);
856         fprintf(stderr, " -> ");
857         print_state(new_state);
858         fprintf(stderr, "\r\n");
859     }
860 
861     current_state = new_state;
862 }
863 
do_state_idle(enum parse_event event)864 static enum parse_event do_state_idle(enum parse_event event)
865 {
866     transition_state(event, Idle);
867 
868     // no action
869 
870     return NoEvent;
871 }
872 
do_state_starting_job(enum parse_event event,card_image_t * card)873 static enum parse_event do_state_starting_job(enum parse_event event, card_image_t *card)
874 {
875     transition_state(event, StartingJob);
876 
877     glyph_buffer[0] = 0;                          // Clear Glyph Buffer
878     save_card_in_cache(&banner_card_cache, card); // Save card in cache
879 
880     return NoEvent;
881 }
882 
do_state_scan_card_for_glyphs(enum parse_event event,card_image_t * card)883 static enum parse_event do_state_scan_card_for_glyphs(enum parse_event event, card_image_t *card)
884 {
885     transition_state(event, PunchGlyphLookup);
886 
887     scan_card_for_glyphs(card);
888 
889     save_card_in_cache(&banner_card_cache, card); // Save card in cache
890 
891     return NoEvent;
892 }
893 
do_state_end_of_header(enum parse_event event,card_image_t * card)894 static enum parse_event do_state_end_of_header(enum parse_event event, card_image_t *card)
895 {
896     transition_state(event, EndOfHeader);
897 
898     save_card_in_cache(&banner_card_cache, card); // Save card in cache
899 
900     if (output_debug)
901     {
902         fprintf(stderr, "\n++++ Glyph Buffer ++++\n'%s'\n", glyph_buffer);
903     }
904 
905     return NoEvent;
906 }
907 
do_state_cache_card(enum parse_event event,card_image_t * card)908 static enum parse_event do_state_cache_card(enum parse_event event, card_image_t *card)
909 {
910     transition_state(event, CacheCard);
911 
912     save_card_in_cache(&data_card_cache, card); // Save card in cache
913 
914     return NoEvent;
915 }
916 
do_state_end_of_deck(enum parse_event event,card_image_t * card)917 static enum parse_event do_state_end_of_deck(enum parse_event event, card_image_t *card)
918 {
919     transition_state(event, EndOfDeck);
920 
921     save_card_in_cache(&trailer_card_cache, card); // Save card in cache
922 
923     return NoEvent;
924 }
925 
do_state_end_of_job(enum parse_event event,card_image_t * card)926 static enum parse_event do_state_end_of_job(enum parse_event event, card_image_t *card)
927 {
928     transition_state(event, EndOfJob);
929 
930     save_card_in_cache(&trailer_card_cache, card); // Save card in cache
931 
932     return Done;
933 }
934 
unexpected_event(enum parse_event event)935 static void unexpected_event(enum parse_event event)
936 {
937     if (output_debug)
938     {
939         fprintf(stderr, "*** Unexpected event ");
940         print_event(event);
941 
942         fprintf(stderr, " in state ");
943         print_state(current_state);
944 
945         fprintf(stderr, "***\n");
946     }
947 }
948 
parse_card(card_image_t * card)949 static void parse_card(card_image_t *card)
950 {
951     enum parse_event event = Card;
952 
953     if (memcmp(card, end_of_deck_card, sizeof(end_of_deck_card)) == 0)
954     {
955         if (output_debug)
956         {
957             fprintf(stderr, "*** Found End Of Deck Card ***\n");
958         }
959         event = EndOfDeckCard;
960     }
961 
962     if (memcmp(card, banner_card, sizeof(banner_card)) == 0)
963     {
964         if (output_debug)
965         {
966             fprintf(stderr, "*** Found Banner Card ***\n");
967         }
968         event = BannerCard;
969     }
970 
971     while (event != NoEvent)
972     {
973         enum parse_event current_event = event;
974         event = NoEvent;
975 
976         switch (current_event)
977         {
978         case BannerCard:
979             switch (current_state)
980             {
981             case Idle:
982                 event = do_state_starting_job(current_event, card);
983                 break;
984 
985             case PunchGlyphLookup:
986                 event = do_state_end_of_header(current_event, card);
987                 break;
988 
989             case EndOfDeck:
990                 event = do_state_end_of_job(current_event, card);
991                 break;
992 
993             default:
994                 unexpected_event(current_event);
995                 break;
996             }
997             break;
998 
999         case EndOfDeckCard:
1000             switch (current_state)
1001             {
1002             case StartingJob:
1003                 event = do_state_end_of_deck(current_event, card);
1004                 break;
1005 
1006             case PunchGlyphLookup:
1007                 event = do_state_end_of_deck(current_event, card);
1008                 break;
1009 
1010             case EndOfHeader:
1011                 event = do_state_end_of_deck(current_event, card);
1012                 break;
1013 
1014             case CacheCard:
1015                 event = do_state_end_of_deck(current_event, card);
1016                 break;
1017 
1018             case EndOfDeck:
1019                 event = do_state_end_of_deck(current_event, card);
1020                 break;
1021 
1022             default:
1023                 unexpected_event(current_event);
1024                 break;
1025             }
1026             break;
1027 
1028         case Card:
1029             switch (current_state)
1030             {
1031             case StartingJob:
1032                 event = do_state_scan_card_for_glyphs(current_event, card);
1033                 break;
1034 
1035             case PunchGlyphLookup:
1036                 event = do_state_scan_card_for_glyphs(current_event, card);
1037                 break;
1038 
1039             case EndOfHeader:
1040                 event = do_state_cache_card(current_event, card);
1041                 break;
1042 
1043             case CacheCard:
1044                 event = do_state_cache_card(current_event, card);
1045                 break;
1046 
1047             case EndOfDeck:
1048                 event = do_state_cache_card(current_event, card);
1049                 break;
1050 
1051             default:
1052                 unexpected_event(current_event);
1053                 break;
1054             }
1055             break;
1056 
1057         case Done:
1058             switch (current_state)
1059             {
1060             case EndOfJob:
1061                 event = do_state_idle(current_event);
1062                 break;
1063 
1064             default:
1065                 unexpected_event(current_event);
1066                 break;
1067             }
1068             break;
1069 
1070         default:
1071             fprintf(stderr, "*** Error: Punch received unknown event!\n");
1072             break;
1073         }
1074     }
1075 }
1076 
parse_cards(FILE * in_file)1077 static void parse_cards(FILE *in_file)
1078 {
1079     card_image_t *card;
1080     while ( (card = ( read_card(in_file) ) ) )
1081     {
1082         parse_card(card);
1083         if (output_debug)
1084         {
1085             fprintf(stderr, "\n");
1086         }
1087     }
1088 }
1089 
init()1090 static void init()
1091 {
1092     memset(&banner_card_cache, 0, sizeof(banner_card_cache));
1093     memset(&data_card_cache, 0, sizeof(data_card_cache));
1094     memset(&trailer_card_cache, 0, sizeof(trailer_card_cache));
1095 }
1096 
print_help(char * program)1097 static void print_help(char *program)
1098 {
1099     printf("\nMultics Punch Utility Program\n");
1100     printf("\nInvoking:\n");
1101     printf("    %s [options]\n", program);
1102     printf("Where options are:\n");
1103     printf("  -h, --help      = Output this message\n");
1104     printf("  -a, --auto      = Attempt to automatically determine the type of card deck\n");
1105     printf("                      (Default; disables any previously enabled output options)\n");
1106     printf("  -n, --no-auto   = Disable auto selection of the card format\n");
1107     printf("                      (You must specify output control options)\n");
1108     printf("  -v, --version   = Add version information to stderr output\n");
1109     printf("\nOutput control options:\n");
1110     printf("    By default 'auto' mode is active, where a scan attempts to determine the\n");
1111     printf("    type of deck.  The scan order is: 'MCC', '7punch', and 'raw' (if neither\n");
1112     printf("    of  the  first  two seem  to apply).  Note  that  the options  below are\n");
1113     printf("    mutually exclusive.\n");
1114     printf("  -7, --7punch    = Interpret the cards as 7punch data and output the data\n");
1115     printf("                      (currently not supported!)\n");
1116     printf("  -c, --cards     = Output an ASCII art form of the punched cards with an '*'\n");
1117     printf("                      representing the punches\n");
1118     printf("  -f, --flip      = If --cards is specified, the banner cards will be 'flipped'\n");
1119     printf("                      so they can be read normally\n");
1120     printf("  -g, --glyphs    = Output the glyphs parsed from the banner cards as ASCII\n");
1121     printf("  -m, --mcc       = Interpret the cards as MCC Punch Codes\n");
1122     printf("                      (Invalid punch codes will be converted to spaces)\n");
1123     printf("  -r, --raw       = Dump the raw card data as 12-bit words in column order\n");
1124     printf("\nThis program will  read a  card  spool file produced  by the DPS8M Simulator,\n");
1125     printf("parse it, and produce the requested output on standard output. Note that only\n");
1126     printf("one output mode may be selected.\n");
1127 
1128     printf("\n");
1129     printf("\n This software is made available under the terms of the ICU License,");
1130     printf("\n version 1.8.1 or later.  For complete details, see the \"LICENSE.md\"");
1131     printf("\n included or https://gitlab.com/dps8m/dps8m/-/blob/master/LICENSE.md");
1132     printf("\n");
1133 }
1134 
1135 #ifdef USE_POPT
1136 static struct poptOption long_options[] = {
1137 #else
1138 static struct option long_options[] = {
1139 #endif /* ifdef USE_POPT */
1140 #ifdef USE_POPT
1141     {"7punch",  '7', POPT_ARG_NONE, NULL, '7', "7punch",  "7PUNCH"},
1142     {"auto",    'a', POPT_ARG_NONE, NULL, 'a', "auto",    "AUTO"},
1143     {"cards",   'c', POPT_ARG_NONE, NULL, 'c', "cards",   "CARDS"},
1144     {"debug",   'd', POPT_ARG_NONE, NULL, 'd', "debug",   "DEBUG"},
1145     {"flip",    'f', POPT_ARG_NONE, NULL, 'f', "flip",    "FLIP"},
1146     {"glyphs",  'g', POPT_ARG_NONE, NULL, 'g', "glyphs",  "GLYPHS"},
1147     {"help",    'h', POPT_ARG_NONE, NULL, 'h', "help",    "HELP"},
1148     {"mcc",     'm', POPT_ARG_NONE, NULL, 'm', "mcc",     "MCC"},
1149     {"no-auto", 'n', POPT_ARG_NONE, NULL, 'n', "no-auto", "NOAUTO"},
1150     {"raw",     'r', POPT_ARG_NONE, NULL, 'r', "raw",     "RAW"},
1151     {"version", 'v', POPT_ARG_NONE, NULL, 'v', "version", "VERSION"}
1152 #else
1153     {"7punch",  no_argument, 0, '7'},
1154     {"auto",    no_argument, 0, 'a'},
1155     {"cards",   no_argument, 0, 'c'},
1156     {"debug",   no_argument, 0, 'd'},
1157     {"flip",    no_argument, 0, 'f'},
1158     {"glyphs",  no_argument, 0, 'g'},
1159     {"help",    no_argument, 0, 'h'},
1160     {"mcc",     no_argument, 0, 'm'},
1161     {"no-auto", no_argument, 0, 'n'},
1162     {"raw",     no_argument, 0, 'r'},
1163     {"version", no_argument, 0, 'v'},
1164     {0,         0,           0,  0 }
1165 #endif /* ifdef USE_POPT */
1166 };
1167 
parse_options(int argc,char * argv[])1168 static void parse_options(int argc, char *argv[])
1169 {
1170     int c;
1171     bool done = false;
1172 
1173     while (!done)
1174     {
1175 #ifdef USE_POPT
1176        poptContext opt_con;
1177       opt_con = poptGetContext(NULL, argc, (const char **)argv, long_options, 0);
1178       while ((c = poptGetNextOpt(opt_con)) >= 0) {
1179 #else
1180         int option_index = 0;
1181         c = getopt_long(argc, argv, "7acdfghmnrvV", long_options, &option_index);
1182 #endif /* ifdef USE_POPT */
1183 
1184         switch (c)
1185         {
1186         case -1:
1187             done = true;
1188             break;
1189 
1190         case '7':
1191             fprintf(stderr, "*** Sorry: 7punch format is not yet supported!\n");
1192             exit(1);
1193 
1194         case 'a':
1195             output_auto = true;
1196             output_cards = false;
1197             output_glyphs = false;
1198             output_mcc = false;
1199             output_raw = false;
1200             break;
1201 
1202         case 'c':
1203             output_auto = false;
1204             output_cards = true;
1205             output_glyphs = false;
1206             output_mcc = false;
1207             output_raw = false;
1208             break;
1209 
1210         case 'd':
1211             output_debug = true;
1212             break;
1213 
1214         case 'f':
1215             output_flip = true;
1216             break;
1217 
1218         case 'g':
1219             output_auto = false;
1220             output_cards = false;
1221             output_glyphs = true;
1222             output_mcc = false;
1223             output_raw = false;
1224             break;
1225 
1226         case 'm':
1227             output_auto = false;
1228             output_cards = false;
1229             output_glyphs = false;
1230             output_mcc = true;
1231             output_raw = false;
1232             break;
1233 
1234         case 'n':
1235             output_auto = false;
1236             break;
1237 
1238         case 'r':
1239             output_auto = false;
1240             output_cards = false;
1241             output_glyphs = false;
1242             output_mcc = false;
1243             output_raw = true;
1244             break;
1245 
1246         case 'v':
1247             fprintf(stderr, "\nVersion 0.1\n");
1248             break;
1249 
1250         case 'h':
1251         case '?':
1252             print_help(argv[0]);
1253             exit(1);
1254 
1255         default:
1256             fprintf(stderr, "*** Internal Error: did not recognize option when parsing options, got %d\n", c);
1257             exit(1);
1258         }
1259 #ifdef USE_POPT
1260      }
1261 #endif /* ifdef USE_POPT */
1262     }
1263 
1264     // Verify selected options
1265     bool override_in_effect = output_cards || output_glyphs || output_mcc || output_raw;
1266 
1267     if (!output_auto && !override_in_effect)
1268     {
1269         fprintf(stderr, "*** Error: auto mode was disabled with no override mode selected!\n");
1270         exit(1);
1271     }
1272 }
1273 
dump_cards(FILE * out_file)1274 static void dump_cards(FILE *out_file)
1275 {
1276     CARD_CACHE_ENTRY *current_entry = banner_card_cache.first_cache_card;
1277     while (current_entry != NULL)
1278     {
1279         print_card(out_file, current_entry->card, output_flip);
1280         current_entry = current_entry->next_entry;
1281     }
1282 
1283     current_entry = data_card_cache.first_cache_card;
1284     while (current_entry != NULL)
1285     {
1286         print_card(out_file, current_entry->card, false);
1287         current_entry = current_entry->next_entry;
1288     }
1289 
1290     current_entry = trailer_card_cache.first_cache_card;
1291     while (current_entry != NULL)
1292     {
1293         print_card(out_file, current_entry->card, output_flip);
1294         current_entry = current_entry->next_entry;
1295     }
1296 }
1297 
dump_mcc(FILE * out_file)1298 static void dump_mcc(FILE *out_file)
1299 {
1300     char ascii_string[CARD_COL_COUNT + 1];
1301     CARD_CACHE_ENTRY *current_entry = data_card_cache.first_cache_card;
1302     while (current_entry != NULL)
1303     {
1304         convert_mcc_to_ascii(current_entry->card->column, ascii_string);
1305         fprintf(out_file, "%s\n", ascii_string);
1306         current_entry = current_entry->next_entry;
1307     }
1308 }
1309 
dump_raw(FILE * out_file)1310 static void dump_raw(FILE *out_file)
1311 {
1312     CARD_CACHE_ENTRY *current_entry = data_card_cache.first_cache_card;
1313     while (current_entry != NULL)
1314     {
1315         if (output_debug)
1316         {
1317             fprintf(stderr, "\nCard:\n");
1318             for (uint col = 0; col < CARD_COL_COUNT; col++)
1319             {
1320                 fprintf(stderr, "  0x%03X\n", current_entry->card->column[col]);
1321             }
1322         }
1323         for (int current_nibble = 0; current_nibble < NIBBLES_PER_CARD; current_nibble += 2)
1324         {
1325             uint8 byte = 0;
1326 
1327             uint col = current_nibble / 3;
1328             uint nibble_offset = 2 - (current_nibble % 3);
1329 
1330             uint nibble = (current_entry->card->column[col] >> (nibble_offset * 4)) & 0x00F;
1331             byte |= nibble << 4;
1332 
1333             col = (current_nibble + 1) / 3;
1334             nibble_offset = 2 - ((current_nibble + 1) % 3);
1335 
1336             nibble = (current_entry->card->column[col] >> (nibble_offset * 4)) & 0x00F;
1337             byte |= nibble;
1338 
1339             if (fwrite(&byte, 1, 1, out_file) != 1)
1340             {
1341                 perror("Failed to write output file\n");
1342                 exit(5);
1343             }
1344         }
1345         current_entry = current_entry->next_entry;
1346     }
1347 }
1348 
main(int argc,char * argv[])1349 int main(int argc, char *argv[])
1350 {
1351     fprintf(stderr, "****\nPunch File Utility\n****\n");
1352 
1353     parse_options(argc, argv);
1354 
1355     init();
1356 
1357     parse_cards(stdin);
1358 
1359     if (output_auto)
1360     {
1361         if (check_for_valid_mcc_cards())
1362         {
1363             output_mcc = true;
1364             fprintf(stderr, ">> Auto-detected MCC Punch Codes <<\n");
1365         }
1366         else
1367         {
1368             output_raw = true;
1369             fprintf(stderr, ">> Auto-detection did not recognize format so output mode set to raw <<\n");
1370         }
1371     }
1372 
1373     if (output_cards)
1374     {
1375         fprintf(stderr, "\n*****\nWriting ASCII Art Card Images (banner cards%s flipped)\n*****\n", output_flip ? "" : " not");
1376         dump_cards(stdout);
1377     }
1378 
1379     if (output_glyphs)
1380     {
1381         fprintf(stderr, "\n*****\nWriting Banner Glyphs as ASCII\n*****\n");
1382         fprintf(stdout, "%s\n", glyph_buffer);
1383     }
1384 
1385     if (output_raw)
1386     {
1387         fprintf(stderr, "\n*****\nWriting raw output\n*****\n");
1388         dump_raw(stdout);
1389     }
1390 
1391     if (output_mcc)
1392     {
1393         fprintf(stderr, "\n*****\nWriting MCC output\n*****\n");
1394         dump_mcc(stdout);
1395     }
1396 
1397 }
1398