joeylib2/tests/gate/stScreenDump.c
2026-10-19 15:46:47 -05:00

257 lines
8.5 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// stScreenDump.c - read an Atari ST low-resolution screen row out of a
// Hatari debugger memory dump, for scripts/verify-atarist-input.sh.
//
// stScreenDump keys LOG ADDRESS TARGET
// stScreenDump cells LOG ADDRESS
//
// LOG holds the output of "hatari-debug m $ADDRESS 160": lines of
// "00XXXXXX: bb bb ..." (up to 16 bytes each; the last dump of an address
// wins). The 160 bytes from ADDRESS are one line of the screen: 16-pixel
// groups of four big-endian plane words, a pixel's color bit n from plane
// n.
//
// keys: KEYS's verification row (examples/keys): pixels 0-3 the sentinel
// A 5 A 5, pixels 4-5 the count of typed characters, and from pixel 8 two
// pixels (high nibble, low nibble) a character. Prints "VERIFY-ST-INPUT
// count=N chars=" and the characters quoted, and exits 0 when they are
// TARGET, 1 otherwise (and when a dump line is missing or the sentinel is
// wrong, saying so).
//
// cells: the row through KEYS's cells: the first pixel of groups 5, 7, ...
// 17, printed as one digit each (color 2 "1", color 1 "0", else "?").
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gateArg.h"
#include "toolFile.h"
#define TOOL_NAME "stScreenDump"
#define LINE_MAX_CHARS 4096u
#define GATE_NAME "verify-atarist-input"
#define ROW_BYTES 160u
#define LINE_BYTES 16u
#define GROUP_BYTES 8u
#define GROUP_PIXELS 16u
#define PLANES 4u
#define ROW_PIXELS (ROW_BYTES / GROUP_BYTES * GROUP_PIXELS)
#define ADDRESS_DIGITS 8u
#define HEX 16
#define NIBBLE_BITS 4u
#define TOP_BIT 15u
#define COUNT_AT 4u
#define CHARS_AT 8u
#define SENTINEL_PIXELS 4u
#define CELL_GROUP_FIRST 5u
#define CELL_GROUP_LAST 17u
#define CELL_GROUP_STEP 2u
#define CELL_ON 2u
#define CELL_OFF 1u
#define PRINTABLE_FIRST 0x20u
#define PRINTABLE_LAST 0x7Eu
// The row's bytes, and which of them a dump line gave.
typedef struct RowT {
uint8_t bytes[ROW_BYTES];
bool given[ROW_BYTES / LINE_BYTES];
} RowT;
static int cells(const RowT *row);
static void dumpRead(const char *path, uint32_t address, RowT *row);
static int keys(const RowT *row, const char *target);
static void pixelsOf(const RowT *row, uint8_t *pixels);
static void quotedPrint(const uint8_t *bytes, size_t len);
static int usage(void);
// The digits of KEYS's cells.
static int cells(const RowT *row) {
uint32_t group;
uint32_t plane;
uint32_t color;
uint32_t word;
size_t at;
for (group = CELL_GROUP_FIRST; group <= CELL_GROUP_LAST; group += CELL_GROUP_STEP) {
color = 0;
for (plane = 0; plane < PLANES; plane++) {
at = group * GROUP_BYTES + plane * 2u;
word = ((uint32_t)row->bytes[at] << 8) | row->bytes[at + 1u];
color |= ((word >> TOP_BIT) & 1u) << plane;
}
putchar(color == CELL_ON ? '1' : (color == CELL_OFF ? '0' : '?'));
}
putchar('\n');
return 0;
}
// The 160 bytes from address out of the log's dump lines (the last of
// each line wins; bytes no line gave are 0).
static void dumpRead(const char *path, uint32_t address, RowT *row) {
FILE *f;
char line[LINE_MAX_CHARS];
char *at;
uint32_t lineAddress;
uint32_t slot;
uint32_t n;
uint32_t i;
uint8_t bytes[LINE_BYTES];
memset(row, 0, sizeof(*row));
f = fopen(path, "rb");
if (f == NULL) {
toolFail("cannot read %s", path);
}
while (fgets(line, sizeof(line), f) != NULL) {
for (i = 0; i < ADDRESS_DIGITS; i++) {
if (!(line[i] >= '0' && line[i] <= '9') && !(line[i] >= 'A' && line[i] <= 'F')) {
break;
}
}
if (i != ADDRESS_DIGITS || line[0] != '0' || line[1] != '0' || line[ADDRESS_DIGITS] != ':' || line[ADDRESS_DIGITS + 1u] != ' ') {
continue;
}
lineAddress = (uint32_t)strtoul(line, NULL, HEX);
at = line + ADDRESS_DIGITS + 2u;
// Bytes as "bb " each: lowercase hex, each followed by a space.
for (n = 0; n < LINE_BYTES; n++) {
if (!((at[0] >= '0' && at[0] <= '9') || (at[0] >= 'a' && at[0] <= 'f')) || !((at[1] >= '0' && at[1] <= '9') || (at[1] >= 'a' && at[1] <= 'f')) || at[2] != ' ') {
break;
}
bytes[n] = (uint8_t)strtoul((char[]){ at[0], at[1], 0 }, NULL, HEX);
at += 3;
}
if (n == 0 || lineAddress < address || lineAddress >= address + ROW_BYTES || (lineAddress - address) % LINE_BYTES != 0) {
continue;
}
slot = (lineAddress - address) / LINE_BYTES;
memset(row->bytes + slot * LINE_BYTES, 0, LINE_BYTES);
memcpy(row->bytes + slot * LINE_BYTES, bytes, n);
row->given[slot] = true;
}
fclose(f);
}
// KEYS's verification row checked against target.
static int keys(const RowT *row, const char *target) {
uint8_t pixels[ROW_PIXELS];
uint8_t chars[ROW_PIXELS];
uint32_t slot;
uint32_t count;
uint32_t i;
for (slot = 0; slot < ROW_BYTES / LINE_BYTES; slot++) {
if (!row->given[slot]) {
printf("%s: FAIL - missing dump line at +%u\n", GATE_NAME, slot * LINE_BYTES);
return 1;
}
}
pixelsOf(row, pixels);
if (pixels[0] != 0xAu || pixels[1] != 0x5u || pixels[2] != 0xAu || pixels[3] != 0x5u) {
printf("%s: FAIL - sentinel wrong: [", GATE_NAME);
for (i = 0; i < SENTINEL_PIXELS; i++) {
printf("%s%u", i > 0 ? ", " : "", pixels[i]);
}
printf("]\n");
return 1;
}
count = ((uint32_t)pixels[COUNT_AT] << NIBBLE_BITS) | pixels[COUNT_AT + 1u];
if (CHARS_AT + 2u * count > ROW_PIXELS) {
printf("%s: FAIL - a count of %u characters is past the row's end\n", GATE_NAME, count);
return 1;
}
for (i = 0; i < count; i++) {
chars[i] = (uint8_t)((pixels[CHARS_AT + 2u * i] << NIBBLE_BITS) | pixels[CHARS_AT + 2u * i + 1u]);
}
printf("VERIFY-ST-INPUT count=%u chars=", count);
quotedPrint(chars, count);
putchar('\n');
if (strlen(target) != count || memcmp(chars, target, count) != 0) {
printf("%s: FAIL - expected \"%s\"\n", GATE_NAME, target);
return 1;
}
return 0;
}
// The row's pixels: each group's four plane words, bit 15 first.
static void pixelsOf(const RowT *row, uint8_t *pixels) {
uint32_t group;
uint32_t bit;
uint32_t plane;
uint32_t word;
uint8_t value;
for (group = 0; group < ROW_BYTES / GROUP_BYTES; group++) {
for (bit = 0; bit < GROUP_PIXELS; bit++) {
value = 0;
for (plane = 0; plane < PLANES; plane++) {
word = ((uint32_t)row->bytes[group * GROUP_BYTES + plane * 2u] << 8) | row->bytes[group * GROUP_BYTES + plane * 2u + 1u];
value |= (uint8_t)(((word >> (TOP_BIT - bit)) & 1u) << plane);
}
pixels[group * GROUP_PIXELS + bit] = value;
}
}
}
// Bytes in double quotes: printable ASCII as it is (a backslash and a quote
// escaped), tab, newline, and return as \t \n \r, anything else as \xNN.
static void quotedPrint(const uint8_t *bytes, size_t len) {
size_t i;
putchar('"');
for (i = 0; i < len; i++) {
if (bytes[i] == '\\' || bytes[i] == '"') {
printf("\\%c", bytes[i]);
} else if (bytes[i] == '\t') {
printf("\\t");
} else if (bytes[i] == '\n') {
printf("\\n");
} else if (bytes[i] == '\r') {
printf("\\r");
} else if (bytes[i] < PRINTABLE_FIRST || bytes[i] > PRINTABLE_LAST) {
printf("\\x%02x", bytes[i]);
} else {
putchar(bytes[i]);
}
}
putchar('"');
}
static int usage(void) {
fprintf(stderr, "usage: %s keys LOG ADDRESS TARGET\n %s cells LOG ADDRESS\n", TOOL_NAME, TOOL_NAME);
return GATE_USAGE_EXIT;
}
int main(int argc, char **argv) {
RowT row;
long address;
toolFileName(TOOL_NAME);
if (argc < 4) {
return usage();
}
address = gateArgLong("address", argv[3]);
if (address < 0) {
gateArgBad("address", argv[3]);
}
if (strcmp(argv[1], "keys") == 0 && argc == 5) {
dumpRead(argv[2], (uint32_t)address, &row);
return keys(&row, argv[4]);
}
if (strcmp(argv[1], "cells") == 0 && argc == 4) {
dumpRead(argv[2], (uint32_t)address, &row);
return cells(&row);
}
return usage();
}