// pad.c // Game controllers for the Voyager. // Written by Anachronaut #include "pad.h" #include "video.h" // What each pad is holding, and where each one gets it from. static uint8_t held[PAD_COUNT]; static FILE *recorded[PAD_COUNT]; static uint8_t live[PAD_COUNT]; static int connected[PAD_COUNT]; static FILE *recording[PAD_COUNT]; // ---- The frame the recordings advance on ---- // // The screen's frame, and it is the same one on purpose: a game reads its pad once a frame // because that is when it draws, so a byte a frame is a byte a poll for anything written the // ordinary way - without making it a byte a READ, which would answer a game that asked twice // differently from one that asked once. // // On the machine's clock, so a recording plays back the same over the same cycles however // fast the host ran. static unsigned long lastFrame; static int started; void padReset(void) { for (int n = 0; n < PAD_COUNT; n++) { held[n] = 0; live[n] = 0; connected[n] = 0; // The files are NOT closed or forgotten. They were named on the command line and // outlive a reset, the same as a disk image does: a machine that restarted itself // and lost its controllers would be a strange thing to debug. } lastFrame = 0; started = 0; } // What the device would report for this pad, which is what a recording has to hold: a // recording of a playback that wrote the LIVE state would be a file of noughts. static uint8_t effective(int which) { return (recorded[which] != NULL) ? held[which] : live[which]; } void padRecordTo(int which, FILE *file) { if (which < 0 || which >= PAD_COUNT) { return; } recording[which] = file; } void padFromFile(int which, FILE *file) { if (which < 0 || which >= PAD_COUNT) { return; } recorded[which] = file; } void padSet(int which, int isConnected, uint8_t heldNow) { if (which < 0 || which >= PAD_COUNT) { return; } connected[which] = isConnected; live[which] = heldNow; } void padTick(unsigned long now) { // The first tick sets the clock rather than counting a frame from nought, or a machine // that started late would take a run of bytes all at once. if (!started) { lastFrame = now; started = 1; } while (now - lastFrame >= VIDEO_FRAME_CYCLES) { lastFrame += VIDEO_FRAME_CYCLES; for (int n = 0; n < PAD_COUNT; n++) { if (recorded[n] == NULL) { continue; } const int byte = fgetc(recorded[n]); // ---- The end of a recording is nothing held ---- // // Not a pad that vanishes and not the last frame repeating for ever. A recording // that ran out and left a direction pressed would send whatever it was driving // off the edge of the world long after the test meant to stop. held[n] = (byte == EOF) ? 0 : (uint8_t)byte; } // ---- And a byte written for every frame that went by ---- // // Inside the loop rather than after it, so a machine that jumped several frames at // once still writes one byte for each of them. A recording is a TIMELINE, and one // that skipped the frames nobody was looking at would play back faster than it was // flown. // // Flushed as it goes, because a recording is usually stopped by whoever is playing // rather than by the program ending, and a demo lost to a buffer would be a demo // flown twice. for (int n = 0; n < PAD_COUNT; n++) { if (recording[n] != NULL) { fputc(effective(n), recording[n]); fflush(recording[n]); } } } } uint8_t padRead(uint8_t port) { if (port == PAD_PRESENT) { uint8_t there = 0; for (int n = 0; n < PAD_COUNT; n++) { // A recording is a pad, and so is anything the front end says is plugged in. // Counting only the recordings meant this said nought on the one machine that // has real controllers, which is the only machine where the answer matters. if (recorded[n] != NULL || connected[n]) { there |= (uint8_t)(1u << n); } } return there; } const int which = port - PORT_PAD; if (which < 0 || which >= PAD_COUNT) { // Everything else in the block is reserved and reads as nothing, which is what a // port block being kept for later should do. return 0; } // A recording wins over a live pad, so a test is not at the mercy of whatever somebody // is leaning on while it runs. return effective(which); }