GE-115 Emulator
An Emulator of the General Electrics GE-115 computer
cap.c
Go to the documentation of this file.
1/*
2 * cap.c — parser for Raspberry-Pi-Pico punch-card reader .cap files.
3 *
4 * File format:
5 * - Free-text preamble lines, bare column counters, "FEED ON/OFF" etc.
6 * - Card delimiter: a line of the exact form "Card n. N" (N = 1, 2, 3, …)
7 * - Following the delimiter: whitespace/newline-separated 4-hex-digit tokens,
8 * one per card column (80 per card), each representing a 12-bit hole pattern.
9 * - Non-hex lines between card data are ignored.
10 *
11 * This parser is fully reentrant; no global state.
12 */
13
14#include "cap.h"
15#include "transcode.h"
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <ctype.h>
21
22#define CAP_MIN_SCATTER_PREFIX_CARDS 4
23#define CAP_MIN_ISOLATION_CARDS 8
24
25/* -------------------------------------------------------------------------
26 * Internal data structures
27 * ------------------------------------------------------------------------- */
28
29struct cap_card {
30 uint16_t *cols; /* dynamically allocated column data */
31 int ncols; /* number of columns stored */
32 int cap; /* allocated capacity */
33};
34
35struct cap_deck {
36 struct cap_card *cards; /* dynamically allocated card array */
37 int ncards; /* number of cards stored */
38 int cap; /* allocated capacity */
39};
40
41/* -------------------------------------------------------------------------
42 * Internal helpers
43 * ------------------------------------------------------------------------- */
44
45/* Grow the card array in the deck by one slot. Returns 0 on success. */
46static int deck_add_card(struct cap_deck *d)
47{
48 if (d->ncards >= d->cap) {
49 int newcap = d->cap ? d->cap * 2 : 16;
50 struct cap_card *tmp = realloc(d->cards,
51 (size_t)newcap * sizeof(struct cap_card));
52 if (!tmp)
53 return -1;
54 d->cards = tmp;
55 d->cap = newcap;
56 }
57 d->cards[d->ncards].cols = NULL;
58 d->cards[d->ncards].ncols = 0;
59 d->cards[d->ncards].cap = 0;
60 d->ncards++;
61 return 0;
62}
63
64/* Append one column value to the current (last) card. Returns 0 on success. */
65static int card_add_col(struct cap_card *c, uint16_t val)
66{
67 if (c->ncols >= c->cap) {
68 int newcap = c->cap ? c->cap * 2 : 80;
69 uint16_t *tmp = realloc(c->cols, (size_t)newcap * sizeof(uint16_t));
70 if (!tmp)
71 return -1;
72 c->cols = tmp;
73 c->cap = newcap;
74 }
75 c->cols[c->ncols++] = val & 0x1FFFu; /* keep 13 bits (12-bit hole + spare) */
76 return 0;
77}
78
79/*
80 * Return non-zero if `s` is a valid 4-hex-digit token (exactly 4 chars,
81 * all hexadecimal digits).
82 */
83static int is_hex4(const char *s)
84{
85 if (!s || strlen(s) != 4)
86 return 0;
87 return isxdigit((unsigned char)s[0]) &&
88 isxdigit((unsigned char)s[1]) &&
89 isxdigit((unsigned char)s[2]) &&
90 isxdigit((unsigned char)s[3]);
91}
92
93/*
94 * Return non-zero if line begins with "Card n. " (case-sensitive).
95 * If it does, *card_num receives the card number (>= 1).
96 */
97static int parse_card_header(const char *line, int *card_num)
98{
99 const char prefix[] = "Card n. ";
100 const size_t plen = sizeof(prefix) - 1;
101 if (strncmp(line, prefix, plen) != 0)
102 return 0;
103 const char *p = line + plen;
104 /* Require at least one digit immediately after the prefix */
105 if (!isdigit((unsigned char)*p))
106 return 0;
107 char *end;
108 long n = strtol(p, &end, 10);
109 if (n < 1)
110 return 0;
111 /* Allow trailing whitespace/newline only */
112 while (*end && (isspace((unsigned char)*end) || *end == '\r'))
113 end++;
114 if (*end != '\0')
115 return 0;
116 *card_num = (int)n;
117 return 1;
118}
119
120/* -------------------------------------------------------------------------
121 * Public API
122 * ------------------------------------------------------------------------- */
123
124struct cap_deck *cap_load(const char *path)
125{
126 if (!path)
127 return NULL;
128
129 FILE *fp = fopen(path, "r");
130 if (!fp)
131 return NULL;
132
133 struct cap_deck *d = calloc(1, sizeof(struct cap_deck));
134 if (!d) {
135 fclose(fp);
136 return NULL;
137 }
138
139 int in_card = 0; /* whether we are inside a card's data section */
140
141 char line[4096];
142 while (fgets(line, sizeof(line), fp)) {
143 /* Strip trailing newline/CR */
144 size_t len = strlen(line);
145 while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r'))
146 line[--len] = '\0';
147
148 /* Check for card header */
149 int card_num = 0;
150 if (parse_card_header(line, &card_num)) {
151 if (deck_add_card(d) != 0)
152 goto oom;
153 in_card = 1;
154 continue;
155 }
156
157 /* If we are not yet inside any card, skip non-card lines */
158 if (!in_card)
159 continue;
160
161 /*
162 * We are inside a card data section. Tokenize the line and
163 * collect 4-hex-digit tokens; ignore anything else (counters,
164 * "FEED ON/OFF", blank lines, etc.).
165 */
166 char *buf = line;
167 char *tok;
168 /* Use a copy because strtok modifies its argument */
169 char linecopy[4096];
170 strncpy(linecopy, line, sizeof(linecopy) - 1);
171 linecopy[sizeof(linecopy) - 1] = '\0';
172
173 tok = strtok(linecopy, " \t\r\n");
174 while (tok) {
175 if (is_hex4(tok)) {
176 uint16_t val = (uint16_t)strtoul(tok, NULL, 16);
177 struct cap_card *cur = &d->cards[d->ncards - 1];
178 if (card_add_col(cur, val) != 0)
179 goto oom;
180 }
181 tok = strtok(NULL, " \t\r\n");
182 }
183 (void)buf; /* suppress unused-variable warning */
184 }
185
186 fclose(fp);
187 return d;
188
189oom:
190 fclose(fp);
191 cap_free(d);
192 return NULL;
193}
194
195struct cap_deck *cap_create(void)
196{
197 return calloc(1, sizeof(struct cap_deck));
198}
199
200int cap_num_cards(const struct cap_deck *d)
201{
202 if (!d)
203 return 0;
204 return d->ncards;
205}
206
207int cap_card_ncols(const struct cap_deck *d, int i)
208{
209 if (!d || i < 0 || i >= d->ncards)
210 return 0;
211 return d->cards[i].ncols;
212}
213
214const uint16_t *cap_card_columns(const struct cap_deck *d, int i)
215{
216 if (!d || i < 0 || i >= d->ncards)
217 return NULL;
218 return d->cards[i].cols;
219}
220
221int cap_append_card(struct cap_deck *d, const uint16_t *cols, int ncols)
222{
223 struct cap_card *cur;
224
225 if (!d || !cols || ncols < 0)
226 return -1;
227 if (deck_add_card(d) != 0)
228 return -1;
229
230 cur = &d->cards[d->ncards - 1];
231 for (int i = 0; i < ncols; i++) {
232 if (card_add_col(cur, cols[i]) != 0)
233 return -1;
234 }
235 return 0;
236}
237
238int cap_save(const struct cap_deck *d, const char *path)
239{
240 FILE *fp;
241
242 if (!d || !path)
243 return -1;
244
245 fp = fopen(path, "w");
246 if (!fp)
247 return -1;
248
249 fprintf(fp, "Generated by gemu cap_save\n");
250 for (int i = 0; i < d->ncards; i++) {
251 const struct cap_card *c = &d->cards[i];
252 fprintf(fp, "Card n. %d\n", i + 1);
253 for (int j = 0; j < c->ncols; j++)
254 fprintf(fp, "%04X%c", c->cols[j] & 0x1FFFu, (j + 1 == c->ncols) ? '\n' : ' ');
255 if (c->ncols == 0)
256 fputc('\n', fp);
257 }
258
259 fclose(fp);
260 return 0;
261}
262
263void cap_free(struct cap_deck *d)
264{
265 if (!d)
266 return;
267 for (int i = 0; i < d->ncards; i++)
268 free(d->cards[i].cols);
269 free(d->cards);
270 free(d);
271}
272
273/* -------------------------------------------------------------------------
274 * Self-addressed scatter load (mirrors gdis --image; see cap.h).
275 * ------------------------------------------------------------------------- */
276
277static int scat_decode_card(const uint16_t *cols, int ncols, int mode,
278 uint8_t out[80])
279{
280 int n = ncols < 80 ? ncols : 80;
281
282 for (int i = 0; i < n; i++)
283 out[i] = transcode_column(cols[i], (enum transcode_mode)mode);
284
285 return n;
286}
287
288static char isolation_ident_char(uint16_t col)
289{
290 int rows[13];
291 int n = 0;
292
293 for (int r = 0; r <= 12; r++) {
294 if (col & (1u << r))
295 rows[n++] = r;
296 }
297 if (n == 0)
298 return 0;
299 if (n == 1 && rows[0] >= 0 && rows[0] <= 9)
300 return (char)('0' + rows[0]);
301 if (n == 2 && rows[0] == 1 && rows[1] == 12)
302 return 'A';
303 if (n == 2 && rows[0] == 2 && rows[1] == 12)
304 return 'B';
305 if (n == 2 && rows[0] == 3 && rows[1] == 12)
306 return 'C';
307 return 0;
308}
309
310static int isolation_ident_value(char a, char b, char c)
311{
312 int v[3];
313 char id[3] = { a, b, c };
314
315 for (int i = 0; i < 3; i++) {
316 if (id[i] >= '0' && id[i] <= '9')
317 v[i] = id[i] - '0';
318 else if (id[i] >= 'A' && id[i] <= 'C')
319 v[i] = 10 + (id[i] - 'A');
320 else
321 return -1;
322 }
323
324 return (v[0] << 8) | (v[1] << 4) | v[2];
325}
326
327/* Detect the deck's dominant 8-byte data-card prefix (cols 0-7). Returns the
328 * match count and copies it into out[8]; 0 if no eligible cards. */
329static int scat_detect_prefix(const struct cap_deck *d, int mode,
330 uint8_t out[8], int *eligible_cards)
331{
332 int nc = cap_num_cards(d);
333 struct { uint8_t p[8]; int cnt; } tab[1024];
334 int ntab = 0, best = -1;
335 int eligible = 0;
336
337 for (int i = 0; i < nc; i++) {
338 int ncols = cap_card_ncols(d, i);
339 const uint16_t *cols = cap_card_columns(d, i);
340 if (ncols < 11 || !cols)
341 continue;
342 eligible++;
343
344 uint8_t b[80];
345 scat_decode_card(cols, ncols, mode, b);
346
347 int j;
348 for (j = 0; j < ntab; j++) {
349 if (memcmp(tab[j].p, b, 8) == 0) {
350 tab[j].cnt++;
351 break;
352 }
353 }
354 if (j == ntab && ntab < 1024) {
355 memcpy(tab[ntab].p, b, 8);
356 tab[ntab].cnt = 1;
357 ntab++;
358 }
359 }
360
361 for (int j = 0; j < ntab; j++) {
362 if (best < 0 || tab[j].cnt > tab[best].cnt)
363 best = j;
364 }
365 if (best < 0)
366 return 0;
367
368 if (eligible_cards)
369 *eligible_cards = eligible;
370 memcpy(out, tab[best].p, 8);
371 return tab[best].cnt;
372}
373
374const char *cap_family_name(enum cap_deck_family family)
375{
376 switch (family) {
378 return "scatter";
380 return "isolation";
381 default:
382 return "unknown";
383 }
384}
385
386enum cap_deck_family cap_detect_family(const struct cap_deck *d, int mode,
387 struct cap_deck_info *info)
388{
389 struct cap_deck_info tmp = {0};
390
391 if (!d)
392 return CAP_FAMILY_UNKNOWN;
393
397
398 int have_prev_iso = 0;
399 int prev_iso = -1;
400 int iso_seq = 0;
401
402 for (int i = 0; i < cap_num_cards(d); i++) {
403 int ncols = cap_card_ncols(d, i);
404 const uint16_t *cols = cap_card_columns(d, i);
405 int idv;
406 char id0, id1, id2;
407 if (ncols < 80 || !cols)
408 continue;
409 tmp.full_80col_cards++;
410 id0 = isolation_ident_char(cols[76]);
411 id1 = isolation_ident_char(cols[77]);
412 id2 = isolation_ident_char(cols[78]);
413 idv = isolation_ident_value(id0, id1, id2);
414 if (idv >= 0) {
415 tmp.isolation_cards++;
416 if (have_prev_iso && idv == prev_iso + 1)
417 iso_seq++;
418 prev_iso = idv;
419 have_prev_iso = 1;
420 }
421 }
422
424 tmp.scatter_prefix[0] == 0x00 &&
425 tmp.scatter_prefix[1] == 0x04) {
427 } else if (tmp.full_80col_cards > 0 &&
429 iso_seq >= CAP_MIN_ISOLATION_CARDS - 1 &&
430 iso_seq * 4 >= tmp.isolation_cards * 3 &&
431 tmp.isolation_cards * 4 >= tmp.full_80col_cards * 3) {
435 }
436
437 if (info)
438 *info = tmp;
439 return tmp.family;
440}
441
442int cap_load_scattered(const char *path, int mode, unsigned char *image,
443 unsigned *lo, unsigned *hi)
444{
445 struct cap_deck *d = cap_load(path);
446 if (!d)
447 return -1;
448
449 {
450 struct cap_deck_info info;
451 if (cap_detect_family(d, mode, &info) != CAP_FAMILY_SCATTER) {
452 cap_free(d);
453 return -1;
454 }
455 }
456
457 uint8_t want[8];
458 int have_prefix = (scat_detect_prefix(d, mode, want, NULL) >= CAP_MIN_SCATTER_PREFIX_CARDS);
459 int loose = !have_prefix; /* no dominant prefix: accept any fitting record */
460
461 int nc = cap_num_cards(d);
462 int loaded = 0;
463 long min_a = -1, max_a = -1;
464
465 for (int i = 0; i < nc; i++) {
466 int ncols = cap_card_ncols(d, i);
467 const uint16_t *cols = cap_card_columns(d, i);
468 if (ncols < 11 || !cols)
469 continue;
470
471 uint8_t b[80];
472 int n = scat_decode_card(cols, ncols, mode, b);
473
474 int match = (have_prefix && n >= 8 && memcmp(b, want, 8) == 0);
475 int ll = b[8];
476 long addr = ((long)b[9] << 8) | b[10];
477 int paylen = ll + 1;
478 int fits = (11 + ll < n) && (addr + ll <= 0xFFFF);
479
480 if (!(loose ? fits : (match && fits)))
481 continue;
482
483 for (int k = 0; k < paylen; k++)
484 image[addr + k] = b[11 + k];
485
486 if (min_a < 0 || addr < min_a)
487 min_a = addr;
488 if (addr + ll > max_a)
489 max_a = addr + ll;
490 loaded++;
491 }
492
493 cap_free(d);
494
495 if (loaded == 0 || min_a < 0)
496 return -1;
497
498 if (lo)
499 *lo = (unsigned)min_a;
500 if (hi)
501 *hi = (unsigned)max_a;
502 return loaded;
503}
504
505int cap_load_isolation_stream(const char *path, unsigned char *image,
506 unsigned org, unsigned *lo, unsigned *hi)
507{
508 struct cap_deck *d = cap_load(path);
509 int loaded = 0;
510 long min_a = -1;
511 long max_a = -1;
512 long off = org;
513
514 if (!d)
515 return -1;
516
517 {
518 struct cap_deck_info info;
520 cap_free(d);
521 return -1;
522 }
523 }
524
525 for (int i = 0; i < cap_num_cards(d); i++) {
526 int ncols = cap_card_ncols(d, i);
527 const uint16_t *cols = cap_card_columns(d, i);
528 if (ncols < 80 || !cols)
529 continue;
530 if (!isolation_ident_char(cols[76]) ||
531 !isolation_ident_char(cols[77]) ||
532 !isolation_ident_char(cols[78]))
533 continue;
534
535 if (off + 75 > 0xFFFF) {
536 cap_free(d);
537 return -1;
538 }
539
540 for (int k = 0; k < 76; k++)
541 image[off + k] = transcode_column(cols[k], TC_COLBIN);
542
543 if (min_a < 0)
544 min_a = off;
545 max_a = off + 75;
546 off += 76;
547 loaded++;
548 }
549
550 cap_free(d);
551
552 if (loaded == 0 || min_a < 0)
553 return -1;
554
555 if (lo)
556 *lo = (unsigned)min_a;
557 if (hi)
558 *hi = (unsigned)max_a;
559 return loaded;
560}
const uint16_t * cap_card_columns(const struct cap_deck *d, int i)
Definition cap.c:214
static int is_hex4(const char *s)
Definition cap.c:83
static int parse_card_header(const char *line, int *card_num)
Definition cap.c:97
int cap_append_card(struct cap_deck *d, const uint16_t *cols, int ncols)
Definition cap.c:221
const char * cap_family_name(enum cap_deck_family family)
Definition cap.c:374
#define CAP_MIN_SCATTER_PREFIX_CARDS
Definition cap.c:22
static int card_add_col(struct cap_card *c, uint16_t val)
Definition cap.c:65
static int isolation_ident_value(char a, char b, char c)
Definition cap.c:310
static int scat_decode_card(const uint16_t *cols, int ncols, int mode, uint8_t out[80])
Definition cap.c:277
int cap_card_ncols(const struct cap_deck *d, int i)
Definition cap.c:207
struct cap_deck * cap_create(void)
Definition cap.c:195
static char isolation_ident_char(uint16_t col)
Definition cap.c:288
int cap_num_cards(const struct cap_deck *d)
Definition cap.c:200
void cap_free(struct cap_deck *d)
Definition cap.c:263
struct cap_deck * cap_load(const char *path)
Definition cap.c:124
int cap_load_scattered(const char *path, int mode, unsigned char *image, unsigned *lo, unsigned *hi)
Definition cap.c:442
#define CAP_MIN_ISOLATION_CARDS
Definition cap.c:23
int cap_save(const struct cap_deck *d, const char *path)
Definition cap.c:238
int cap_load_isolation_stream(const char *path, unsigned char *image, unsigned org, unsigned *lo, unsigned *hi)
Definition cap.c:505
static int scat_detect_prefix(const struct cap_deck *d, int mode, uint8_t out[8], int *eligible_cards)
Definition cap.c:329
static int deck_add_card(struct cap_deck *d)
Definition cap.c:46
enum cap_deck_family cap_detect_family(const struct cap_deck *d, int mode, struct cap_deck_info *info)
Definition cap.c:386
cap_deck_family
Definition cap.h:9
@ CAP_FAMILY_ISOLATION
Definition cap.h:12
@ CAP_FAMILY_UNKNOWN
Definition cap.h:10
@ CAP_FAMILY_SCATTER
Definition cap.h:11
Definition cap.c:29
uint16_t * cols
Definition cap.c:30
int ncols
Definition cap.c:31
int cap
Definition cap.c:32
int isolation_cards
Definition cap.h:20
uint8_t scatter_prefix[8]
Definition cap.h:21
int full_80col_cards
Definition cap.h:19
int eligible_scatter_cards
Definition cap.h:17
enum cap_deck_family family
Definition cap.h:16
int scatter_prefix_count
Definition cap.h:18
Definition cap.c:35
int ncards
Definition cap.c:37
struct cap_card * cards
Definition cap.c:36
int cap
Definition cap.c:38
uint8_t transcode_column(uint16_t column, enum transcode_mode mode)
Definition transcode.c:564
transcode_mode
Definition transcode.h:18
@ TC_COLBIN
Definition transcode.h:22