Files
AnachronautandClaude Opus 5 de1857f5f7 Record every pad, not the one that happened to be first
The first recording ever made with this came back 1,766 frames of nothing.
It recorded pad NOUGHT and the controller was somewhere else - which pad
one lands on is an accident of the host, the same accident that made Lunar
Porter read all four in the first place - and a flight flown for the
purpose was lost to it.

So every pad is or-ed into the byte. A demo is a record of what somebody
DID, and on a machine one person is playing the number it arrived on is
not part of that. It plays back on pad nought, where --pad puts the first
file given, and any program that reads more than one pad reads them or-ed
anyway for exactly the same reason.

--record-pad takes one file now rather than filling pads in turn, because
there is nothing left for the second one to mean.

The check for it plays a recording on pad ONE with nought holding nothing
and requires the bytes back. That is the case that was missing: the round
trip was tested and passed, on pad nought, which is the only pad it could
not have gone wrong on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 14:27:58 -04:00

194 lines
9.1 KiB
C

// utility.c
// Utilities for the SplitBit CPU Emulator
// Written by Anachronaut
// 10/15/2024
#include "utility.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "../Assembler/assembly.h"
void printHelp(const char *programName) {
printf("Usage: %s [OPTIONS] [boot image]\n", programName);
printf("\n");
printf("Named an image, it is placed into memory and started, which is what a\n");
printf("debugger does and how the test suite runs. Given only a disk, the machine\n");
printf("starts the way hardware would: the built in ROM is shadowed into Program\n");
printf("Memory, and it reads the disk for everything else.\n");
printf("\n");
printf("Options:\n");
printf(" -d, --debug Enable debug mode.\n");
printf(" -c, --cycles N Stop after N cycles instead of running until the program halts.\n");
printf(" -f, --fast Run as fast as possible, ignoring the emulated cycle rate.\n");
printf(" -D, --disk FILE Attach a disk image, making one if it is not there.\n");
printf(" -L, --disk-cycles N How many cycles a block read or write takes. Zero, the\n");
printf(" default, finishes before the next instruction starts.\n");
printf(" -W, --write-protect Attach the disk read only. A disk the host will not let\n");
printf(" you write is read only whether you ask for this or not.\n");
printf(" -S, --screen FILE Save a picture of the screen, as a PPM, when the machine\n");
printf(" stops. Works with or without a window, which is how the\n");
printf(" tests look at a screen on a host that has no display.\n");
printf(" -K, --keyboard FILE Feed the console from this file as though it were a\n");
printf(" keyboard rather than a terminal. Which means the console does\n");
printf(" its own line editing, the way it must when a window is open\n");
printf(" and there is no terminal behind it to do it.\n");
printf(" -P, --pad FILE Hold a controller from a file, one byte a frame. Given\n");
printf(" again for the next pad. A byte is the buttons held:\n");
printf(" 1 right, 2 left, 4 down, 8 up, 16 A, 32 B, 64 start,\n");
printf(" 128 select.\n");
printf(" -Y, --record-pad FILE\n");
printf(" Write what the controllers held, one byte a frame, in the\n");
printf(" format --pad reads. Every pad at once, because which one a\n");
printf(" controller lands on is the host's business. Play a thing\n");
printf(" once and keep what happened.\n");
printf(" -N, --sound FILE Save every sample the machine made, as raw signed 16 bit\n");
printf(" at 48kHz. What --screen is for a picture: the only way to\n");
printf(" check a sound on a machine with no speaker.\n");
printf(" -h, --help Display this help message.\n");
}
uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
static struct option long_options[] = {
{"debug", no_argument, 0, 'd'},
{"cycles", required_argument, 0, 'c'},
{"fast", no_argument, 0, 'f'},
{"disk", required_argument, 0, 'D'},
{"ram-disk", required_argument, 0, 'R'},
{"write-protect", no_argument, 0, 'W'},
{"disk-cycles", required_argument, 0, 'L'},
{"screen", required_argument, 0, 'S'},
{"keyboard", required_argument, 0, 'K'},
{"sound", required_argument, 0, 'N'},
{"pad", required_argument, 0, 'P'},
{"record-pad", required_argument, 0, 'Y'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0 }
};
int opt;
int option_index = 0;
// ---- Everything off, in one line rather than nine ----
//
// This was a list of assignments, one per field, and a list beside a struct drifts from
// the struct: adding `disks` and `diskCount` left them holding whatever was on the stack,
// so a machine given one disk was told it already had four drives. The same struct
// growing a field once before left Voyager linking against an object that disagreed
// about its size.
//
// Every default here is nought or nothing, and a default that is not can be written
// below this line where it will be read as the exception it is.
*options = (EmulatorOptions){0};
// Parse options
while ((opt = getopt_long(argc, argv, "dc:fhD:WL:S:K:N:R:P:Y:", long_options, &option_index)) != -1) {
switch (opt) {
case 'd':
options->debug = 1;
break;
case 'c': {
// The count has to be a plain positive number. Anything else is
// almost certainly a mistyped command line rather than a request
// to run zero cycles.
char *end;
long value = strtol(optarg, &end, 10);
if (*end != '\0' || value <= 0) {
fprintf(stderr, "Error: --cycles needs a positive number, not \"%s\".\n", optarg);
return OPTIONS_ERROR;
}
options->cycles = (unsigned long)value;
}
break;
case 'f':
options->fast = 1;
break;
case 'D':
// Each one is the next drive. The first is also left in `disk`, because a
// machine with one disk is what almost every caller means and reading it
// that way keeps them all unchanged.
if (options->diskCount >= DISK_DRIVE_COUNT) {
fprintf(stderr, "Error: This machine has %d drives.\n",
DISK_DRIVE_COUNT);
return 1;
}
options->disks[options->diskCount++] = optarg;
if (options->disk == NULL) {
options->disk = optarg;
}
break;
case 'R': {
char *end;
const unsigned long blocks = strtoul(optarg, &end, 10);
if (*optarg == '\0' || *end != '\0' || blocks == 0) {
fprintf(stderr, "Error: --ram-disk wants a number of blocks.\n");
return 1;
}
options->ramDisk = blocks;
}
break;
case 'W':
options->writeProtect = 1;
break;
case 'L':
options->diskCycles = strtoul(optarg, NULL, 0);
break;
case 'S':
options->screen = optarg;
break;
case 'K':
options->keyboard = optarg;
break;
case 'N':
options->sound = optarg;
break;
case 'Y':
options->padRecord = optarg;
break;
case 'P':
// Fills the pads in turn, the same way --disk fills the drives, so the first
// one named is pad nought and the machine has as many as were asked for.
if (options->padCount < PAD_DRIVE_COUNT) {
options->pads[options->padCount++] = optarg;
}
break;
case 'h':
printHelp(argv[0]);
return OPTIONS_HELP;
default:
printHelp(argv[0]);
return OPTIONS_ERROR;
}
}
return OPTIONS_OK;
}
// Writes a byte out as eight binary digits, most significant first. printf's %b is
// a recent addition to C and not available everywhere, so this does it by hand.
// The buffer must have room for nine characters.
static void formatBinary(uint8_t value, char *out) {
for (int i = 0; i < 8; i++) {
out[i] = (value & (0x80 >> i)) ? '1' : '0';
}
out[8] = '\0';
}
void printRegisters(CPURegisters *cpu, uint8_t *Program, uint8_t *Data) {
char status[9];
formatBinary(cpu->Status, status);
printf("***** CPU Registers *****\n");
printf("A: 0x%02X\tB: 0x%02X\tQ: 0x%02X\tStatus: 0b%s\n", cpu->A, cpu->B, cpu->Q, status);
printf("Program Counter: 0x%04X Current Instruction: 0x%02X (%s)\n", cpu->ProgramCounter, Program[cpu->ProgramCounter],getMnemonic(Program[cpu->ProgramCounter]));
for (int i = 0; i < DATA_POINTERS; i++) {
printf(" Data Pointer %d: 0x%04X Current Data Value: 0x%02X%s\n",
i, cpu->DataPointer[i], Data[cpu->DataPointer[i]],
i >= PRESERVED_DATA_POINTERS ? " (volatile)" : "");
}
// The two casts keep these inside Data Memory. The Stack Pointer starts at the
// very top, so without them the display would read off the end of the array
// before a single byte has been pushed.
printf(" Stack Pointer: 0x%04X Current Value: (0x%02X) (0x%02X)\n", cpu->StackPointer,
Data[(uint16_t)(cpu->StackPointer + 1)], Data[(uint16_t)(cpu->StackPointer + 2)]);
}