Files
vt1200/bad-apple/player/main.c
T

240 lines
6.1 KiB
C

/* main.c -- Bad Apple payload: network receive, then tile playback. */
#include "movie.h"
#include "romapi.h"
#include "modecfg.h"
#ifndef NET_DEFAULT
#define NET_DEFAULT 0
#endif
/* The 30 s stream's exact size, from data/manifest.json. The real loader
* allocates this before the first datagram; a different clip needs a rebuild. */
#define NET_LEN 2401942
#define MOVIE_BASE 0x00080000
#define MOVIE_MAX 0x00308000
#define VRAM_BASE 0x04000000
#define ROW_BITS 2048
#define MARK 0x01810000
#define CTRL_START (MARK + 0x20 * 8)
#define CTRL_COUNT (MARK + 0x24 * 8)
#define CTRL_PACE (MARK + 0x28 * 8)
#define CTRL_MOVIE (MARK + 0x2C * 8)
#define CTRL_MOVIELEN (MARK + 0x30 * 8)
#define CTRL_WAIT (MARK + 0x34 * 8)
#define CTRL_READY (MARK + 0x38 * 8)
#define CTRL_NET (MARK + 0x3C * 8)
#define NET_PORT 7001
extern void fill_run_();
extern void fill_init_();
extern void vid_dint_();
unsigned int fill_daddr;
unsigned int fill_dydx;
unsigned int fill_color;
void fill_rect(x, y, w, h, color)
int x;
int y;
int w;
int h;
int color;
{
if (w <= 0 || h <= 0) {
return;
}
fill_daddr = VRAM_BASE + (unsigned int)y * ROW_BITS + (unsigned int)x;
fill_dydx = ((unsigned int)h << 16) | (unsigned int)w;
/* The decoder's colour is the canvas value: 1 = black, 0 = white. */
fill_color = color ? 0x0000 : 0xFFFF;
fill_run_();
}
static void paint(x, y, w, h, color, ctx)
int x;
int y;
int w;
int h;
int color;
void *ctx;
{
fill_rect(x, y, w, h, color);
}
static unsigned char rxbuf[1600];
static unsigned char sa[16];
static unsigned char from[16];
static int fromlen;
/* Receive movie.bin over the OS's UDP stack into a malloc'd bit buffer.
* Packet: u32 offset, u16 length, u16 seq, then bytes; length 0 is EOF. */
static unsigned int receive_movie(expected)
unsigned int expected;
{
unsigned int ptr;
unsigned int offset;
unsigned int length;
unsigned int total;
unsigned int i;
unsigned char *dst;
int fd;
int n;
ptr = malloc_(expected);
if (ptr == 0) {
return 0;
}
fd = socket_(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0) {
return 0;
}
sa_any(sa, NET_PORT);
if (bind_(fd, sa, SOCKADDR_LEN) < 0) {
close_(fd);
return 0;
}
total = 0;
dst = (unsigned char *)ptr;
for (;;) {
fromlen = SOCKADDR_LEN;
n = recvfrom_(fd, rxbuf, (int)sizeof(rxbuf), 0, from, &fromlen);
if (n < 8) {
continue;
}
offset = (unsigned int)rxbuf[0]
| ((unsigned int)rxbuf[1] << 8)
| ((unsigned int)rxbuf[2] << 16)
| ((unsigned int)rxbuf[3] << 24);
length = (unsigned int)rxbuf[4] | ((unsigned int)rxbuf[5] << 8);
if (length == 0) {
break;
}
if (offset + length > expected) {
continue;
}
for (i = 0; i < length; i++) {
dst[offset + i] = rxbuf[8 + i];
}
total += length;
if (total >= expected) {
break;
}
}
close_(fd);
return ptr;
}
/* Poll the display interrupt pending bit. Interrupts are disabled; this is
* a status bit, not an ISR. A missing DPYINT only costs a bounded spin. */
static void wait_vsync()
{
unsigned int i;
unsigned short pending;
/* Clear a stale DI, then wait for the next one. DPYINT is programmed to
* line 1 once at startup, so DI appears at the top of every frame. */
*(unsigned short *)0xC0000120 = 0;
for (i = 0; i < 200000; i++) {
pending = *(unsigned short *)0xC0000120;
if (pending & 0x0400) {
return;
}
}
}
int main()
{
struct movie_header header;
unsigned int frame;
unsigned int last;
unsigned int start;
unsigned int count;
unsigned int pace;
unsigned int movie_base;
unsigned int movie_len;
unsigned int net;
int rc;
movie_base = *(unsigned int *)CTRL_MOVIE;
movie_len = *(unsigned int *)CTRL_MOVIELEN;
if (movie_base == 0) {
movie_base = MOVIE_BASE;
}
if (movie_len == 0) {
movie_len = MOVIE_MAX;
}
net = *(unsigned int *)CTRL_NET;
if (net == 0 && NET_DEFAULT) {
net = 1;
}
if (net != 0) {
/* Real delivery: the OS stack is still alive for the receive; the
* payload owns the machine only after the last byte arrives. */
movie_len = NET_LEN;
*(unsigned int *)(MARK + 0x10 * 8) = 0x20;
movie_base = receive_movie(movie_len);
if (movie_base == 0) {
*(unsigned int *)MARK = 0xBAD00003;
for (;;) {
}
}
vid_dint_();
} else {
vid_dint_();
if (*(unsigned int *)CTRL_WAIT != 0) {
*(unsigned int *)(MARK + 0x10 * 8) = 0x10;
while (*(unsigned int *)CTRL_READY == 0) {
}
}
}
fill_init_();
/* Display interrupt at line 1: our only timebase. Interrupts stay off;
* wait_vsync() polls the pending bit. */
*(unsigned short *)0xC00000A0 = 1;
/* Start from white; the OS left its own screen behind. */
fill_rect(0, 0, 1280, 1024, 0);
rc = movie_open((unsigned char *)movie_base, movie_len, &header);
if (rc != 0) {
*(unsigned int *)MARK = 0xBAD00001;
for (;;) {
}
}
start = *(unsigned int *)CTRL_START;
count = *(unsigned int *)CTRL_COUNT;
pace = *(unsigned int *)CTRL_PACE;
last = header.frame_count;
if (last != 0) {
last = last - 1;
}
if (count != 0 && start + count - 1 < last) {
last = start + count - 1;
}
for (frame = 0; frame <= last; frame++) {
if (pace != 0 && frame >= start) {
wait_vsync();
}
rc = movie_decode_frame((unsigned char *)movie_base, movie_len, frame,
&header, paint, 0);
if (rc != 0) {
*(unsigned int *)MARK = 0xBAD00002;
*(unsigned int *)(MARK + 0x14 * 8) = frame;
for (;;) {
}
}
*(unsigned int *)MARK = frame;
}
*(unsigned int *)(MARK + 0x10 * 8) = (count != 0) ? 2 : 1;
for (;;) {
}
}