257 lines
8.5 KiB
C
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 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 <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 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();
|
|
}
|