~rabbits/uxn-playdate

ff99984d0fc76ddf0ded327d4e8ce22e71d7055d — neauoire 5 months ago 22cae76
Housekeeping
6 files changed, 11 insertions(+), 703 deletions(-)

D devices/apu.c
D devices/apu.h
D devices/ppu.c
D devices/ppu.h
D main.c
M src/main.c
D devices/apu.c => devices/apu.c +0 -95
@@ 1,95 0,0 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include "apu.h"

#define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025)
#define ADSR_STEP (SAMPLE_FREQUENCY / 0xf)

/* clang-format off */

static uint32_t advances[12] = {
	0x80000, 0x879c8, 0x8facd, 0x9837f, 0xa1451, 0xaadc1,
	0xb504f, 0xbfc88, 0xcb2ff, 0xd7450, 0xe411f, 0xf1a1c
};

/* clang-format on */

static int32_t
envelope(Apu *c, uint32_t age)
{
	if(!c->r) return 0x0888;
	if(age < c->a) return 0x0888 * age / c->a;
	if(age < c->d) return 0x0444 * (2 * c->d - c->a - age) / (c->d - c->a);
	if(age < c->s) return 0x0444;
	if(age < c->r) return 0x0444 * (c->r - age) / (c->r - c->s);
	c->advance = 0;
	return 0x0000;
}

int
apu_render(Apu *c, uint8_t *dat, int16_t *left, int16_t *right, int len)
{
	int32_t s;
	if(!c->advance || !c->period) return 0;
	while(len--) {
		c->count += c->advance;
		c->i += c->count / c->period;
		c->count %= c->period;
		if(c->i >= c->len) {
			if(!c->repeat) {
				c->advance = 0;
				break;
			}
			c->i %= c->len;
		}
		s = (int8_t)(dat[c->addr + c->i] + 0x80) * envelope(c, c->age++);
		*left++ = s * c->volume[0] / 0x180;
		*right++ = s * c->volume[1] / 0x180;
	}
	return 1;
}

void
apu_start(Apu *c, uint16_t adsr, uint8_t pitch)
{
	if(pitch < 108 && c->len)
		c->advance = advances[pitch % 12] >> (8 - pitch / 12);
	else {
		c->advance = 0;
		return;
	}
	c->a = ADSR_STEP * (adsr >> 12);
	c->d = ADSR_STEP * (adsr >> 8 & 0xf) + c->a;
	c->s = ADSR_STEP * (adsr >> 4 & 0xf) + c->d;
	c->r = ADSR_STEP * (adsr >> 0 & 0xf) + c->s;
	c->age = 0;
	c->i = 0;
	if(c->len <= 0x100) /* single cycle mode */
		c->period = NOTE_PERIOD * 337 / 2 / c->len;
	else /* sample repeat mode */
		c->period = NOTE_PERIOD;
}

uint8_t
apu_get_vu(Apu *c)
{
	int i;
	int32_t sum[2] = {0, 0};
	if(!c->advance || !c->period) return 0;
	for(i = 0; i < 2; ++i) {
		if(!c->volume[i]) continue;
		sum[i] = 1 + envelope(c, c->age) * c->volume[i] / 0x800;
		if(sum[i] > 0xf) sum[i] = 0xf;
	}
	return (sum[0] << 4) | sum[1];
}

D devices/apu.h => devices/apu.h +0 -27
@@ 1,27 0,0 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include <stdint.h>

#define SAMPLE_FREQUENCY 44100

typedef struct {
	uint32_t count, advance, period, age, a, d, s, r;
	uint16_t addr, i, len;
	int8_t volume[2];
	uint8_t pitch, repeat;
} Apu;

int apu_render(Apu *c, uint8_t *dat, int16_t *left, int16_t *right, int len);
void apu_start(Apu *c, uint16_t adsr, uint8_t pitch);
uint8_t apu_get_vu(Apu *c);
void apu_finished_handler(Apu *c);

D devices/ppu.c => devices/ppu.c +0 -109
@@ 1,109 0,0 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include "ppu.h"

