/* Copyright (c) 2021 Bad Diode 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 #include #include "common.h" #include "bitmap.h" #include "filesystem.c" #include "rom.c" #include "uxn.c" #include "ppu.c" #include "apu.c" #include "file.c" #include "text.h" // // Config parameters. // #if !defined(TEXT_MODE) || TEXT_MODE == 0 #define TEXT_LAYER ppu.fg #else #define TEXT_LAYER ppu.bg #endif #ifndef CONTROL_METHODS #define CONTROL_METHODS CONTROL_CONTROLLER,CONTROL_MOUSE,CONTROL_KEYBOARD #endif #ifdef PROF_ENABLE #if PROF_ENABLE == 0 #define TEXT_ENABLE 1 #define PROF(F,VAR) (profile_start(),(F),(VAR) = profile_stop()) #elif PROF_ENABLE == 1 #define TEXT_ENABLE 1 #define PROF(F,VAR) (profile_start(),(F),(VAR) = MAX(profile_stop(), (VAR))) #endif #ifndef PROF_SHOW_X #define PROF_SHOW_X 0 #endif #ifndef PROF_SHOW_Y #define PROF_SHOW_Y 0 #endif #define PROF_SHOW() \ do { \ txt_position((PROF_SHOW_X), (PROF_SHOW_Y));\ txt_printf("INPUT: %lu ", input_cycles);\ txt_position((PROF_SHOW_X), (PROF_SHOW_Y)+1);\ txt_printf("EVAL: %lu ", eval_cycles);\ txt_position((PROF_SHOW_X), (PROF_SHOW_Y)+2);\ txt_printf("FLIP: %lu ", flip_cycles);\ txt_position((PROF_SHOW_X), (PROF_SHOW_Y)+3);\ txt_printf("MIX: %lu ", mix_cycles);\ } while (0) #define PROF_INIT() \ u32 flip_cycles = 0;\ u32 eval_cycles = 0;\ u32 input_cycles = 0;\ u32 mix_cycles = 0; #else #define PROF(F,VAR) (F) #define PROF_SHOW() #define PROF_INIT() #endif static time_t seconds = 0; typedef enum { CONTROL_CONTROLLER, CONTROL_MOUSE, CONTROL_KEYBOARD, } ControlMethod; const ControlMethod ctrl_methods[] = { CONTROL_METHODS }; static ControlMethod ctrl_idx = 0; #define MOUSE_DELTA 1 typedef struct Mouse { int x; int y; } Mouse; EWRAM_BSS static u8 umem[0x10300]; static Uxn u; static Ppu ppu; static Mouse mouse = {SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2}; int uxn_halt(Uxn *u, u8 instr, u8 err, u16 addr) { (void)u; txt_printf("HALTED\n"); txt_printf("I: %lu\n", instr); txt_printf("E: %lu\n", err); txt_printf("A: %lu\n", addr); while (true); } IWRAM_CODE u8 screen_dei(u8 *d, u8 port) { switch(port) { case 0x2: return (SCREEN_WIDTH >> 8); case 0x3: return (SCREEN_WIDTH); case 0x4: return (SCREEN_HEIGHT >> 8); case 0x5: return (SCREEN_HEIGHT); default: return d[port]; } } IWRAM_CODE void screen_deo(u8 *ram, u8 *d, u8 port) { switch(port) { case 0xe: { u16 x, y; u8 layer = d[0xe] & 0x40; PEKDEV(x, 0x8); PEKDEV(y, 0xa); ppu_pixel(layer ? ppu.fg : ppu.bg, x, y, d[0xe] & 0x3); if(d[0x6] & 0x01) POKDEV(0x8, x + 1); /* auto x+1 */ if(d[0x6] & 0x02) POKDEV(0xa, y + 1); /* auto y+1 */ break; } case 0xf: { u16 x, y, dx, dy, addr; u8 n, twobpp = !!(d[0xf] & 0x80); PEKDEV(x, 0x8); PEKDEV(y, 0xa); PEKDEV(addr, 0xc); n = d[0x6] >> 4; dx = (d[0x6] & 0x01) << 3; dy = (d[0x6] & 0x02) << 2; if(addr > 0x10000 - ((n + 1) << (3 + twobpp))) { return; } u8 *layer = (d[0xf] & 0x40) ? ppu.fg : ppu.bg; u8 color = d[0xf] & 0xf; u8 flipx = d[0xf] & 0x10; u8 flipy = d[0xf] & 0x20; for(size_t i = 0; i <= n; i++) { u8 *sprite = &ram[addr]; if (twobpp) { ppu_2bpp(layer, x + dy * i, y + dx * i, sprite, color, flipx, flipy); } else { ppu_1bpp(layer, x + dy * i, y + dx * i, sprite, color, flipx, flipy); } addr += (d[0x6] & 0x04) << (1 + twobpp); } POKDEV(0xc, addr); /* auto addr+length */ POKDEV(0x8, x + dx); /* auto x+8 */ POKDEV(0xa, y + dy); /* auto y+8 */ break; } } } u8 audio_dei(int instance, u8 *d, u8 port) { AudioChannel *c = &channels[instance]; switch(port) { // case 0x4: return apu_get_vu(instance); case 0x2: { POKDEV(0x2, c->pos); c->pos <<= 12; // fixed point. break; } } return d[port]; } void audio_deo(int instance, u8 *d, u8 port, Uxn *u) { AudioChannel *c = &channels[instance]; if (port == 0xf) { u16 length = 0; u16 adsr = 0; u16 addr = 0; u8 pitch = d[0xf] & 0x7f; PEKDEV(adsr, 0x8); PEKDEV(length, 0xa); PEKDEV(addr, 0xc); u8 *data = &u->ram[addr]; u32 vol = MAX(d[0xe] >> 4, d[0xe] & 0xf) * 4 / 3; bool loop = !(d[0xf] & 0x80); update_channel(c, data, length, pitch, adsr, vol, loop); } } u8 datetime_dei(u8 *d, u8 port) { struct tm *t = gmtime(&seconds); switch(port) { case 0x0: return (t->tm_year + 1900) >> 8; case 0x1: return (t->tm_year + 1900); 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[port]; } } u8 file_dei(u8 id, u8 *d, u8 port) { UxnFile *c = &uxn_file[id]; u16 res; switch(port) { case 0xc: case 0xd: { res = file_read(c, &d[port], 1); POKDEV(0x2, res); break; } } return d[port]; } void file_deo(u8 id, u8 *ram, u8 *d, u8 port) { u16 a, b, res; UxnFile *f = &uxn_file[id]; switch(port) { case 0x5: { PEKDEV(a, 0x4); PEKDEV(b, 0xa); if(b > 0x10000 - a) { b = 0x10000 - a; } res = file_stat(f, &ram[a], b); POKDEV(0x2, res); } break; case 0x6: { // TODO: no file deletion for now // res = file_delete(); // POKDEV(0x2, res); } break; case 0x9: { PEKDEV(a, 0x8); res = file_init(f, &ram[a]); POKDEV(0x2, res); } break; case 0xd: { PEKDEV(a, 0xc); PEKDEV(b, 0xa); if(b > 0x10000 - a) { b = 0x10000 - a; } res = file_read(f, &ram[a], b); POKDEV(0x2, res); } break; case 0xf: { PEKDEV(a, 0xe); PEKDEV(b, 0xa); if(b > 0x10000 - a) { b = 0x10000 - a; } res = file_write(f, &ram[a], b, d[0x7]); POKDEV(0x2, res); } break; } } void console_deo(u8 *d, u8 port) { txt_printf("%c", d[port]); } void system_deo(Uxn *u, u8 *d, u8 port) { switch(port) { case 0x2: u->wst = (Stack *)(u->ram + (d[port] ? (d[port] * 0x100) : 0x10000)); break; case 0x3: u->rst = (Stack *)(u->ram + (d[port] ? (d[port] * 0x100) : 0x10100)); break; } } u8 emu_dei(Uxn *u, u8 addr) { u8 p = addr & 0x0f, d = addr & 0xf0; switch(d) { case 0x20: return screen_dei(&u->dev[d], p); case 0x30: return audio_dei(0, &u->dev[d], p); case 0x40: return audio_dei(1, &u->dev[d], p); case 0x50: return audio_dei(2, &u->dev[d], p); case 0x60: return audio_dei(3, &u->dev[d], p); case 0xa0: return file_dei(0, &u->dev[d], p); case 0xb0: return file_dei(1, &u->dev[d], p); case 0xc0: return datetime_dei(&u->dev[d], p); } return u->dev[addr]; return 0; } void emu_deo(Uxn *u, u8 addr, u8 v) { u8 p = addr & 0x0f, d = addr & 0xf0; u->dev[addr] = v; switch(d) { case 0x00: system_deo(u, &u->dev[d], p); if(p > 0x7 && p < 0xe) putcolors(&u->dev[0x8]); break; case 0x10: console_deo(&u->dev[d], p); break; case 0x20: screen_deo(u->ram, &u->dev[d], p); break; case 0x30: audio_deo(0, &u->dev[d], p, u); break; case 0x40: audio_deo(1, &u->dev[d], p, u); break; case 0x50: audio_deo(2, &u->dev[d], p, u); break; case 0x60: audio_deo(3, &u->dev[d], p, u); break; case 0xa0: file_deo(0, u->ram, &u->dev[d], p); break; case 0xb0: file_deo(1, u->ram, &u->dev[d], p); break; } } void init_uxn(Uxn *u) { // Initialize uxn. u32 fill = 0; dma_fill(umem, fill, 0x10300, 3); uxn_boot(u, umem, emu_dei, emu_deo); // Copy rom to VM. memcpy(u->ram + PAGE_PROGRAM, uxn_rom, sizeof(uxn_rom)); } IWRAM_CODE void handle_input(Uxn *u) { poll_keys(); if (key_tap(KEY_SELECT)) { // Reset control variables on method switch. switch (ctrl_methods[ctrl_idx]) { case CONTROL_CONTROLLER: { u8 *d = &u->dev[0x80]; d[2] = 0; uxn_eval(u, GETVEC(d)); d[3] = 0; } break; case CONTROL_MOUSE: { u8 *d = &u->dev[0x90]; d[6] = 0; d[7] = 0; POKDEV(0x2, -10); POKDEV(0x4, -10); uxn_eval(u, GETVEC(d)); } break; case CONTROL_KEYBOARD: { toggle_keyboard(); } break; } // Update ctrl_idx. ctrl_idx = (ctrl_idx + 1 > (int)LEN(ctrl_methods) - 1) ? 0 : ctrl_idx + 1; // Initialize controller variables here. if (ctrl_methods[ctrl_idx] == CONTROL_KEYBOARD) { toggle_keyboard(); } } if (ctrl_methods[ctrl_idx] == CONTROL_CONTROLLER) { u8 *d = &u->dev[0x80]; // TODO: We don't need ifs if we use KEY_INPUTS directly and maybe just // swap some things if needed. u8 *flag = &d[2]; if (key_tap(KEY_A)) { *flag |= 0x01; } else { *flag &= ~0x01; } if (key_tap(KEY_B)) { *flag |= 0x02; } else { *flag &= ~0x02; } if (key_tap(KEY_L)) { *flag |= 0x04; } else { *flag &= ~0x04; } if (key_tap(KEY_R)) { *flag |= 0x08; } else { *flag &= ~0x08; } if (key_tap(KEY_UP)) { *flag |= 0x10; } else { *flag &= ~0x10; } if (key_tap(KEY_DOWN)) { *flag |= 0x20; } else { *flag &= ~0x20; } if (key_tap(KEY_LEFT)) { *flag |= 0x40; } else { *flag &= ~0x40; } if (key_tap(KEY_RIGHT)) { *flag |= 0x80; } else { *flag &= ~0x80; } uxn_eval(u, GETVEC(d)); d[3] = 0; } else if (ctrl_methods[ctrl_idx] == CONTROL_MOUSE) { u8 *d = &u->dev[0x90]; // Detect "mouse key press". u8 flag = d[6]; if (key_tap(KEY_B)) { flag |= 0x01; } else if (key_released(KEY_B)) { flag &= ~0x01; } if (key_tap(KEY_A)) { flag |= 0x10; } else if (key_released(KEY_A)) { flag &= ~0x10; } // Handle chording. d[6] = flag; if(flag == 0x10 && (d[6] & 0x01)) { d[7] = 0x01; } if(flag == 0x01 && (d[6] & 0x10)) { d[7] = 0x10; } // Detect mouse movement. if (key_pressed(KEY_UP)) { mouse.y = CLAMP(mouse.y - MOUSE_DELTA, 0, SCREEN_HEIGHT - 8); } else if (key_pressed(KEY_DOWN)) { mouse.y = CLAMP(mouse.y + MOUSE_DELTA, 0, SCREEN_HEIGHT - 8); } if (key_pressed(KEY_LEFT)) { mouse.x = CLAMP(mouse.x - MOUSE_DELTA, 0, SCREEN_WIDTH - 8); } else if (key_pressed(KEY_RIGHT)) { mouse.x = CLAMP(mouse.x + MOUSE_DELTA, 0, SCREEN_WIDTH - 8); } // Eval mouse. POKDEV(0x2, mouse.x); POKDEV(0x4, mouse.y); uxn_eval(u, GETVEC(d)); } else if (ctrl_methods[ctrl_idx] == CONTROL_KEYBOARD) { u8 *d = &u->dev[0x80]; if (key_tap(KEY_LEFT)) { update_cursor(cursor_position - 1); } else if (key_tap(KEY_RIGHT)) { update_cursor(cursor_position + 1); } if (key_tap(KEY_UP) && cursor_position >= KEYBOARD_ROW_SIZE) { update_cursor(cursor_position - KEYBOARD_ROW_SIZE); } else if (key_tap(KEY_DOWN) && cursor_position < LEN(keyboard) - KEYBOARD_ROW_SIZE) { update_cursor(cursor_position + KEYBOARD_ROW_SIZE); } if (key_tap(KEY_B)) { u8 symbol = keyboard[cursor_position].symbol; switch (symbol) { case 0x7f: { // Backspace. d[3] = 0x08; } break; case 0x14: { // New line. d[3] = 0x0d; } break; case 0x18: { // Arrow up. d[2] = 0x10; } break; case 0x19: { // Arrow down. d[2] = 0x20; } break; case 0x1b: { // Arrow left. d[2] = 0x40; } break; case 0x1a: { // Arrow right. d[2] = 0x80; } break; default: { d[3] = symbol; } break; } uxn_eval(u, GETVEC(d)); d[3] = 0; } } } int main(void) { // Adjust system wait times. SYSTEM_WAIT = SYSTEM_WAIT_CARTRIDGE; // Initialize filesystem. fs_init(); // Register interrupts. irq_init(); irs_set(IRQ_VBLANK, sound_vsync); // Initialize PPU. initppu(&ppu, 30, 20); // Initialize text engine. #ifdef TEXT_ENABLE txt_init(1, TEXT_LAYER); txt_position(0,0); #endif // Initialize UXN. init_uxn(&u); // Enable sound. init_sound(); // Main loop. uxn_eval(&u, PAGE_PROGRAM); PROF_INIT(); u8 frame_counter = 0; while(true) { bios_vblank_wait(); PROF(handle_input(&u), input_cycles); PROF(uxn_eval(&u, GETVEC(&u.dev[0x20])), eval_cycles); PROF(sound_mix(), mix_cycles); PROF_SHOW(); PROF(flipbuf(&ppu), flip_cycles); frame_counter++; if (frame_counter == 60) { seconds++; frame_counter = 0; } } return 0; }