rockbox/apps/plugins/varvara/varvara.c

424 lines
11 KiB
C

/* Varvara plugin for rockbox. I have no idea what I'm doing.
copyright (c) 2021 Devine Lu Linvega
copyright (c) 2021 nihilazo
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.
TODO optimise support for greyscale/1-bit displays
TODO audio device
TODO clean up
*/
#define POLYPHONY 4
#define AUDIO_BUF_SIZE 1024
#include "uxn.h"
#include "devices/ppu.h"
#include "devices/file.h"
#include "devices/apu.h"
#include "plugin.h"
#include "keymaps.h"
#include <stdio.h>
#include <stdint.h>
#include <time.h>
static Uxn u;
static Ppu ppu;
static Apu apu[POLYPHONY];
static Device *devctrl, *devsystem, *devconsole, *devscreen, *devaudio0;
unsigned int palette[4];
static Uint8 framebuffer[LCD_HEIGHT * LCD_WIDTH * 4];
static fb_data *lcd_fb = NULL;
char audio_buf[AUDIO_BUF_SIZE];
static unsigned long starttick;
static long sleeptime;
static void
memzero8(void *src, uint64_t n)
{
uint8_t * ptr = src;
for (size_t i = 0; i < n; i++) {
ptr[i] = 0;
}
}
#if 0
/* taken from uxncli */
static void
inspect(Stack *s, char *name)
{
Uint8 x, y;
DEBUGF("\n%s\n", name);
for(y = 0; y < 0x04; ++y) {
for(x = 0; x < 0x08; ++x) {
Uint8 p = y * 0x08 + x;
DEBUGF(p == s->ptr ? "[%02x]" : " %02x ",
s->dat[p]);
}
DEBUGF("\n");
}
}
#endif
static void
set_palette(Uint8 *addr)
{
#if LCD_DEPTH > 4
int i;
for(i = 0; i < 4; ++i) {
Uint8
r = (*(addr + i / 2) >> (!(i % 2) << 2)) & 0x0f,
g = (*(addr + 2 + i / 2) >> (!(i % 2) << 2)) & 0x0f,
b = (*(addr + 4 + i / 2) >> (!(i % 2) << 2)) & 0x0f;
palette[i] = FB_RGBPACK(r*8,g*8,b*8);
}
#else
int i;
for(i = 0; i < 4; ++i) {
Uint8 sum = (*(addr + i / 2) >> (!(i % 2) << 2)) & 0x0f;
sum += (*(addr + 2 + i / 2) >> (!(i % 2) << 2)) & 0x0f;
sum += (*(addr + 4 + i / 2) >> (!(i % 2) << 2)) & 0x0f;
palette[i] = sum > 0x17;
}
#endif
}
/* taken from uxncli */
static void
system_deo(Device *d, Uint8 port)
{
switch(port) {
case 0x2: d->u->wst.ptr = d->dat[0x2]; break;
case 0x3: d->u->rst.ptr = d->dat[0x3]; break;
}
if(port > 0x7 && port < 0xe)
set_palette(&d->dat[0x8]);
}
static Uint8 system_dei(Device *d, Uint8 port) {
switch(port) {
case 0x2: return d->u->wst.ptr; break;
case 0x3: return d->u->rst.ptr; break;
default: return d->dat[port];
}
}
/* taken from uxncli */
static void
console_deo(Device *d, Uint8 port)
{
if(port == 0x1)
d->vector = peek16(d->dat, 0x0);
if(port > 0x7)
DEBUGF("%c", d->dat[port]);
}
/* taken from uxnemu */
static Uint8
screen_dei(Device *d, Uint8 port)
{
switch(port) {
case 0x2: return ppu.width >> 8; break;
case 0x3: return ppu.width; break;
case 0x4: return ppu.height >> 8; break;
case 0x5: return ppu.height; break;
default: return d->dat[port];
}
}
static void
screen_deo(Device *d, Uint8 port) {
switch(port) {
case 0x1: d->vector = peek16(d->dat, 0x0); break;
case 0x5:
break;
case 0xe: {
Uint16 x = peek16(d->dat, 0x8);
Uint16 y = peek16(d->dat, 0xa);
Uint8 layer = d->dat[0xe] & 0x40;
ppu_write(&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 */
break;
}
case 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 */
break;
}
}
}
static void
file_deo(Device *d, Uint8 port)
{
switch(port) {
case 0x1: d->vector = peek16(d->dat, 0x0); break;
case 0x9: poke16(d->dat, 0x2, file_init(&d->mem[peek16(d->dat, 0x8)])); break;
case 0xd: poke16(d->dat, 0x2, file_read(&d->mem[peek16(d->dat, 0xc)], peek16(d->dat, 0xa))); break;
case 0xf: poke16(d->dat, 0x2, file_write(&d->mem[peek16(d->dat, 0xe)], peek16(d->dat, 0xa), d->dat[0x7])); break;
case 0x5: poke16(d->dat, 0x2, file_stat(&d->mem[peek16(d->dat, 0x4)], peek16(d->dat, 0xa))); break;
case 0x6: poke16(d->dat, 0x2, file_delete()); break;
}
}
static void
nil_deo(Device *d, Uint8 port)
{
(void)d;
(void)port;
}
static Uint8
nil_dei(Device *d, Uint8 port)
{
return d->dat[port];
}
static Uint8
datetime_dei(Device *d, Uint8 port)
{
struct tm* t = rb->get_time();
t->tm_year += 1900;
switch(port) {
case 0x0: return t->tm_year >> 8;
case 0x1: return t->tm_year;
case 0x2: return t->tm_mon;
case 0x3: return t->tm_mday;
case 0x4: return t->tm_hour;
case 0x5: return t->tm_min;
case 0x6: return t->tm_sec;
case 0x7: return t->tm_wday;
case 0x8: return t->tm_yday >> 8;
case 0x9: return t->tm_yday;
case 0xa: return t->tm_isdst;
default: return d->dat[port];
}
}
void
apu_finished_handler(Apu *c)
{
DEBUGF("APU done\n");
(void) c;
}
void audio_callback(const void** start, size_t *size) {
*start = audio_buf;
*size = AUDIO_BUF_SIZE;
memzero8(audio_buf, AUDIO_BUF_SIZE);
apu_render(&apu[0], (Sint16 *)audio_buf, (Sint16 *)(audio_buf + AUDIO_BUF_SIZE));
}
static Uint8
audio_dei(Device *d, Uint8 port)
{
Apu *c = &apu[d - devaudio0];
switch(port) {
case 0x4: return apu_get_vu(c);
case 0x2: poke16(d->dat, 0x2, c->i); /* fall through */
default: return d->dat[port];
}
}
static void
audio_deo(Device *d, Uint8 port)
{
Apu *c = &apu[d - devaudio0];
if(port == 0xf) {
c->len = peek16(d->dat, 0xa);
c->addr = &d->mem[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);
}
}
// TODO mono and greyscale that don't suck
static void redraw(void)
{
#if LCD_DEPTH > 4
if (!lcd_fb)
{
struct viewport *vp_main = rb->lcd_set_viewport(NULL);
lcd_fb = vp_main->buffer->fb_ptr;
}
for(int i = 0 ; i < LCD_WIDTH*LCD_HEIGHT; i++ ) {
Uint32 row = i / 2;
Uint8 shift = !(i & 0x1) << 2;
Uint8 pix = framebuffer[row] >> shift;
if(pix & 0x0c)
pix = pix >> 2;
lcd_fb[i] = palette[pix & 0x3];
}
rb->lcd_update();
#else
rb->lcd_clear_display();
for(Uint16 x = 0; x < LCD_WIDTH; x++ ) {
for(Uint16 y = 0; y < LCD_HEIGHT; y++ ) {
if(palette[ppu_read(&ppu, x, y)]) {
rb->lcd_drawpixel(x, y);
}
}
}
rb->lcd_update();
#endif
ppu.reqdraw = 0;
}
int
uxn_halt(Uxn *u, Uint8 error, char *name, int id)
{
rb->splash(HZ, "Halted\n");
return 0;
(void) u; (void) error; (void) name; (void) id;
}
static void run(void) {
while(!devsystem->dat[0xf]) {
starttick = *rb->current_tick;
uxn_eval(&u, devscreen->vector);
if(ppu.reqdraw || devsystem->dat[0xe])
redraw();
int b = rb->button_get(false);
if(b != BUTTON_NONE) {
// TODO allow scrolls (that don't have a button release) to be mapped as buttons
if(b == VARVARA_MENU)
return;
if(b == VARVARA_A)
devctrl->dat[2] += 0x01;
if(b == (VARVARA_A | BUTTON_REL))
devctrl->dat[2] -= 0x01;
if(b == VARVARA_B)
devctrl->dat[2] += 0x02;
if(b == (VARVARA_B | BUTTON_REL))
devctrl->dat[2] -= 0x02;
if(b == VARVARA_START)
devctrl->dat[2] += 0x04;
if(b == (VARVARA_START | BUTTON_REL))
devctrl->dat[2] -= 0x04;
if(b == VARVARA_SELECT)
devctrl->dat[2] += 0x08;
if(b == (VARVARA_SELECT | BUTTON_REL))
devctrl->dat[2] -= 0x08;
if(b == VARVARA_UP)
devctrl->dat[2] += 0x10;
if(b == (VARVARA_UP | BUTTON_REL))
devctrl->dat[2] -= 0x10;
if(b == VARVARA_DOWN)
devctrl->dat[2] += 0x20;
if(b == (VARVARA_DOWN | BUTTON_REL))
devctrl->dat[2] -= 0x20;
if(b == VARVARA_LEFT)
devctrl->dat[2] += 0x40;
if(b == (VARVARA_LEFT | BUTTON_REL))
devctrl->dat[2] -= 0x40;
if(b == VARVARA_RIGHT)
devctrl->dat[2] += 0x80;
if(b == (VARVARA_RIGHT | BUTTON_REL))
devctrl->dat[2] -= 0x80;
uxn_eval(&u, peek16(devctrl->dat, 0));
}
sleeptime = (HZ/60)-(*rb->current_tick-starttick);
if(sleeptime > 0) {
rb->sleep(sleeptime);
}
}
}
/* this is the plugin entry point */
enum plugin_status plugin_start(const void* parameter)
{
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(true);
#endif
if(!parameter) {
rb->splash(HZ, "Please open a .rom file using this plugin!");
return PLUGIN_ERROR;
}
/* audio init */
#if INPUT_SRC_CAPS != 0
/* Select playback */
rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
#endif
rb->pcmbuf_fade(false, true);
rb->pcm_play_stop();
rb->mixer_set_frequency(44100);
rb->mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, audio_callback, NULL, 0);
/* uxn init */
DEBUGF("zeroing\n");
memzero8(&u, sizeof(Uxn));
DEBUGF("loading ROM\n");
char* filename = (char*) parameter;
int fd = rb->open(filename, O_RDONLY);
if (fd < 0) {
rb->splash(HZ, "File Error");
return PLUGIN_ERROR;
}
int numread = rb->read(fd, u.ram.dat + PAGE_PROGRAM, 65536-PAGE_PROGRAM);
if (numread==-1) {
rb->splash(HZ, "I/O Error");
return PLUGIN_ERROR;
}
rb->close(fd);
ppu_init(&ppu, LCD_WIDTH, LCD_HEIGHT, framebuffer);
// Register ports
/* system */ devsystem = uxn_port(&u, 0x0, system_dei, system_deo);
/* console */ devconsole = uxn_port(&u, 0x1, nil_dei, console_deo);
/* screen */ devscreen = uxn_port(&u, 0x2, screen_dei, screen_deo);
/* audio0 */ devaudio0 = uxn_port(&u, 0x3, audio_dei, audio_deo);
/* audio1 */ uxn_port(&u, 0x4, nil_dei, nil_deo);
/* audio2 */ uxn_port(&u, 0x5, nil_dei, nil_deo);
/* audio3 */ uxn_port(&u, 0x6, nil_dei, nil_deo);
/* empty */ uxn_port(&u, 0x7, nil_dei, nil_deo);
/* control */ devctrl = uxn_port(&u, 0x8, nil_dei, nil_deo);
/* mouse */ uxn_port(&u, 0x9, nil_dei, nil_deo);
/* file */ uxn_port(&u, 0xa, nil_dei, file_deo);
/* datetime */ uxn_port(&u, 0xb, datetime_dei, nil_deo);
/* empty */ uxn_port(&u, 0xc, nil_dei, nil_deo);
/* empty */ uxn_port(&u, 0xd, nil_dei, nil_deo);
/* empty */ uxn_port(&u, 0xe, nil_dei, nil_deo);
/* empty */ uxn_port(&u, 0xf, nil_dei, nil_deo);
uxn_eval(&u, 0x0100);
run();
rb->pcm_play_stop();
rb->pcm_set_frequency(HW_SAMPR_DEFAULT);
file_cleanup();
return PLUGIN_OK;
}