/* 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 #include #include 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; }