int
ppu_init(Ppu *p, uint32_t *framebuffer)
{
	p->framebuffer = framebuffer;
	return 1;
}

static inline void
render(Ppu *p, int offset, int count)
{
	uint32_t *in = &p->pixels[offset * 3];
	uint32_t *out = &p->framebuffer[offset];
	while(count--) {
		*(out++) = in[0] ^ (in[2] & (in[0] ^ in[1]));
		in += 3;
	}
}

void
render_all(Ppu *p)
{
	render(p, 0, STRIDE_WORDS * HEIGHT);
}

void
ppu_pixel(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t color)
{
	if(x >= WIDTH_PIXELS || y >= HEIGHT) return;
	int offset = y * STRIDE_WORDS + x / 32;
	uint32_t *px = &p->pixels[offset * 3 + layer];
	uint32_t mask = 1 << ((x & 0x18) + (7 - (x & 0x7)));
	if(p->color_is_white[color])
		p->pixels[offset * 3 + layer] |= mask;
	else
		p->pixels[offset * 3 + layer] &= ~mask;
	if(layer) {
		if(color)
			p->pixels[offset * 3 + 2] |= mask;
		else
			p->pixels[offset * 3 + 2] &= ~mask;
	}
	render(p, offset, 1);
}

void
ppu_clear(Ppu *p)
{
	uint32_t background = p->color_is_white[0] ? 0xffffffff : 0;
	int i;
	for(i = 0; i < HEIGHT * STRIDE_WORDS * 3; i += 3) {
		p->pixels[i] = background;
		p->pixels[i + 2] = 0;
	}
}

static uint8_t blending[5][16] = {
	{0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
	{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
	{1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
	{2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2},
	{1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}};

void
ppu_1bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy)
{
	uint16_t v, h;
	for(v = 0; v < 8; v++)
		for(h = 0; h < 8; h++) {
			uint8_t ch1 = (sprite[v] >> (7 - h)) & 0x1;
			if(ch1 || blending[4][color])
				ppu_pixel(p,
					layer,
					x + (flipx ? 7 - h : h),
					y + (flipy ? 7 - v : v),
					blending[ch1][color]);
		}
}

void
ppu_2bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy)
{
	uint16_t v, h;
	for(v = 0; v < 8; v++)
		for(h = 0; h < 8; h++) {
			uint8_t ch1 = ((sprite[v] >> (7 - h)) & 0x1);
			uint8_t ch2 = ((sprite[v + 8] >> (7 - h)) & 0x1);
			uint8_t ch = ch1 + ch2 * 2;
			if(ch || blending[4][color])
				ppu_pixel(p,
					layer,
					x + (flipx ? 7 - h : h),
					y + (flipy ? 7 - v : v),
					blending[ch][color]);
		}
}

D devices/ppu.h => devices/ppu.h +0 -46
@@ 1,46 0,0 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include <stdint.h>

#define WIDTH_PIXELS 400
#define STRIDE_PIXELS (52 * 8)
#define STRIDE_WORDS (STRIDE_PIXELS / 32)
#define HEIGHT 240

/*
 * framebuffer is the Playdate's raw framebuffer.
 *
 * pixels is the buffer for Varvara's two graphics layers and are grouped in
 * three sets of 32 bits.
 *
 * pixels[0] carries the background color of the first 32 pixels,
 * pixels[1] carries the foreground color, and
 * pixels[2] carries the mask that switches between the two.
 *
 * Zero color bits are white, ones are black.
 * Zero mask bits mean that the background color is shown, one is for the
 * foreground color.
 */

typedef struct Ppu {
	uint32_t pixels[HEIGHT * STRIDE_WORDS * 3];
	uint32_t *framebuffer;
	int color_is_white[4];
} Ppu;

int ppu_init(Ppu *p, uint32_t *framebuffer);
void ppu_pixel(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t color);
void ppu_1bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy);
void ppu_2bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy);
void ppu_clear(Ppu *p);
void render_all(Ppu *p);

D main.c => main.c +0 -412
@@ 1,412 0,0 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include "pd_api.h"
#include "uxn/src/uxn.h"
#include "devices/apu.h"
#include "devices/ppu.h"

