// 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=", 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 " ", 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 #include #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 \" \"", " 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 " "; 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; }