rockbox/apps/plugins/varvara/varvara.c

339 lines
8.4 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 rom loading
TODO other varvara devices
TODO clean up
*/
#include "uxn.h"
#include "devices/ppu.h"
#include "plugin.h"
#include "keymaps.h"
#include <stdio.h>
#include <stdint.h>
#include <time.h>
static Uxn u;
static Ppu ppu;
static Device *devctrl, *devsystem, *devconsole, *devscreen;
unsigned int palette[3];
static Uint8 framebuffer[LCD_HEIGHT * LCD_WIDTH * 4];
static fb_data *lcd_fb = NULL;
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 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;
}
} 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 0xe:
inspect(&d->u->wst, "Working-stack");
inspect(&d->u->rst, "Return-stack");
break;
case 0xf: return 0;
}
if(b0 > 0x7 && b0 < 0xe)
set_palette(&d->dat[0x8]);
}
return 1;
}
/* taken from uxncli */
static int
console_talk(Device *d, Uint8 b0, Uint8 w)
{
if(b0 == 0x1)
d->vector = peek16(d->dat, 0x0);
if(w && b0 > 0x7)
DEBUGF("%c",(char *)&d->dat[b0]);
return 1;
}
/* taken from uxnemu */
static int
screen_talk(Device *d, Uint8 b0, Uint8 w)
{
if(!w) switch(b0) {
case 0x2: d->dat[0x2] = ppu.width >> 8; break;
case 0x3: d->dat[0x3] = ppu.width; break;
case 0x4: d->dat[0x4] = ppu.height >> 8; break;
case 0x5: d->dat[0x5] = ppu.height; break;
}
else
switch(b0) {
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;
}
}
return 1;
}
static int
nil_talk(Device *d, Uint8 b0, Uint8 w)
{
(void)d;
(void)b0;
(void)w;
return 1;
}
static int
datetime_talk(Device *d, Uint8 b0, Uint8 w)
{
struct tm* t = rb->get_time();
t->tm_year += 1900;
poke16(d->dat, 0x0, t->tm_year);
d->dat[0x2] = t->tm_mon;
d->dat[0x3] = t->tm_mday;
d->dat[0x4] = t->tm_hour;
d->dat[0x5] = t->tm_min;
d->dat[0x6] = t->tm_sec;
d->dat[0x7] = t->tm_wday;
poke16(d->dat, 0x08, t->tm_yday);
d->dat[0xa] = t->tm_isdst;
(void)b0;
(void)w;
return 1;
}
// 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)
{
if(!parameter) {
rb->splash(HZ, "Please open a .rom file using this plugin!");
return PLUGIN_ERROR;
}
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_talk);
/* console */ devconsole = uxn_port(&u, 0x1, console_talk);
/* screen */ devscreen = uxn_port(&u, 0x2, screen_talk);
/* audio0 */ uxn_port(&u, 0x3, nil_talk);
/* audio1 */ uxn_port(&u, 0x4, nil_talk);
/* audio2 */ uxn_port(&u, 0x5, nil_talk);
/* audio3 */ uxn_port(&u, 0x6, nil_talk);
/* empty */ uxn_port(&u, 0x7, nil_talk);
/* control */ devctrl = uxn_port(&u, 0x8, nil_talk);
/* mouse */ uxn_port(&u, 0x9, nil_talk);
/* file */ uxn_port(&u, 0xa, nil_talk);
/* datetime */ uxn_port(&u, 0xb, datetime_talk);
/* empty */ uxn_port(&u, 0xc, nil_talk);
/* empty */ uxn_port(&u, 0xd, nil_talk);
/* empty */ uxn_port(&u, 0xe, nil_talk);
/* empty */ uxn_port(&u, 0xf, nil_talk);
uxn_eval(&u, 0x0100);
run();
return PLUGIN_OK;
}