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

238 lines
8.1 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// dosRow.c - KEYS's verification rows off a DOSBox raw screenshot
// (scripts/verify-dos-input.sh and verify-dos-mouse.sh), or off a screen that
// shows the stage at 1:1 (scripts/verify-psp-input.sh).
//
// dosRow input PNG TARGET
// dosRow mouse PNG
// dosRow mouse-at PNG X,Y
//
// DOSBox's raw capture is the 320x200 stage, or a whole multiple of it.
// KEYS (examples/keys) stamps its rows as nibble pixels in its palette 0,
// which the port's 6-bit DAC shows as each 4-bit gun widened to 6 bits and
// then to 8 (bit replication); a pixel is the nearest entry, since
// emulators differ in how they scale the DAC (replication or rounding) but
// the entries sit some 17 apart a gun. A screen that widens each gun x17
// (the PSP's) is at most 3 off them, so the nearest is exact there too.
//
// input: row 198's first 40 bytes (two pixels a byte, high nibble first),
// printed as "VERIFY-DOS-INPUT row=<hex>", must start with the sentinel A5
// A5, TARGET's length, a zero, and TARGET; a pixel no entry is near, or a
// screenshot of another size, fails. Prints the FAIL lines
// verify-dos-input.sh shows; exits 0 or 1.
//
// mouse: row 199's four words (four nibbles each) must start with the
// sentinel 5A5A; prints "<flags hex> <x> <y>", or nothing and exits 1 when
// the row is not there (or the screenshot is another size).
//
// mouse-at: the same, from the stage at 1:1 with its corner at (X, Y) on a
// screenshot of any size it fits in.
//
// A bad command line exits 2.
#include <stdio.h>
#include <string.h>
#include "gateArg.h"
#include "gateImage.h"
#include "toolBuf.h"
#include "toolFile.h"
#define TOOL_NAME "dosRow"
#define FAIL_EXIT 1
#define STAGE_W 320u
#define STAGE_H 200u
#define TYPED_Y 198u
#define TYPED_PIXELS 80u
#define TYPED_BYTES (TYPED_PIXELS / 2u)
#define MOUSE_Y 199u
#define MOUSE_WORDS 4u
#define WORD_NIBBLES 4u
#define MOUSE_SENTINEL 0x5A5Au
#define MOUSE_FLAGS 1u
#define MOUSE_X 2u
#define MOUSE_Y_WORD 3u
#define NIBBLE_BITS 4u
#define NIBBLE_MASK 0xFu
#define DAC_BITS 6u
#define DAC_MASK 0x3Fu
#define DAC_LOW_BITS 2u
#define MAX_DIST (3u * 8u * 8u)
// The screenshot, the stage's scale and corner on it, and KEYS's palette as
// the DAC shows it.
typedef struct StageT {
GateImageT img;
uint32_t sx;
uint32_t sy;
uint32_t ox;
uint32_t oy;
uint8_t palette[GATE_KEYS_COLORS][GATE_RGB_BYTES];
} StageT;
static uint8_t dac8(uint32_t c4);
static int mousePrint(StageT *stage);
static uint32_t pixel(const StageT *stage, uint32_t x, uint32_t y, uint32_t *distance, const uint8_t **rgb);
static int readInput(char **args);
static int readMouse(char **args);
static int readMouseAt(char **args);
static bool stageLoad(const char *path, const char *at, StageT *stage);
static const GateModeT kModes[] = {
{ "input", 2, readInput },
{ "mouse", 1, readMouse },
{ "mouse-at", 2, readMouseAt }
};
static const char *const kUsage[] = {
"usage:",
" dosRow input PNG TARGET KEYS's row 198 must hold TARGET",
" dosRow mouse PNG KEYS's row 199: prints \"<flags hex> <x> <y>\"",
" dosRow mouse-at PNG X,Y the same, the stage at 1:1 with its corner at (X, Y)"
};
// A 4-bit gun through the 6-bit DAC, widened to 8 bits.
static uint8_t dac8(uint32_t c4) {
uint32_t v6;
v6 = ((c4 << DAC_LOW_BITS) | (c4 >> DAC_LOW_BITS)) & DAC_MASK;
return (uint8_t)((v6 << DAC_LOW_BITS) | (v6 >> (DAC_BITS - DAC_LOW_BITS)));
}
// KEYS's mouse row off the stage, printed as "<flags hex> <x> <y>"; 1 when
// the row is not there.
static int mousePrint(StageT *stage) {
const uint8_t *rgb;
uint32_t words[MOUSE_WORDS];
uint32_t distance;
uint32_t w;
uint32_t n;
for (w = 0; w < MOUSE_WORDS; w++) {
words[w] = 0u;
for (n = 0; n < WORD_NIBBLES; n++) {
words[w] = (words[w] << NIBBLE_BITS) | pixel(stage, w * WORD_NIBBLES + n, MOUSE_Y, &distance, &rgb);
}
}
gateImageFree(&stage->img);
if (words[0] != MOUSE_SENTINEL) {
return FAIL_EXIT;
}
printf("%04X %u %u\n", (unsigned)words[MOUSE_FLAGS], (unsigned)words[MOUSE_X], (unsigned)words[MOUSE_Y_WORD]);
return 0;
}
// The palette entry nearest stage pixel (x, y), and how near.
static uint32_t pixel(const StageT *stage, uint32_t x, uint32_t y, uint32_t *distance, const uint8_t **rgb) {
*rgb = gateImagePixel(&stage->img, stage->ox + x * stage->sx, stage->oy + y * stage->sy);
return gateNearest(*rgb, (const uint8_t (*)[GATE_RGB_BYTES])stage->palette, GATE_KEYS_COLORS, distance);
}
static int readInput(char **args) {
StageT stage;
ToolBufT expect;
uint8_t row[TYPED_BYTES];
const uint8_t *rgb;
uint32_t distance;
uint32_t nibble;
uint32_t p;
size_t gotLen;
if (!stageLoad(args[0], NULL, &stage)) {
printf("verify-dos-input: FAIL - unexpected screenshot size %ux%u\n", (unsigned)stage.img.width, (unsigned)stage.img.height);
return FAIL_EXIT;
}
memset(row, 0, sizeof(row));
for (p = 0; p < TYPED_PIXELS; p++) {
nibble = pixel(&stage, p, TYPED_Y, &distance, &rgb);
if (distance > MAX_DIST) {
printf("verify-dos-input: FAIL - pixel %u color (%u, %u, %u) too far from any palette entry\n", (unsigned)p, (unsigned)rgb[0], (unsigned)rgb[1], (unsigned)rgb[2]);
return FAIL_EXIT;
}
row[p / 2u] |= (uint8_t)((p % 2u == 0u) ? nibble << NIBBLE_BITS : nibble);
}
memset(&expect, 0, sizeof(expect));
gateKeysExpect(args[1], &expect);
gotLen = (expect.len < TYPED_BYTES) ? expect.len : TYPED_BYTES;
printf("VERIFY-DOS-INPUT row=");
gateHexPrint(row, TYPED_BYTES);
printf("\n");
if (gotLen != expect.len || memcmp(row, expect.data, gotLen) != 0) {
printf("verify-dos-input: FAIL\n expected: ");
gateHexPrint(expect.data, expect.len);
printf("\n got: ");
gateHexPrint(row, gotLen);
printf("\n");
return FAIL_EXIT;
}
toolBufFree(&expect);
gateImageFree(&stage.img);
return 0;
}
static int readMouse(char **args) {
StageT stage;
return stageLoad(args[0], NULL, &stage) ? mousePrint(&stage) : FAIL_EXIT;
}
static int readMouseAt(char **args) {
StageT stage;
return stageLoad(args[0], args[1], &stage) ? mousePrint(&stage) : FAIL_EXIT;
}
// The screenshot, the palette, and where the stage is: the whole screenshot,
// at a whole multiple of its size (at NULL), or at 1:1 with its corner at
// at's "X,Y". False when the stage is not there.
static bool stageLoad(const char *path, const char *at, StageT *stage) {
uint16_t words[GATE_KEYS_COLORS];
uint32_t i;
long x;
long y;
gateImageLoad(path, &stage->img);
gateKeysPalette(words);
for (i = 0; i < GATE_KEYS_COLORS; i++) {
stage->palette[i][0] = dac8((words[i] >> (2u * NIBBLE_BITS)) & NIBBLE_MASK);
stage->palette[i][1] = dac8((words[i] >> NIBBLE_BITS) & NIBBLE_MASK);
stage->palette[i][2] = dac8(words[i] & NIBBLE_MASK);
}
if (at != NULL) {
gateArgPair("corner", at, ',', &x, &y);
if (x < 0 || y < 0 || (unsigned long)x + STAGE_W > stage->img.width || (unsigned long)y + STAGE_H > stage->img.height) {
return false;
}
stage->sx = 1u;
stage->sy = 1u;
stage->ox = (uint32_t)x;
stage->oy = (uint32_t)y;
return true;
}
stage->sx = stage->img.width / STAGE_W;
stage->sy = stage->img.height / STAGE_H;
stage->ox = 0u;
stage->oy = 0u;
return stage->sx >= 1u && stage->sy >= 1u && stage->img.width % STAGE_W == 0u && stage->img.height % STAGE_H == 0u;
}
int main(int argc, char **argv) {
int exitCode;
toolFileName(TOOL_NAME);
if (!gateModeRun(kModes, GATE_COUNT_OF(kModes), argc, argv, &exitCode)) {
return gateUsage(kUsage, GATE_COUNT_OF(kUsage));
}
return exitCode;
}