#include #include "v9t9_common.h" #include "vdp.h" #include "sound.h" #include "demo.h" #define _L LOG_DEMO static OSSpec demo_spec; static OSRef demo_ref; static OSHandle demo_handle; static bool demo_recording; /* * Append data to demo. */ static void handle_append(OSHandle *handle, void *data, OSSize size) { OSError err; if (demo_recording) { err = OS_AppendHandle(handle, data, size); if (err != OS_NOERR) { logger(_L|LOG_ERROR|LOG_USER, "Could not add %d bytes to demo (%s)\n", size, OS_GetErrText(err)); demo_stop_record(); } } } typedef struct { int type, idx, max; u8 *data; } demo_buffer; static demo_buffer buffer_video, buffer_sound, buffer_speech; static u16 vdp_addr_next; /* * Initialize buffer for reading or writing. */ static void buffer_init(demo_buffer *buffer, int size, int type) { buffer->type = type; buffer->idx = 0; buffer->max = size; if (buffer->data) xfree(buffer->data); buffer->data = (u8 *)xmalloc(size); } /* * Terminate use of a buffer. */ static void buffer_term(demo_buffer *buffer) { if (buffer->data) xfree(buffer->data); memset(buffer, 0, sizeof(demo_buffer)); } /* * Set up a buffer for reading or writing. */ static void buffer_setup(demo_buffer *buffer) { buffer->idx = 0; } /* * Flush a buffer to the file, with a type and length header. */ static void buffer_flush(demo_buffer *buffer) { u8 header[4]; // write event type and buffer length header[0] = buffer->type; header[1] = (buffer->idx & 0xff); header[2] = (buffer->idx >> 8) & 0xff; handle_append(&demo_handle, (void *)header, 3); // write data handle_append(&demo_handle, (void *)buffer->data, buffer->idx); buffer->idx = 0; } /* * Write data to a buffer */ static void buffer_write(demo_buffer *buffer, void *data, int len) { if (buffer->idx + len > buffer->max) { buffer_flush(buffer); } memcpy((u8 *)buffer->data + buffer->idx, data, len); buffer->idx += len; } /* * Write data into a buffer */ static void buffer_poke(demo_buffer *buffer, int offset, void *data, int len) { if (offset + len > buffer->idx) { logger(_L|LOG_FATAL, "buffer_poke: overrun (%d+%d > %d)\n", offset, len, buffer->idx); } memcpy((u8 *)buffer->data + offset, data, len); } void demo_init(void) { // these constants were hardcoded in v9t9 6.0 and // should probably be kept this way buffer_init(&buffer_video, 8192, demo_type_video); buffer_init(&buffer_sound, 1024, demo_type_sound); buffer_init(&buffer_speech, 512, demo_type_speech); } void demo_term(void) { buffer_term(&buffer_video); buffer_term(&buffer_sound); buffer_term(&buffer_speech); } int demo_start_record(OSSpec *spec) { OSError err; int addr; logger(_L|L_1, "Setting up demo (%s)\n", OS_SpecToString1(spec)); /* Create file first */ demo_spec = *spec; err = OS_Create(spec, &OS_TEXTTYPE); if (err != OS_NOERR) { logger(_L|LOG_ERROR|LOG_USER, "Could not create demo file '%s' (%s)\n", OS_SpecToString1(spec), OS_GetErrText(err)); return 0; } /* Store all data in a memory handle */ err = OS_NewHandle(0, &demo_handle); if (err != OS_NOERR) { logger(_L|LOG_ERROR|LOG_USER, "Could not allocate memory for demo (%s)\n", OS_GetErrText(err)); return 0; } /* Clear buffers */ buffer_setup(&buffer_video); buffer_setup(&buffer_sound); buffer_setup(&buffer_speech); /* Write initial data */ demo_recording = true; stateflag |= ST_DEMOING; /* Magic header */ handle_append(&demo_handle, DEMO_MAGIC_HEADER, sizeof(DEMO_MAGIC_HEADER)); /* Write VDP regs */ for (addr = 0; addr < 8; addr++) { demo_record_event(demo_type_video, 0x8000 + (addr << 8) + vdpregs[addr]); } /* Write VDP memory */ for (addr = 0; addr < 0x4000; addr++) { demo_record_event(demo_type_video, 0x4000 + addr, vdp_mmio_read(addr)); } buffer_flush(&buffer_video); /* Write sound data */ for (addr = 0; addr < 4; addr++) { u8 base = 0x80 + (addr << 5); /* tone/noise bytes */ demo_record_event(demo_type_sound, sound_voices[addr].operation[OPERATION_FREQUENCY_LO]); demo_record_event(demo_type_sound, sound_voices[addr].operation[OPERATION_FREQUENCY_HI]); demo_record_event(demo_type_sound, sound_voices[addr].operation[OPERATION_ATTENUATION]); /* volume */ demo_record_event(demo_type_sound, base + sound_voices[addr].volume); } buffer_flush(&buffer_sound); /* Write speech data */ demo_record_event(demo_type_speech, demo_speech_terminating); buffer_flush(&buffer_speech); return 1; } void demo_pause_record(bool pausing) { demo_record_event(demo_type_tick); if (pausing) stateflag &= ~ST_DEMOING; else stateflag |= ST_DEMOING; } void demo_stop_record(void) { OSError err; OSRef demo_ref; OSSize size; void *ptr; logger(_L|L_1, "Closing demo\n"); if (demo_recording) { logger(_L|L_1, "Closing demo (%s)\n", OS_SpecToString1(&demo_spec)); buffer_flush(&buffer_video); buffer_flush(&buffer_sound); buffer_flush(&buffer_speech); demo_recording = false; if ((err = OS_Open(&demo_spec, OSWrite, &demo_ref)) == OS_NOERR && (err = OS_GetHandleSize(&demo_handle, &size)) == OS_NOERR && (ptr = OS_LockHandle(&demo_handle)) != 0L && (err = OS_Write(demo_ref, (void *)ptr, &size)) == OS_NOERR) { OS_UnlockHandle(&demo_handle); OS_FreeHandle(&demo_handle); OS_Close(demo_ref); } else { logger(_L|LOG_USER|LOG_ERROR, "Could not write demo file '%s' (%s)\n", OS_SpecToString1(&demo_spec), OS_GetErrText(err)); } stateflag &= ~ST_DEMOING; } } /* * Main interface to demo saving engine */ int demo_record_event(demo_type type, ...) { va_list va; int arg; u8 data[8]; va_start(va, type); switch (type) { case demo_type_tick: /* flush all current data */ logger(_L|L_1, "Tick\n"); data[0] = type; handle_append(&demo_handle, (void *)data, 1); buffer_flush(&buffer_video); buffer_flush(&buffer_sound); buffer_flush(&buffer_speech); break; /* for these events, buffer */ case demo_type_video: /* if address is different than the last one plus one, flush the buffer */ arg = va_arg(va, int); my_assert(buffer_video.max == 256); logger(_L|L_2, "Video >%04X", arg); if (arg != vdp_addr_next // contiguous || buffer_video.idx == 0 // empty || buffer_video.idx == 255 + 3) // full { if (buffer_video.idx != 0) { #warning this is flawed; there is another level of nesting here /* set subblock length byte */ data[0] = buffer_video.idx - 3; buffer_poke(&buffer_video, 2, data, 1); buffer_flush(&buffer_video); } data[0] = arg & 0xff; data[1] = (arg >> 8) & 0xff; buffer_write(&buffer_video, data, 2); vdp_addr_next = arg + 1; /* byte for contiguous subblock length */ data[0] = 0; buffer_write(&buffer_video, data, 1); } /* write data if not a register set */ if (!(arg & 0x8000)) { data[0] = va_arg(va, int); logger(_L|L_2, " (>%02X)", data[0]); buffer_write(&buffer_video, data, 1); vdp_addr_next++; } logger(_L|L_2, "\n"); break; case demo_type_sound: /* just send it */ data[0] = va_arg(va, int); logger(_L|L_2, "Sound >%02X\n", data[0]); buffer_write(&buffer_sound, data, 1); break; case demo_type_speech: /* just send it */ data[0] = va_arg(va, int); logger(_L|L_2, "Speech >%02X", data[0]); buffer_write(&buffer_speech, data, 1); /* this event has a byte of info */ if (data[0] == demo_speech_adding_byte) { data[0] = va_arg(va, int); logger(_L|L_2, " (>%02X)", data[0]); buffer_write(&buffer_speech, data, 1); } logger(_L|L_2, "\n"); break; default: logger(LOG_FATAL, "Unknown event type passed to demo_event (%d)\n", type); break; } va_end(va); return 1; }