#define POLYPHONY 4
#define BOOT_ROM_FILENAME "boot.rom"
#define SNAPSHOT_FILENAME "uxn.sna"
#define SNAPSHOT_VERSION 1

static PlaydateAPI *pd = NULL;
static SoundSource *apu_sources[POLYPHONY];
static Uxn u;
static Device *devctrl, *devconsole, *devscreen, *devmouse, *devdatetime, *devaudio0;
static Ppu ppu;
static Apu apu[POLYPHONY];

static const char *errors[] = {"underflow", "overflow", "division by zero"};

int
uxn_halt(Uxn *u, Uint8 error, char *name, int id)
{
	pd->system->error("Halted: %s %s#%04x, at 0x%04x\n", name, errors[error - 1], id, u->ram.ptr);
	u->ram.ptr = 0;
	return 0;
}

static void
docolors(Device *d)
{
	int i, sum;
	for(i = 0; i < 4; ++i) {
		sum = ((d->dat[0x8 + i / 2] >> (!(i % 2) << 2)) & 0x0f);
		sum += ((d->dat[0xa + i / 2] >> (!(i % 2) << 2)) & 0x0f);
		sum += ((d->dat[0xc + i / 2] >> (!(i % 2) << 2)) & 0x0f);
		ppu.color_is_white[i] = sum > 0x17;
	}
	ppu_clear(&ppu);
	render_all(&ppu);
}

static uint8_t
getcolors(int i)
{
	return (ppu.color_is_white[i] ? 0xf0 : 0) + (ppu.color_is_white[i + 1] ? 0x0f : 0);
}

static int
system_talk(Device *d, Uint8 b0, Uint8 w)
{
	if(!w) { /* read */
		switch(b0) {
		case 0x2: d->dat[0x2] = d->u->wst.ptr; break;
		case 0x3: d->dat[0x3] = d->u->rst.ptr; break;
		case 0x8 ... 0xd: d->dat[b0] = getcolors((b0 & 1) << 1); break;
		}
	} else { /* write */
		switch(b0) {
		case 0x2: d->u->wst.ptr = d->dat[0x2]; break;
		case 0x3: d->u->rst.ptr = d->dat[0x3]; break;
		case 0x8 ... 0xd: docolors(d); break;
		case 0xf: d->u->ram.ptr = 0x0000; break;
		}
	}
	return 1;
}

static int
console_talk(Device *d, Uint8 b0, Uint8 w)
{
	if(w && b0 > 0x7)
		pd->system->logToConsole("%c", d->dat[b0]);
	return 1;
}

static int
screen_talk(Device *d, Uint8 b0, Uint8 w)
{

	if(w && b0 == 0xe) {
		Uint16 x = peek16(d->dat, 0x8);
		Uint16 y = peek16(d->dat, 0xa);
		Uint8 layer = d->dat[0xe] & 0x40;
		ppu_pixel(&ppu, !!layer, x, y, d->dat[0xe] & 0x3);
		if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 1); /* auto x+1 */
		if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 1); /* auto y+1 */
		pd->graphics->markUpdatedRows(y, y);
	} else if(w && b0 == 0xf) {
		Uint16 x = peek16(d->dat, 0x8);
		Uint16 y = peek16(d->dat, 0xa);
		Uint8 layer = d->dat[0xf] & 0x40;
		Uint8 *addr = &d->mem[peek16(d->dat, 0xc)];
		if(d->dat[0xf] & 0x80) {
			ppu_2bpp(&ppu, !!layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20);
			if(d->dat[0x6] & 0x04) poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 16); /* auto addr+16 */
		} else {
			ppu_1bpp(&ppu, !!layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20);
			if(d->dat[0x6] & 0x04) poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 8); /* auto addr+8 */
		}
		if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 8); /* auto x+8 */
		if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 8); /* auto y+8 */
		pd->graphics->markUpdatedRows(y, y + 7);
	}
	return 1;
}

