#include "ppu.h" #include "bd-font.c" /* Copyright (c) 2021 Devine Lu Linvega Copyright (c) 2021 Andrew Alderwick Copyright (c) 2021 Adrian "asie" Siekierka 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. */ #define FG_FRONT ((u32*)(MEM_VRAM)) #define BG_FRONT ((u32*)(MEM_VRAM + KB(20))) #define FG_BACK ((u32*)(MEM_VRAM + KB(44))) #define BG_BACK ((u32*)(MEM_VRAM + KB(64))) #define TILE_MAP ((u32*)(MEM_VRAM + KB(40))) #define FONT_DATA ((u32*)(MEM_VRAM + KB(84))) // Keyboard. #define SPRITE_START_IDX 640 static u32 lut_2bpp[256] = { 0x00000000, 0x00000001, 0x00000010, 0x00000011, 0x00000100, 0x00000101, 0x00000110, 0x00000111, 0x00001000, 0x00001001, 0x00001010, 0x00001011, 0x00001100, 0x00001101, 0x00001110, 0x00001111, 0x00010000, 0x00010001, 0x00010010, 0x00010011, 0x00010100, 0x00010101, 0x00010110, 0x00010111, 0x00011000, 0x00011001, 0x00011010, 0x00011011, 0x00011100, 0x00011101, 0x00011110, 0x00011111, 0x00100000, 0x00100001, 0x00100010, 0x00100011, 0x00100100, 0x00100101, 0x00100110, 0x00100111, 0x00101000, 0x00101001, 0x00101010, 0x00101011, 0x00101100, 0x00101101, 0x00101110, 0x00101111, 0x00110000, 0x00110001, 0x00110010, 0x00110011, 0x00110100, 0x00110101, 0x00110110, 0x00110111, 0x00111000, 0x00111001, 0x00111010, 0x00111011, 0x00111100, 0x00111101, 0x00111110, 0x00111111, 0x01000000, 0x01000001, 0x01000010, 0x01000011, 0x01000100, 0x01000101, 0x01000110, 0x01000111, 0x01001000, 0x01001001, 0x01001010, 0x01001011, 0x01001100, 0x01001101, 0x01001110, 0x01001111, 0x01010000, 0x01010001, 0x01010010, 0x01010011, 0x01010100, 0x01010101, 0x01010110, 0x01010111, 0x01011000, 0x01011001, 0x01011010, 0x01011011, 0x01011100, 0x01011101, 0x01011110, 0x01011111, 0x01100000, 0x01100001, 0x01100010, 0x01100011, 0x01100100, 0x01100101, 0x01100110, 0x01100111, 0x01101000, 0x01101001, 0x01101010, 0x01101011, 0x01101100, 0x01101101, 0x01101110, 0x01101111, 0x01110000, 0x01110001, 0x01110010, 0x01110011, 0x01110100, 0x01110101, 0x01110110, 0x01110111, 0x01111000, 0x01111001, 0x01111010, 0x01111011, 0x01111100, 0x01111101, 0x01111110, 0x01111111, 0x10000000, 0x10000001, 0x10000010, 0x10000011, 0x10000100, 0x10000101, 0x10000110, 0x10000111, 0x10001000, 0x10001001, 0x10001010, 0x10001011, 0x10001100, 0x10001101, 0x10001110, 0x10001111, 0x10010000, 0x10010001, 0x10010010, 0x10010011, 0x10010100, 0x10010101, 0x10010110, 0x10010111, 0x10011000, 0x10011001, 0x10011010, 0x10011011, 0x10011100, 0x10011101, 0x10011110, 0x10011111, 0x10100000, 0x10100001, 0x10100010, 0x10100011, 0x10100100, 0x10100101, 0x10100110, 0x10100111, 0x10101000, 0x10101001, 0x10101010, 0x10101011, 0x10101100, 0x10101101, 0x10101110, 0x10101111, 0x10110000, 0x10110001, 0x10110010, 0x10110011, 0x10110100, 0x10110101, 0x10110110, 0x10110111, 0x10111000, 0x10111001, 0x10111010, 0x10111011, 0x10111100, 0x10111101, 0x10111110, 0x10111111, 0x11000000, 0x11000001, 0x11000010, 0x11000011, 0x11000100, 0x11000101, 0x11000110, 0x11000111, 0x11001000, 0x11001001, 0x11001010, 0x11001011, 0x11001100, 0x11001101, 0x11001110, 0x11001111, 0x11010000, 0x11010001, 0x11010010, 0x11010011, 0x11010100, 0x11010101, 0x11010110, 0x11010111, 0x11011000, 0x11011001, 0x11011010, 0x11011011, 0x11011100, 0x11011101, 0x11011110, 0x11011111, 0x11100000, 0x11100001, 0x11100010, 0x11100011, 0x11100100, 0x11100101, 0x11100110, 0x11100111, 0x11101000, 0x11101001, 0x11101010, 0x11101011, 0x11101100, 0x11101101, 0x11101110, 0x11101111, 0x11110000, 0x11110001, 0x11110010, 0x11110011, 0x11110100, 0x11110101, 0x11110110, 0x11110111, 0x11111000, 0x11111001, 0x11111010, 0x11111011, 0x11111100, 0x11111101, 0x11111110, 0x11111111 }; static u32 lut2bpp_flipx[256] = { 0x00000000, 0x10000000, 0x01000000, 0x11000000, 0x00100000, 0x10100000, 0x01100000, 0x11100000, 0x00010000, 0x10010000, 0x01010000, 0x11010000, 0x00110000, 0x10110000, 0x01110000, 0x11110000, 0x00001000, 0x10001000, 0x01001000, 0x11001000, 0x00101000, 0x10101000, 0x01101000, 0x11101000, 0x00011000, 0x10011000, 0x01011000, 0x11011000, 0x00111000, 0x10111000, 0x01111000, 0x11111000, 0x00000100, 0x10000100, 0x01000100, 0x11000100, 0x00100100, 0x10100100, 0x01100100, 0x11100100, 0x00010100, 0x10010100, 0x01010100, 0x11010100, 0x00110100, 0x10110100, 0x01110100, 0x11110100, 0x00001100, 0x10001100, 0x01001100, 0x11001100, 0x00101100, 0x10101100, 0x01101100, 0x11101100, 0x00011100, 0x10011100, 0x01011100, 0x11011100, 0x00111100, 0x10111100, 0x01111100, 0x11111100, 0x00000010, 0x10000010, 0x01000010, 0x11000010, 0x00100010, 0x10100010, 0x01100010, 0x11100010, 0x00010010, 0x10010010, 0x01010010, 0x11010010, 0x00110010, 0x10110010, 0x01110010, 0x11110010, 0x00001010, 0x10001010, 0x01001010, 0x11001010, 0x00101010, 0x10101010, 0x01101010, 0x11101010, 0x00011010, 0x10011010, 0x01011010, 0x11011010, 0x00111010, 0x10111010, 0x01111010, 0x11111010, 0x00000110, 0x10000110, 0x01000110, 0x11000110, 0x00100110, 0x10100110, 0x01100110, 0x11100110, 0x00010110, 0x10010110, 0x01010110, 0x11010110, 0x00110110, 0x10110110, 0x01110110, 0x11110110, 0x00001110, 0x10001110, 0x01001110, 0x11001110, 0x00101110, 0x10101110, 0x01101110, 0x11101110, 0x00011110, 0x10011110, 0x01011110, 0x11011110, 0x00111110, 0x10111110, 0x01111110, 0x11111110, 0x00000001, 0x10000001, 0x01000001, 0x11000001, 0x00100001, 0x10100001, 0x01100001, 0x11100001, 0x00010001, 0x10010001, 0x01010001, 0x11010001, 0x00110001, 0x10110001, 0x01110001, 0x11110001, 0x00001001, 0x10001001, 0x01001001, 0x11001001, 0x00101001, 0x10101001, 0x01101001, 0x11101001, 0x00011001, 0x10011001, 0x01011001, 0x11011001, 0x00111001, 0x10111001, 0x01111001, 0x11111001, 0x00000101, 0x10000101, 0x01000101, 0x11000101, 0x00100101, 0x10100101, 0x01100101, 0x11100101, 0x00010101, 0x10010101, 0x01010101, 0x11010101, 0x00110101, 0x10110101, 0x01110101, 0x11110101, 0x00001101, 0x10001101, 0x01001101, 0x11001101, 0x00101101, 0x10101101, 0x01101101, 0x11101101, 0x00011101, 0x10011101, 0x01011101, 0x11011101, 0x00111101, 0x10111101, 0x01111101, 0x11111101, 0x00000011, 0x10000011, 0x01000011, 0x11000011, 0x00100011, 0x10100011, 0x01100011, 0x11100011, 0x00010011, 0x10010011, 0x01010011, 0x11010011, 0x00110011, 0x10110011, 0x01110011, 0x11110011, 0x00001011, 0x10001011, 0x01001011, 0x11001011, 0x00101011, 0x10101011, 0x01101011, 0x11101011, 0x00011011, 0x10011011, 0x01011011, 0x11011011, 0x00111011, 0x10111011, 0x01111011, 0x11111011, 0x00000111, 0x10000111, 0x01000111, 0x11000111, 0x00100111, 0x10100111, 0x01100111, 0x11100111, 0x00010111, 0x10010111, 0x01010111, 0x11010111, 0x00110111, 0x10110111, 0x01110111, 0x11110111, 0x00001111, 0x10001111, 0x01001111, 0x11001111, 0x00101111, 0x10101111, 0x01101111, 0x11101111, 0x00011111, 0x10011111, 0x01011111, 0x11011111, 0x00111111, 0x10111111, 0x01111111, 0x11111111 }; static u8 blending[5][16] = { {0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0}, {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}, {1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1}, {2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2}, {1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0} }; static u32 dirty_tiles[21] = {0}; void putcolors(u8 *addr) { int i; for(i = 0; i < 4; ++i) { u8 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; PAL_BUFFER_BG[i] = rgb15( (r << 1) | (r >> 3), (g << 1) | (g >> 3), (b << 1) | (b >> 3)); for (size_t j = 0; j < 16; ++j) { PAL_BUFFER_SPRITES[i * 16 + j] = rgb15( (r << 1) | (r >> 3), (g << 1) | (g >> 3), (b << 1) | (b >> 3)); } } } IWRAM_CODE void ppu_pixel(u32 *layer, u16 x, u16 y, u8 color) { if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return; size_t tile_x = x / 8; size_t tile_y = y / 8; size_t start_col = x % 8; size_t start_row = y % 8; size_t pos = (start_row + ((tile_x + tile_y * 32) * 8)); size_t shift = start_col * 4; layer[pos] = (layer[pos] & (~(0xF << shift))) | (color << shift); dirty_tiles[tile_y] |= 1 << tile_x; } IWRAM_CODE void ppu_1bpp(u32 *layer, u16 x, u16 y, u8 *sprite, u8 color, u8 flipx, u8 flipy) { u8 sprline; u16 v; u32 dirtyflag = (1 << (x >> 3)) | (1 << ((x + 7) >> 3)); u32 layerpos = ((y & 7) + (((x >> 3) + (y >> 3) * 32) * 8)); u32 *layerptr = &layer[layerpos]; u32 shift = (x & 7) << 2; u32 *lut_expand = flipx ? lut_2bpp : lut2bpp_flipx; if (flipy) flipy = 7; if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return; if (blending[4][color]) { u64 mask = ~((u64)0xFFFFFFFF << shift); for (v = 0; v < 8; v++, layerptr++) { if ((y + v) >= SCREEN_HEIGHT) break; sprline = sprite[v ^ flipy]; u64 data = (u64)(lut_expand[sprline] * (color & 3)) << shift; data |= (u64)(lut_expand[sprline ^ 0xFF] * (color >> 2)) << shift; layerptr[0] = (layerptr[0] & mask) | data; layerptr[8] = (layerptr[8] & (mask >> 32)) | (data >> 32); if (((y + v) & 7) == 7) layerptr += (32 - 1) * 8; } } else { for (v = 0; v < 8; v++, layerptr++) { if ((y + v) >= SCREEN_HEIGHT) break; sprline = sprite[v ^ flipy]; u64 mask = ~((u64)(lut_expand[sprline] * 0xF) << shift); u64 data = (u64)(lut_expand[sprline] * (color & 3)) << shift; layerptr[0] = (layerptr[0] & mask) | data; layerptr[8] = (layerptr[8] & (mask >> 32)) | (data >> 32); if (((y + v) & 7) == 7) layerptr += (32 - 1) * 8; } } dirty_tiles[y >> 3] |= dirtyflag; dirty_tiles[(y + 7) >> 3] |= dirtyflag; } IWRAM_CODE void ppu_2bpp(u32 *layer, u16 x, u16 y, u8 *sprite, u8 color, u8 flipx, u8 flipy) { u8 sprline1, sprline2; u8 xrightedge = x < ((32 - 1) * 8); u16 v, h; u32 dirtyflag = (1 << (x >> 3)) | (1 << ((x + 7) >> 3)); u32 layerpos = ((y & 7) + (((x >> 3) + (y >> 3) * 32) * 8)); u32 *layerptr = &layer[layerpos]; u32 shift = (x & 7) << 2; if (flipy) flipy = 7; if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return; if (color == 1) { u32 *lut_expand = flipx ? lut_2bpp : lut2bpp_flipx; u64 mask = ~((u64)0xFFFFFFFF << shift); for (v = 0; v < 8; v++, layerptr++) { if ((y + v) >= (24 * 8)) break; sprline1 = sprite[v ^ flipy]; sprline2 = sprite[(v ^ flipy) | 8]; u32 data32 = (lut_expand[sprline1]) | (lut_expand[sprline2] << 1); u64 data = ((u64) (data32 & 0x33333333)) << shift; layerptr[0] = (layerptr[0] & mask) | data; if (xrightedge) layerptr[8] = (layerptr[8] & (mask >> 32)) | (data >> 32); if (((y + v) & 7) == 7) layerptr += (32 - 1) * 8; } } else if (blending[4][color]) { u64 mask = ~((u64)0xFFFFFFFF << shift); for (v = 0; v < 8; v++, layerptr++) { if ((y + v) >= (24 * 8)) break; u8 ch1 = sprite[v ^ flipy]; u8 ch2 = sprite[(v ^ flipy) | 8]; u32 data32 = 0; if (!flipx) { for (h = 0; h < 8; h++) { data32 <<= 4; u8 ch = (ch1 & 1) | ((ch2 & 1) << 1); data32 |= blending[ch][color]; ch1 >>= 1; ch2 >>= 1; } } else { for (h = 0; h < 8; h++) { data32 <<= 4; u8 ch = (ch1 >> 7) | ((ch2 >> 7) << 1); data32 |= blending[ch][color]; ch1 <<= 1; ch2 <<= 1; } } u64 data = ((u64) (data32 & 0x33333333)) << shift; layerptr[0] = (layerptr[0] & mask) | data; if (xrightedge) layerptr[8] = (layerptr[8] & (mask >> 32)) | (data >> 32); if (((y + v) & 7) == 7) layerptr += (32 - 1) * 8; } } else { for (v = 0; v < 8; v++, layerptr++) { if ((y + v) >= (24 * 8)) break; u8 ch1 = sprite[v ^ flipy]; u8 ch2 = sprite[(v ^ flipy) | 8]; u32 data32 = 0; u32 mask32 = 0; if (!flipx) { for (h = 0; h < 8; h++) { data32 <<= 4; mask32 <<= 4; if ((ch1 | ch2) & 1) { u8 ch = (ch1 & 1) | ((ch2 & 1) << 1); data32 |= blending[ch][color]; mask32 |= 0xF; } ch1 >>= 1; ch2 >>= 1; } } else { for (h = 0; h < 8; h++) { data32 <<= 4; mask32 <<= 4; if ((ch1 | ch2) & 128) { u8 ch = (ch1 >> 7) | ((ch2 >> 7) << 1); data32 |= blending[ch][color]; mask32 |= 0xF; } ch1 <<= 1; ch2 <<= 1; } } u64 data = ((u64) (data32 & 0x33333333)) << shift; u64 mask = ~(((u64) (mask32 & 0x33333333)) << shift); layerptr[0] = (layerptr[0] & mask) | data; if (xrightedge) layerptr[8] = (layerptr[8] & (mask >> 32)) | (data >> 32); if (((y + v) & 7) == 7) layerptr += (32 - 1) * 8; } } dirty_tiles[y >> 3] |= dirtyflag; dirty_tiles[(y + 7) >> 3] |= dirtyflag; } IWRAM_CODE void putfontchar(u32 *layer, u16 tile_x, u16 tile_y, u8 ch, u8 color) { u32 pos = (tile_x + tile_y * 32) * 8; u32 *tile_data = &layer[pos]; u32 *font_data = &FONT_DATA[8 * ch]; for (size_t i = 0; i < 8; ++i) { tile_data[i] = font_data[i] * color; } dirty_tiles[tile_y] |= 1 << tile_x; } IWRAM_CODE void flipbuf(Ppu *p) { Tile *mem_fg = FG_FRONT; Tile *mem_bg = BG_FRONT; for (size_t j = 0; j < 20; ++j) { if (dirty_tiles[j] == 0) { continue; } size_t k = 1; for (size_t i = 0; i < 30; ++i, k <<= 1) { if (dirty_tiles[j] & k) { Tile *tile_fg = p->fg; Tile *tile_bg = p->bg; mem_fg[i + j * 32] = tile_fg[i + j * 32]; mem_bg[i + j * 32] = tile_bg[i + j * 32]; } } dirty_tiles[j] = 0; } } typedef struct KeyboardChar { int x; int y; u8 symbol; } KeyboardChar; static u8 cursor_position = 0; #define KEYBOARD_ROW_SIZE 12 #define KEYBOARD_START_TILE_X (30 / 2 - KEYBOARD_ROW_SIZE / 2) #define KEYBOARD_START_TILE_Y (20 / 2 - 3) KeyboardChar keyboard[] = { {0, 0, '!'}, {0, 0, '?'}, {0, 0, '@'}, {0, 0, '#'}, {0, 0, '$'}, {0, 0, '%'}, {0, 0, '^'}, {0, 0, '&'}, {0, 0, '*'}, {0, 0, '"'}, {0, 0, '\''}, {0, 0, 0x7f}, {0, 0, '('}, {0, 0, ')'}, {0, 0, '['}, {0, 0, ']'}, {0, 0, '{'}, {0, 0, '}'}, {0, 0, '<'}, {0, 0, '>'}, {0, 0, '+'}, {0, 0, '-'}, {0, 0, '='}, {0, 0, 0x14}, {0, 0, '0'}, {0, 0, '1'}, {0, 0, '2'}, {0, 0, '3'}, {0, 0, '4'}, {0, 0, '5'}, {0, 0, '6'}, {0, 0, '7'}, {0, 0, '8'}, {0, 0, '9'}, {0, 0, '~'}, {0, 0, 0x18}, {0, 0, 'a'}, {0, 0, 'b'}, {0, 0, 'c'}, {0, 0, 'd'}, {0, 0, 'e'}, {0, 0, 'f'}, {0, 0, 'g'}, {0, 0, 'h'}, {0, 0, 'i'}, {0, 0, 'j'}, {0, 0, '/'}, {0, 0, 0x19}, {0, 0, 'k'}, {0, 0, 'l'}, {0, 0, 'm'}, {0, 0, 'n'}, {0, 0, 'o'}, {0, 0, 'p'}, {0, 0, 'q'}, {0, 0, 'r'}, {0, 0, 's'}, {0, 0, 't'}, {0, 0, '\\'}, {0, 0, 0x1b}, {0, 0, 'u'}, {0, 0, 'v'}, {0, 0, 'w'}, {0, 0, 'x'}, {0, 0, 'y'}, {0, 0, 'z'}, {0, 0, ','}, {0, 0, '.'}, {0, 0, ';'}, {0, 0, ':'}, {0, 0, '_'}, {0, 0, 0x1a}, }; void toggle_keyboard(void) { for (size_t i = 0; i < LEN(keyboard); ++i) { OBJ_ATTR_0(i) ^= OBJ_HIDDEN; } OBJ_ATTR_0(127) ^= OBJ_HIDDEN; } void update_cursor(u8 pos) { cursor_position = CLAMP(pos, 0, LEN(keyboard) - 1); OBJ_ATTR_0(127) = (OBJ_ATTR_0(127) & ~0xFF) | OBJ_Y_COORD(keyboard[cursor_position].y); OBJ_ATTR_1(127) = (OBJ_ATTR_0(127) & ~0x1FF) | OBJ_X_COORD(keyboard[cursor_position].x); } int initppu(Ppu *p, u8 hor, u8 ver, u8 pad) { p->hor = hor; p->ver = ver; p->pad = pad; p->width = (8 * p->hor + p->pad * 2); p->height = (8 * p->ver + p->pad * 2); // Initialize display mode and bg palette. DISP_CTRL = DISP_MODE_0 | DISP_BG_0 | DISP_BG_1 | DISP_OBJ; // Initialize backgrounds. u8 cb_fg = 0; u8 cb_bg = 1; u8 sb_fg = 20; u8 sb_bg = 21; BG_CTRL(0) = BG_CHARBLOCK(cb_fg) | BG_SCREENBLOCK(sb_fg) | BG_PRIORITY(1); BG_CTRL(1) = BG_CHARBLOCK(cb_bg) | BG_SCREENBLOCK(sb_bg) | BG_PRIORITY(2); // Clear front buffer. p->fg = FG_FRONT; p->bg = BG_FRONT; // Use DMA to clear VRAM. u32 fill = 0; dma_fill(p->fg, fill, KB(20), 3); dma_fill(p->bg, fill, KB(20), 3); // Clear back buffer. p->fg = FG_BACK; p->bg = BG_BACK; dma_fill(p->fg, fill, KB(20), 3); dma_fill(p->bg, fill, KB(20), 3); // Initialize default palette. PAL_BUFFER_BG[0] = COLOR_BLACK; PAL_BUFFER_BG[1] = COLOR_WHITE; PAL_BUFFER_BG[2] = COLOR_RED; PAL_BUFFER_BG[3] = COLOR_BLUE; for (size_t i = 0; i < 16; ++i) { PAL_BUFFER_SPRITES[i] = COLOR_BLACK; PAL_BUFFER_SPRITES[1 * 16] = COLOR_WHITE; PAL_BUFFER_SPRITES[2 * 16] = COLOR_RED; PAL_BUFFER_SPRITES[3 * 16] = COLOR_BLUE; } // Initialize background memory map. u16 *mem_map_fg = SCREENBLOCK_MEM[sb_fg]; u16 *mem_map_bg = SCREENBLOCK_MEM[sb_bg]; size_t k = 0; for (size_t i = 0; i < 32 * 20; ++i, ++k) { mem_map_fg[i] = k; mem_map_bg[i] = k + 32 * 4; } // Load font data into VRAM. unpack_tiles(&bd_font, FONT_DATA, 256); // Initialize keyboard sprites. int tile_x = KEYBOARD_START_TILE_X; int tile_y = KEYBOARD_START_TILE_Y; for (size_t i = 0; i < sizeof(keyboard) / sizeof(keyboard[0]); ++i) { keyboard[i].x = tile_x * 8; keyboard[i].y = tile_y * 8; OBJ_ATTR_0(i) = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(keyboard[i].y) | OBJ_HIDDEN; OBJ_ATTR_1(i) = OBJ_SIZE_SMALL | OBJ_X_COORD(keyboard[i].x); OBJ_ATTR_2(i) = (SPRITE_START_IDX + keyboard[i].symbol) | OBJ_PAL_BANK(0); tile_x++; if (tile_x - KEYBOARD_START_TILE_X >= KEYBOARD_ROW_SIZE) { tile_x = KEYBOARD_START_TILE_X; tile_y++; } } OBJ_ATTR_0(127) = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(keyboard[cursor_position].y) | OBJ_HIDDEN; OBJ_ATTR_1(127) = OBJ_SIZE_SMALL | OBJ_X_COORD(keyboard[cursor_position].x); OBJ_ATTR_2(127) = (SPRITE_START_IDX + 0xdb) | OBJ_PAL_BANK(3); return 1; }