238 lines
8.1 KiB
C
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;
|
|
}
|