static int
file_talk(Device *d, Uint8 b0, Uint8 w)
{
	Uint8 read = b0 == 0xd;
	if(w && (read || b0 == 0xf)) {
		char *name = (char *)&d->mem[peek16(d->dat, 0x8)];
		Uint16 result = 0, length = peek16(d->dat, 0xa);
		Uint16 offset = peek16(d->dat, 0x4);
		Uint16 addr = peek16(d->dat, b0 - 1);
		SDFile *f = pd->file->open(name, read ? (kFileRead | kFileReadData) : (offset ? kFileAppend : kFileWrite));
		if(f) {
			pd->system->logToConsole("%s %s %s #%04x, ", read ? "Loading" : "Saving", name, read ? "to" : "from", addr);
			if(pd->file->seek(f, offset, SEEK_SET) != -1)
				result = read ? pd->file->read(f, &d->mem[addr], length) : pd->file->write(f, &d->mem[addr], length);
			pd->system->logToConsole("%04x bytes\n", result);
			pd->file->close(f);
		} else {
			pd->system->logToConsole("unable to open %s", name);
		}
		poke16(d->dat, 0x2, result);
	}
	return 1;
}

static int
audio_talk(Device *d, Uint8 b0, Uint8 w)
{
	Apu *c = &apu[d - devaudio0];
	if(!w) {
		if(b0 == 0x2)
			poke16(d->dat, 0x2, c->i);
		else if(b0 == 0x4)
			d->dat[0x4] = apu_get_vu(c);
	} else if(b0 == 0xf) {
		c->len = peek16(d->dat, 0xa);
		c->addr = peek16(d->dat, 0xc);
		c->volume[0] = d->dat[0xe] >> 4;
		c->volume[1] = d->dat[0xe] & 0xf;
		c->repeat = !(d->dat[0xf] & 0x80);
		apu_start(c, peek16(d->dat, 0x8), d->dat[0xf] & 0x7f);
	}
	return 1;
}

static int
nil_talk(Device *d, Uint8 b0, Uint8 w)
{
	(void)d;
	(void)b0;
	(void)w;
	return 1;
}

static int
update_datetime(lua_State *L)
{
	// DateTime
	int i;
	for(i = 0; i < 11; ++i)
		devdatetime->dat[i] = pd->lua->getArgInt(i + 1);
	return 0;
}

static int
update_controller(lua_State *L)
{
	// Buttons
	PDButtons state, pushed, released;
	pd->system->getButtonState(&state, &pushed, &released);
	if(pushed || released) {
		devctrl->dat[2] = 0x00;
		if(state & kButtonA)
			devctrl->dat[2] |= 0x01;
		if(state & kButtonB)
			devctrl->dat[2] |= 0x02;
		if(state & kButtonUp)
			devctrl->dat[2] |= 0x10;
		if(state & kButtonDown)
			devctrl->dat[2] |= 0x20;
		if(state & kButtonLeft)
			devctrl->dat[2] |= 0x40;
		if(state & kButtonRight)
			devctrl->dat[2] |= 0x80;
		uxn_eval(&u, peek16(devctrl->dat, 0));
	}

	// Crank
	unsigned char crank = pd->system->getCrankChange();
	if(crank) {
		devmouse->dat[7] = crank;
		uxn_eval(&u, peek16(devmouse->dat, 0));
		devmouse->dat[7] = 0x00;
	}

	return 0;
}

static int
update_mouse(lua_State *L)
{
	int i;
	for(i = 0; i < 5; ++i) {
		devmouse->dat[i + 2] = pd->lua->getArgInt(i + 1);
	}
	uxn_eval(&u, peek16(devmouse->dat, 0));
	return 0;
}

static int
update_screen(lua_State *L)
{
	uxn_eval(&u, peek16(devscreen->dat, 0));
	return 0;
}

static int
audio_callback(void *context, int16_t *left, int16_t *right, int len)
{
	return apu_render((Apu *)context, u.ram.dat, left, right, len);
}

