Files
SplitBit-Emulator/Source/Emulator/bootstrap.c
T
AnachronautandClaude Opus 5 b6004bdcde Say "boot image" where that is what is meant
"Binary" was doing three jobs. It meant an SPBT file that the machine starts
from; it meant whatever the assembler happened to produce, which is now
either that or a loadable program; and it meant a compiled host tool. A word
that means three things means none of them, and the first of the three has a
name already - this project has been calling them boot images for a while
and the manuals had not caught up.

  Where it means an SPBT file       -> boot image
  Where it means either output      -> output
  Where it means a host executable  -> left alone
  Where it means base two           -> left alone

The user facing messages move with it:

  Error: No boot image specified.
  Usage: ./SplitBit [OPTIONS] <boot image>
  Error: This is not a SplitBit boot image.
  Error: This boot image is in format version 2, and this emulator reads 1.
  Successfully wrote SplitBit boot image to "hello.bin".

The assembler's own help was the interesting case. Its -o writes either
format, so "the binary" there was never right - it is "the output" now, and
the message that names the format is the one that says which it wrote.

No recorded output contained the word, so nothing needed re-blessing.
Checked before starting rather than after.

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

162 lines
6.0 KiB
C

// boostrap.c
// Boostrapping Functions for the SplitBit CPU Emulator
// Written by Anachronaut
// 10/16/2024
#include "bootstrap.h"
#include "../Assembler/assembly.h" // For the boot image format, which both tools share.
#include <stdio.h>
#include <string.h>
// Reads a number of the given width, most significant byte first.
// Returns 1 if the file runs out before the number does.
static uint8_t readNumber(FILE *file, int width, const char *what, uint32_t *result) {
uint32_t value = 0;
for (int i = 0; i < width; i++) {
int byte = fgetc(file);
if (byte == EOF) {
fprintf(stderr, "Error: Unexpected end of file while reading %s.\n", what);
return 1;
}
value = (value << 8) | (uint8_t)byte;
}
*result = value;
return 0;
}
// Reads a fixed length marker and checks it against the one expected. The caller's
// buffer must have room for length + 1 characters; it always comes back with a null
// on the end, so it is safe to print in an error message either way.
static uint8_t readMarker(FILE *file, const char *expected, int length, char *found) {
memset(found, 0, length + 1);
for (int i = 0; i < length; i++) {
int byte = fgetc(file);
if (byte == EOF) {
return 1;
}
found[i] = (char)byte;
}
return strncmp(found, expected, length) != 0;
}
// Reads the file header: the magic, the format version, and the features the boot image
// says it needs from the machine.
static uint8_t readFileHeader(FILE *file) {
char magic[SPLITBIT_MAGIC_LENGTH + 1];
if (readMarker(file, SPLITBIT_MAGIC, SPLITBIT_MAGIC_LENGTH, magic)) {
fprintf(stderr, "Error: This is not a SplitBit boot image.\n");
if (strncmp(magic, "PRG", 3) == 0) {
fprintf(stderr, " It looks like a boot image from before the format carried a version.\n Reassemble it and try again.\n");
} else {
fprintf(stderr, " Expected the file to begin with \"%s\", found \"%s\".\n", SPLITBIT_MAGIC, magic);
}
return 1;
}
uint32_t version;
if (readNumber(file, 1, "the format version", &version)) {
return 1;
}
if (version != SPLITBIT_FORMAT_VERSION) {
fprintf(stderr, "Error: This boot image is in format version %u, and this emulator reads version %u.\n", version, SPLITBIT_FORMAT_VERSION);
return 1;
}
uint32_t required;
if (readNumber(file, SPLITBIT_FLAGS_LENGTH, "the feature flags", &required)) {
return 1;
}
uint32_t missing = required & ~(uint32_t)SPLITBIT_FEATURES_SUPPORTED;
if (missing) {
fprintf(stderr, "Error: This boot image was built for a machine this emulator cannot provide.\n");
fprintf(stderr, " It asks for feature bits 0x%08X, which are not implemented here.\n", missing);
return 1;
}
return 0;
}
static uint8_t loadSegment(FILE *file, uint8_t *Memory, uint32_t length) {
for (uint32_t i = 0; i < length; i++) {
int byte = fgetc(file);
if (byte == EOF) {
fprintf(stderr, "Error: Unexpected end of file while reading a segment.\n");
return 1;
}
Memory[i] = (uint8_t)byte;
}
return 0;
}
// Reads one segment: its marker, its length, and then its contents.
static uint8_t readSegment(FILE *file, const char *marker, uint8_t *Memory) {
char found[SEGMENT_MARKER_LENGTH + 1];
if (readMarker(file, marker, SEGMENT_MARKER_LENGTH, found)) {
fprintf(stderr, "Error: Expected a \"%s\" segment here, found \"%s\".\n", marker, found);
return 1;
}
uint32_t length;
if (readNumber(file, SEGMENT_LENGTH_BYTES, "a segment length", &length)) {
return 1;
}
return loadSegment(file, Memory, length);
}
// Reads the Vector Segment, which is optional and last. A file that simply ends here
// was written before vectors existed, and an empty table is exactly right for it: every
// entry reads as zero, which means no handler, and the boot vector reading zero means
// the program starts at 0x0000 the way it always did.
//
// Each entry says where in Program Memory the vector sits and where its handler is, so
// installing one is a write straight into the vector table.
static uint8_t readVectorSegment(FILE *file, uint8_t *Program) {
int first = fgetc(file);
if (first == EOF) {
return 0;
}
ungetc(first, file);
char found[SEGMENT_MARKER_LENGTH + 1];
if (readMarker(file, "VEC", SEGMENT_MARKER_LENGTH, found)) {
fprintf(stderr, "Error: Expected a \"VEC\" segment here, found \"%s\".\n", found);
return 1;
}
uint32_t length;
if (readNumber(file, SEGMENT_LENGTH_BYTES, "the vector segment length", &length)) {
return 1;
}
if (length % VECTOR_ENTRY_FILE_BYTES != 0) {
fprintf(stderr, "Error: The vector segment is %u bytes, which is not a whole number of vectors.\n", length);
return 1;
}
for (uint32_t i = 0; i < length / VECTOR_ENTRY_FILE_BYTES; i++) {
uint32_t slot, handler;
if (readNumber(file, 2, "a vector address", &slot)
|| readNumber(file, 2, "a handler address", &handler)) {
return 1;
}
if (slot < SOFTWARE_VECTOR_BASE) {
fprintf(stderr, "Error: This boot image puts a vector at 0x%04X, which is below the vector table.\n", slot);
return 1;
}
Program[slot] = (handler >> 8) & 0xFF;
Program[(uint16_t)(slot + 1)] = handler & 0xFF;
}
return 0;
}
uint8_t loadFile(char *path, uint8_t *Program, uint8_t *Data) {
FILE *file = fopen(path, "rb");
if (file == NULL) {
fprintf(stderr, "Error: Couldn't open file: %s\n", path);
return 1;
}
// The Program Segment must come first, then the Data Segment. Short circuiting
// here means there is one exit, and so only one place that has to close the file.
uint8_t failed = readFileHeader(file)
|| readSegment(file, "PRG", Program)
|| readSegment(file, "DAT", Data)
|| readVectorSegment(file, Program);
fclose(file);
return failed;
}