424 lines
11 KiB
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[3];
|
|
static Uint8 framebuffer[LCD_HEIGHT * LCD_WIDTH * 4];
|
|
static fb_data *lcd_fb = NULL;
|
|
int 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;
|
|
}
|
|
}
|
|
|
|
/* 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");
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* 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",(char *)&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];
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
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)
|
|
{
|
|
(void)d;
|
|
(void)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 audio_callback(const void** start, size_t *size) {
|
|
int high = 32000;
|
|
int low = 0;
|
|
for (size_t i = 0; i < AUDIO_BUF_SIZE; i++) {
|
|
if (i & 1) {
|
|
audio_buf[i] = high;
|
|
} else {
|
|
audio_buf[i] = low;
|
|
}
|
|
}
|
|
|
|
*start = &audio_buf;
|
|
*size = 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;
|
|
}
|
|
|
|
static void run() {
|
|
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->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();
|
|
file_cleanup();
|
|
return PLUGIN_OK;
|
|
|
|
}
|