static int
varvara_init_once(void)
{
	int i;
	SoundChannel *ch = pd->sound->getDefaultChannel();
	for(i = 0; i < POLYPHONY; ++i) {
		apu_sources[i] = pd->sound->channel->addCallbackSource(ch, audio_callback, &apu[i], 1);
	}
	pd->sound->channel->setVolume(ch, 1.0);
	pd->sound->channel->setPan(ch, 0.0);
	return 1;
}

static int
post_load_init(void)
{
	if(!ppu_init(&ppu, (uint32_t *)pd->graphics->getFrame()))
		return 0;
	render_all(&ppu);

	uxn_port(&u, 0x0, system_talk);
	devconsole = uxn_port(&u, 0x1, console_talk);
	devscreen = uxn_port(&u, 0x2, screen_talk);
	devaudio0 = uxn_port(&u, 0x3, audio_talk);
	uxn_port(&u, 0x4, audio_talk);
	uxn_port(&u, 0x5, audio_talk);
	uxn_port(&u, 0x6, audio_talk);
	uxn_port(&u, 0x7, nil_talk);
	devctrl = uxn_port(&u, 0x8, nil_talk);
	devmouse = uxn_port(&u, 0x9, nil_talk);
	uxn_port(&u, 0xa, file_talk);
	devdatetime = uxn_port(&u, 0xb, nil_talk);
	uxn_port(&u, 0xc, nil_talk);
	uxn_port(&u, 0xd, nil_talk);
	uxn_port(&u, 0xe, nil_talk);
	uxn_port(&u, 0xf, nil_talk);

	return 1;
}

static int
load(Uxn *u, char *filepath)
{
	SDFile *f = pd->file->open(filepath, kFileRead | kFileReadData);
	if(f == NULL) {
		pd->system->error("Failed to open rom.");
		return 0;
	}
	pd->file->read(f, u->ram.dat + PAGE_PROGRAM, sizeof(u->ram.dat) - PAGE_PROGRAM);
	pd->system->logToConsole("Loaded %s\n", filepath);
	return post_load_init();
}

static int
reset(Uxn *u, char *filepath)
{
	if(!uxn_boot(u)) {
		pd->system->error("Failed to start uxn.");
		return 0;
	}
	memset(&ppu, 0, sizeof(ppu));
	memset(&apu, 0, sizeof(apu));
	if(!load(u, filepath))
		return 0;

	poke16(devscreen->dat, 2, WIDTH_PIXELS);
	poke16(devscreen->dat, 4, HEIGHT);

	uxn_eval(u, PAGE_PROGRAM);
	return 1;
}

static int
menu_reset(lua_State *L)
{
	if(!reset(&u, BOOT_ROM_FILENAME))
		pd->system->error("Failed to reset Uxn.");
	return 0;
}

static int
freeze(void)
{
	SDFile *f = pd->file->open(SNAPSHOT_FILENAME, kFileWrite);
	if(f == NULL) {
		pd->system->error("Failed to open snapshot for writing.");
		return 0;
	}
	int version = SNAPSHOT_VERSION;
	/* clang-format off */
	if(pd->file->write(f, &version, sizeof(version)) != sizeof(version)
			|| pd->file->write(f, &u, sizeof(u)) != sizeof(u)
			|| pd->file->write(f, &ppu, sizeof(ppu)) != sizeof(ppu)
			|| pd->file->write(f, &apu, sizeof(apu)) != sizeof(apu)) {
		pd->system->error("Failed to write snapshot data.");
		pd->file->close(f);
		return 0;
	}
	pd->file->close(f);
	/* clang-format on */
	return 1;
}

static int
thaw(void)
{
	SDFile *f = pd->file->open(SNAPSHOT_FILENAME, kFileRead | kFileReadData);
	if(f == NULL)
		return 0;
	int version;
	/* clang-format off */
	if(pd->file->read(f, &version, sizeof(version)) != sizeof(version)
			|| pd->file->read(f, &u, sizeof(u)) != sizeof(u)
			|| pd->file->read(f, &ppu, sizeof(ppu)) != sizeof(ppu)
			|| pd->file->read(f, &apu, sizeof(apu)) != sizeof(apu)) {
		pd->file->close(f);
		return 0;
	}
	pd->file->close(f);
	/* clang-format on */
	if(version != SNAPSHOT_VERSION)
		return 0;
	return post_load_init();
}

