// 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 colour 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 (colour 2 "1", colour 1 "0", else "?"). #include #include #include #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 colour; uint32_t word; size_t at; for (group = CELL_GROUP_FIRST; group <= CELL_GROUP_LAST; group += CELL_GROUP_STEP) { colour = 0; for (plane = 0; plane < PLANES; plane++) { at = group * GROUP_BYTES + plane * 2u; word = ((uint32_t)row->bytes[at] << 8) | row->bytes[at + 1u]; colour |= ((word >> TOP_BIT) & 1u) << plane; } putchar(colour == CELL_ON ? '1' : (colour == 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(); }