struct lua_function_pair {
	lua_CFunction f;
	const char *name;
};

static struct lua_function_pair fns[] = {
	{update_datetime, "varvaraDateTime"},
	{update_controller, "varvaraController"},
	{update_mouse, "varvaraMouse"},
	{update_screen, "varvaraScreen"},
	{menu_reset, "varvaraReset"}};

int
eventHandler(PlaydateAPI *playdate, PDSystemEvent event, uint32_t arg)
{
	switch(event) {
	case kEventInit:
		pd = playdate;
		pd->display->setRefreshRate(60);

		if(!varvara_init_once())
			pd->system->error("Failed to initialize Varvara.");

		if(!thaw() && !reset(&u, BOOT_ROM_FILENAME))
			pd->system->error("Failed to reset Uxn.");

		break;

	case kEventInitLua: {
		const char *err;
		int i;
		for(i = 0; i < sizeof(fns) / sizeof(struct lua_function_pair); ++i) {
			if(!pd->lua->addFunction(fns[i].f, fns[i].name, &err))
				pd->system->error("Failed to create function: %s", err);
		}
		break;
	}

	case kEventTerminate:
	case kEventLowPower:
		freeze();
		break;

	default:
		break;
	}
	return 0;
}

M src/main.c => src/main.c +11 -14
@@ 8,19 8,6 @@ static PlaydateAPI *pd = NULL;
Uxn u;
Ppu ppu;

/*
static unsigned char rom[] = {
	0xa0, 0x01, 0x39, 0x94, 0x80, 0x18, 0x17, 0x21, 0x94, 0x80, 0xf7, 0x0d, 0x22, 0x80, 0x22, 0x36, 
	0x80, 0x01, 0x3f, 0xa0, 0x00, 0x08, 0x39, 0x80, 0x28, 0x37, 0x80, 0x24, 0x36, 0x80, 0x01, 0x3f, 
	0xa0, 0x00, 0x08, 0x39, 0x80, 0x2a, 0x37, 0x80, 0x16, 0x80, 0x26, 0x17, 0xa0, 0x01, 0x47, 0x80, 
	0x2c, 0x37, 0x80, 0x01, 0x80, 0x2f, 0x97, 0x17, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 
	0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x40, 0x48, 0x48, 0x00, 
	0xe0, 0x18, 0x04, 0x04, 0x02, 0x12, 0x12, 0x40, 0x40, 0x44, 0x23, 0x20, 0x18, 0x07, 0x00, 0x02, 
	0x02, 0x22, 0xc4, 0x04, 0x18, 0xe0, 0x00
};

*/

unsigned char rom[] = {
	0xa0, 0x4c, 0xfd, 0x80, 0x08, 0x37, 0xa0, 0x4c, 0xf3, 0x80, 0x0a, 0x37, 0xa0, 0xdc, 0xf2, 0x80, 
	0x0c, 0x37, 0xa0, 0x01, 0x4b, 0x80, 0x20, 0x37, 0x80, 0x22, 0x36, 0x80, 0x01, 0x3f, 0x80, 0x00, 


@@ 41,7 28,6 @@ unsigned char rom[] = {
	0x00, 0xf0, 0x00, 0x00, 0xe0, 0xfc, 0xfc, 0x80, 0x00, 
};


/* Varvara */

static const char *errors[] = {


@@ 64,6 50,17 @@ emu_error(char *msg, const char *err)
}

static int
load(Uxn *u, char *filepath)
{
	SDFile *f = pd->file->open(filepath, kFileRead | kFileReadData);
	if(f == NULL)
		return emu_error("Missing file", filepath);
	pd->file->read(f, u->ram + PAGE_PROGRAM, sizeof(u->ram) - PAGE_PROGRAM);
	pd->system->logToConsole("Loaded %s\n", filepath);
	return 1;
}

static int
emu_load(unsigned char *r)
{
	int i, len = sizeof(rom);