From 815aa5c2a31ce49523d3174d41bb53bd3655e4e8 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 27 Mar 2020 22:51:32 +0200 Subject: [PATCH] Add video testing program for fun --- Makefile | 19 +++- core/bin/mouse.c | 129 +++++++++++++++++++++++ core/bin/video.c | 202 +++++++++++++++++++++++++++++++----- vsh/font.S | 8 ++ vsh/font.psfu | Bin 0 -> 4969 bytes vsh/input.c | 127 +++++++++++++++++++++++ vsh/input.h | 25 +++++ vsh/logo.c | 261 +++++++++++++++++++++++++++++++++++++++++++++++ vsh/logo.h | 13 +++ vsh/psf.h | 15 +++ vsh/video.c | 130 +++++++++++++++++++++++ vsh/video.h | 11 ++ vsh/vsh.c | 165 ++++++++++++++++++++++++++++++ 13 files changed, 1076 insertions(+), 29 deletions(-) create mode 100644 core/bin/mouse.c create mode 100644 vsh/font.S create mode 100644 vsh/font.psfu create mode 100644 vsh/input.c create mode 100644 vsh/input.h create mode 100644 vsh/logo.c create mode 100644 vsh/logo.h create mode 100644 vsh/psf.h create mode 100644 vsh/video.c create mode 100644 vsh/video.h create mode 100644 vsh/vsh.c diff --git a/Makefile b/Makefile index e5d0784..3e31876 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,8 @@ CC=x86_64-elf-yggdrasil-gcc DIRS=$(O) \ $(STAGE) \ $(O)/sh \ - $(O)/ase + $(O)/ase \ + $(O)/vsh HDRS=$(shell find $(S) -type f -name "*.h") STAGE_BIN=$(STAGE)/init \ $(STAGE)/bin/hexd \ @@ -24,7 +25,8 @@ STAGE_BIN=$(STAGE)/init \ $(STAGE)/bin/netctl \ $(STAGE)/bin/netmeow \ $(STAGE)/bin/netdump \ - $(STAGE)/bin/video + $(STAGE)/bin/mouse \ + $(STAGE)/bin/vsh # $(STAGE)/bin/com \ # $(STAGE)/bin/ase \ @@ -35,6 +37,11 @@ sh_OBJS=$(O)/sh/sh.o \ $(O)/sh/builtin.o \ $(O)/sh/cmd.o ase_OBJS=$(O)/ase/ase.o +vsh_OBJS=$(O)/vsh/vsh.o \ + $(O)/vsh/input.o \ + $(O)/vsh/video.o \ + $(O)/vsh/font.o \ + $(O)/vsh/logo.o usr_CFLAGS=-ggdb \ -msse \ @@ -75,9 +82,17 @@ $(STAGE)/bin/%: core/bin/%.c $(STAGE)/bin/sh: $(sh_OBJS) $(CC) -o $@ $(usr_LDFLAGS) $(sh_OBJS) +$(STAGE)/bin/vsh: $(vsh_OBJS) + $(CC) -o $@ $(usr_LDFLAGS) $(vsh_OBJS) + $(STAGE)/bin/ase: $(ase_OBJS) $(CC) -o $@ $(usr_LDFLAGS) $(ase_OBJS) +$(O)/vsh/%.o: vsh/%.c $(shell find vsh -name "*.h") + $(CC) -c -o $@ $(usr_CFLAGS) $< +$(O)/vsh/%.o: vsh/%.S + $(CC) -c -o $@ $(usr_CFLAGS) $< + $(O)/sh/%.o: sh/%.c $(shell find sh -name "*.h") $(CC) -c -o $@ $(usr_CFLAGS) $< diff --git a/core/bin/mouse.c b/core/bin/mouse.c new file mode 100644 index 0000000..d85b556 --- /dev/null +++ b/core/bin/mouse.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static int running = 1; + +static int mx = 40; +static int my = 12; +static double mx_d = 40, my_d = 12; + +static int con_width, con_height; + +static void signal_handle(int signum) { + running = 0; +} + +static void kb_handle(char *buf, size_t len) { + if (buf[0] == 'q') { + running = 0; + } +} + +static void ms_handle(char *buf, size_t len) { + for (size_t i = 0; i < len / 5; ++i) { + char type = buf[i * 5 + 0]; + + switch (type) { + case 'd': { + int16_t dx = *(int16_t *) &buf[i * 5 + 1]; + int16_t dy = *(int16_t *) &buf[i * 5 + 3]; + + mx_d += (double) dx / 4.0; + my_d -= (double) dy / 12.0; + + if (mx_d < 0) { + mx_d = 0; + } + if (mx_d >= con_width - 1) { + mx_d = con_width - 2; + } + + if (my_d < 0) { + my_d = 0; + } + if (my_d >= con_height - 1) { + my_d = con_height - 2; + } + + mx = mx_d; + my = my_d; + puts2("\033[2J\033[1;1f"); + printf("\033[%d;%dfX\033[%d;%df", my + 1, mx + 1, my + 1, mx + 1); + } + break; + } + } +} + +int main(int argc, char **argv) { + fd_set fds; + struct timeval tv; + struct termios old_ts, ts; + struct winsize winsz; + int kb_fd = STDIN_FILENO, ms_fd; + char buf[512]; + ssize_t len; + + signal(SIGINT, signal_handle); + + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &winsz) != 0) { + perror("ioctl()"); + return -1; + } + printf("Terminal is %dx%d\n", winsz.ws_col, winsz.ws_row); + con_width = winsz.ws_col; + con_height = winsz.ws_row; + if (tcgetattr(kb_fd, &old_ts) != 0) { + perror("tcgetattr()"); + return -1; + } + memcpy(&ts, &old_ts, sizeof(struct termios)); + ts.c_iflag &= ~ICANON; + ts.c_lflag = 0; + if (tcsetattr(kb_fd, TCSANOW, &ts) != 0) { + perror("tcsetattr()"); + return -1; + } + + ms_fd = open("/dev/ps2aux", O_RDONLY, 0); + + while (running) { + FD_ZERO(&fds); + FD_SET(ms_fd, &fds); + FD_SET(kb_fd, &fds); + tv.tv_sec = 1; + tv.tv_usec = 0; + + int res = select(ms_fd + 1, &fds, NULL, NULL, &tv); + + if (res < 0) { + perror("select()"); + break; + } + + if (res != 0) { + if (FD_ISSET(ms_fd, &fds)) { + len = read(ms_fd, buf, sizeof(buf)); + ms_handle(buf, len); + } + if (FD_ISSET(kb_fd, &fds)) { + len = read(kb_fd, buf, sizeof(buf)); + kb_handle(buf, len); + } + } + } + + if (tcsetattr(kb_fd, TCSANOW, &old_ts) != 0) { + perror("tcsetattr()"); + return -1; + } + printf("Stopped\n"); + + return 0; +} diff --git a/core/bin/video.c b/core/bin/video.c index ee25cda..0def8b3 100644 --- a/core/bin/video.c +++ b/core/bin/video.c @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #include @@ -8,16 +10,119 @@ #include #include +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + static int running = 1; +static double mx_d = 40, my_d = 12; + +static uint8_t cursor[8] = { + 0b11111111, + 0b11111110, + 0b11111100, + 0b11111000, + 0b11110000, + 0b11100000, + 0b11000000, + 0b10000000, +}; + +static uint32_t *vmem; +static size_t vsize; static void signal_handler(int signum) { running = 0; } +static void ms_handle(char *buf, size_t len) { + for (size_t i = 0; i < len / 5; ++i) { + char type = buf[i * 5 + 0]; + + switch (type) { + case 'd': { + int16_t dx = *(int16_t *) &buf[i * 5 + 1]; + int16_t dy = *(int16_t *) &buf[i * 5 + 3]; + + mx_d += (double) dx / 4.0; + my_d -= (double) dy / 6.0; + + if (mx_d < 0) { + mx_d = 0; + } + if (mx_d >= mode.width - 1) { + mx_d = mode.width - 2; + } + + if (my_d < 0) { + my_d = 0; + } + if (my_d >= mode.height - 1) { + my_d = mode.height - 2; + } + } + break; + } + } +} + +static void rect(int x0, int y0, int x1, int y1, uint32_t col) { + for (int x = MAX(x0, 0); x <= MIN(x1, mode.width - 1); ++x) { + for (int y = MAX(y0, 0); y <= MIN(y1, mode.height - 1); ++y) { + vmem[y * mode.width + x] = col; + } + } +} + +static void draw_cursor(int x, int y, uint32_t col) { + for (int i = 0; i < 8; ++i) { + if (i + x >= mode.width) { + break; + } + for (int j = 0; j < 8; ++j) { + if (j + y >= mode.height) { + break; + } + + if (!(cursor[j] & (1 << (7 - i)))) { + continue; + } + + vmem[(j + y) * mode.width + x + i] = col; + } + } +} + +static void render(void) { + static double px, py; + + rect(px, py, px + 8, py + 8, 0); + + px = mx_d; + py = my_d; + + draw_cursor(px, py, 0xFF0000); +} + int main(int argc, char **argv) { - struct ioc_vmode mode; int fd = open("/dev/fb0", O_RDONLY, 0); - if (fd < 0) { + int mouse_fd = open("/dev/ps2aux", O_RDONLY, 0); + struct termios tc, tc_old; + struct timeval tv; + fd_set fds; + char buf[512]; + ssize_t len; + int no = 0; + + tc.c_iflag = 0; + tc.c_lflag = 0; + tc.c_oflag = 0; + + if (tcgetattr(STDIN_FILENO, &tc_old) != 0) { + perror("tcgetattr()"); + goto end; + } + + if (fd < 0 || mouse_fd < 0) { perror("open()"); return -1; } @@ -27,55 +132,98 @@ int main(int argc, char **argv) { goto end; } + if (tcsetattr(STDIN_FILENO, TCSANOW, &tc) != 0) { + perror("tcsetattr()"); + goto end; + } + signal(SIGINT, signal_handler); printf("Video mode: %dx%d\n", mode.width, mode.height); - size_t size = mode.width * mode.height * 4; - size = (size + 0xFFF) & ~0xFFF; - printf("%u bytes\n", size); + vsize = mode.width * mode.height * 4; + vsize = (vsize + 0xFFF) & ~0xFFF; + printf("%u bytes\n", vsize); printf("You can exit by pressing ^C\n"); - printf("Starting video in 3 secs\n"); - usleep(3000000); + printf("Starting video in 1 sec\n"); + usleep(1000000); + + if (!running) { + // Interrupt happened + close(mouse_fd); + close(fd); + + if (tcsetattr(STDIN_FILENO, TCSANOW, &tc_old) != 0) { + perror("tcsetattr()"); + goto end; + } + + puts2("\033[2J\033[1;1f"); + printf("Goodbye\n"); + + return 0; + } - int no = 0; if (ioctl(fd, IOC_FBCON, &no) != 0) { perror("ioctl()"); goto end; } - uint32_t *data = mmap(NULL, size, 0, MAP_PRIVATE, fd, 0); - assert(data); + vmem = mmap(NULL, vsize, 0, MAP_PRIVATE, fd, 0); + assert(vmem); - struct timeval tv; - int c = 0; - int s = 0; + memset(vmem, 0, vsize); while (running) { - usleep(10000); - gettimeofday(&tv, NULL); - if (s == 0) { - c += 2; - } else { - c -= 2; + tv.tv_sec = 0; + tv.tv_usec = 10000; + FD_ZERO(&fds); + FD_SET(mouse_fd, &fds); + FD_SET(STDIN_FILENO, &fds); + + int res = select(mouse_fd + 1, &fds, NULL, NULL, &tv); + + if (res < 0) { + no = 1; + if (ioctl(fd, IOC_FBCON, &no) != 0) { + perror("ioctl()"); + goto end; + } + + printf("select() failed\n"); + + break; } - if (c < 0) { - s = 0; - c = 0; - } else if (c >= 256) { - s = 1; - c = 255; + + if (res > 0) { + if (FD_ISSET(mouse_fd, &fds)) { + len = read(mouse_fd, buf, sizeof(buf)); + ms_handle(buf, len); + } + if (FD_ISSET(STDIN_FILENO, &fds)) { + read(STDIN_FILENO, buf, sizeof(buf)); + + if (buf[0] == 'q') { + break; + } + } } - memset(data, c, size); + + render(); } - munmap(data, size); + munmap(vmem, vsize); no = 1; if (ioctl(fd, IOC_FBCON, &no) != 0) { perror("ioctl()"); goto end; } + if (tcsetattr(STDIN_FILENO, TCSANOW, &tc_old) != 0) { + perror("tcsetattr()"); + goto end; + } + puts2("\033[2J\033[1;1f"); printf("Goodbye\n"); end: diff --git a/vsh/font.S b/vsh/font.S new file mode 100644 index 0000000..2296400 --- /dev/null +++ b/vsh/font.S @@ -0,0 +1,8 @@ +.section .text +.global font_start +.global font_end + +.align 4 +font_start: + .incbin "vsh/font.psfu" +font_end: diff --git a/vsh/font.psfu b/vsh/font.psfu new file mode 100644 index 0000000000000000000000000000000000000000..e789b77189b9c3e585075eeed5a5eba693f3c435 GIT binary patch literal 4969 zcmZu!TWlO>6&`}L;$h{6w-K?N0^xq?(t@+JgAJkF3KS@m`(;VWHo(xD0*;}>W}P~& zLoL*ccN5dnI7yQ<7bp!)5NV<;Gt!6$Yx9OyT4P1?V80+_sZ!#HXg|L5UuI`qj{VQf z`Oo*C|J?pFvyYs=XWAj%%JBwy;AJy!GTtQo>!>z)d~)*g{N!X!i)!cs(JEkg$MJil zpe!mBZ7v5{*7dyA)m7K^jM(*RT-@h5O^Bd}QQ9 z18=!~Vv#hVHF#B$=W+whkKQp_B8{zG)BN6Ddv}o@TwQ7D3D@m#b{R>%H~0 zYpuExZz>E7jr2x_1`3-D+@or@W({^^I}-0&zPH=$KDtFn@j+ZE`*G}-i*cX<>M9}A zEW@GonRsfRQ^tOIBK0?9xQe}^kt!XfO0QDND0f<|meHa`7h)B;MLkn;i;CR7hgA=riMGNpI(9Fsg@$AnIBBh>a_bK#HWkD^lW!-8 z<1T4UbKP$rrprp*JM{8{=iFA~Nw<7)Sgq(z{fl?6MhxS@%U)t@`U)-mC81J+Zs8vufgH_)29h zvwk^_t9NQ%c2e7!{iOI5d0IZCs-b`0SGd*01UFsL^0kVhbU*1N-AKsjU%B2}Zuk9B zu@=_&tNarc#y%te*K$xC5DiK$KAQa~uat|0bUnKBIeSI5BBNiK5=CrL+8)JsyRiL3 ziCnL0?WLSplJOtWv?T8zk?xI-r;kgiWaJJz*jRZm?N^>YKE3MaoRVor~7C{OLVitgn-ynDL2{-3kKtaqyVC7~1fr`qJi7(~j0$&V@s)bX( zKQ8)Bd|8@h6;R_}q2LVgIFQ)pxtMre00ytY{df_w0`}x?mrt6OM7@nSr*w+@{%CoS zmVmX-9_gj=YQH|zdRBn-Z*!@1Q&KWKXm)cPVx(*}>xlDJe{lPx{i6d$yDkEwmeZi%EK!<%=Fopq#~vb5DQ29e^;d>+5mP4ULQ?baW4 z-(DI`can$EQeJ+;N%bonoEsa~r+6bz@d|gNk5~4gug~a7@pk=2|8n~c52yAj9Q{-4 z>?Px`nHt@&9+ubksN-t4l>5efrSVfgf-)DQKb=|IK(}ll1GJXv`}k6)(^=Br=Z^fg z>MXJav9SyB+l+@%NH`(+{1?(30qkl0_Y-GpRM%16MM2cdmmRic7K|@xR}EWZ3s=A3 z3~$;q+k_%vW7+P%njhll?1-q;{YfTkeA(Bul-D;F$iG&z+YOc~(%vhjL4&aUHMT2w zSoFV{Fh4JBO_ziR^`9N7mVR!_9&D2Q*QENPdG6or%RHTN z?b9vl^X@;s?`eNJ5*nKdg2pPX;Q<%dU9|I$#jDYFCn~u@FZo#OuxwS6vs0h zPjftpWae-(OO4ZWgq-8Jgj>Ufq+U9|WFt($NkK#_;1rHy?2uAU7 zd;&Z0N!*Q3;nVmGK8w%c9(*2Oz!z~ZzJxF1E4U9|#n*5@zK(C;oA?&Kjql*Q_#VEG zAE1ICVkdUt0sIJ6Jc!-+F@A!d!pG0>5FW-DevU`*3;Ytl!Z`L|0>8#@@F)WO7BwWZ z`w%^j=n|q+h>jw90nq}Y(2!XgILk2pjqD7{%D_#bt?PtX7W literal 0 HcmV?d00001 diff --git a/vsh/input.c b/vsh/input.c new file mode 100644 index 0000000..25999af --- /dev/null +++ b/vsh/input.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "video.h" +#include "input.h" + +static fd_set input_fds; +static struct termios tc_old, tc; +static struct timeval input_tv; +static int keyboard_fd = STDIN_FILENO; +static int mouse_fd; +int cursor_x = 0, cursor_y = 0; + +void cursor_move(int16_t dx, int16_t dy) { + cursor_x += dx; + cursor_y -= dy; + + if (cursor_x < 0) { + cursor_x = 0; + } else if (cursor_x >= fb_mode.width) { + cursor_x = fb_mode.width - 1; + } + if (cursor_y < 0) { + cursor_y = 0; + } else if (cursor_y >= fb_mode.height) { + cursor_y = fb_mode.height - 1; + } +} + +int input_wait_event(struct input_event *ev) { + int res; + ssize_t len; + char buf[128]; + + FD_ZERO(&input_fds); + FD_SET(keyboard_fd, &input_fds); + FD_SET(mouse_fd, &input_fds); + input_tv.tv_sec = 0; + input_tv.tv_usec = 10000; + + res = select(mouse_fd + 1, &input_fds, NULL, NULL, &input_tv); + + if (res < 0) { + return -1; + } + + if (res > 0) { + if (FD_ISSET(keyboard_fd, &input_fds)) { + // Read a single key event + len = read(keyboard_fd, buf, 1); + + if (len < 0) { + return -1; + } + + ev->type = IN_KEY_DOWN; + ev->key = buf[0]; + + return 1; + } + if (FD_ISSET(mouse_fd, &input_fds)) { + len = read(mouse_fd, buf, 5); + + if (len != 5) { + return 0; + } + + char type = buf[0]; + + switch (type) { + case 'd': + ev->type = IN_MOUSE_MOVE; + ev->mouse_move.dx = *(int16_t *) &buf[1]; + ev->mouse_move.dy = *(int16_t *) &buf[3]; + cursor_move(ev->mouse_move.dx, ev->mouse_move.dy); + return 1; + case 'b': + ev->type = buf[3] ? IN_BUTTON_DOWN : IN_BUTTON_UP; + ev->button = buf[1]; + return 1; + default: + // Skip unknown events + return 0; + } + } + } + + return 0; +} + +int input_init(void) { + if (tcgetattr(keyboard_fd, &tc_old)) { + perror("tcgetattr()"); + return -1; + } + memcpy(&tc, &tc_old, sizeof(struct termios)); + // No canonical mode + tc.c_iflag &= ~ICANON; + // No echo + tc.c_lflag = 0; + + if (tcsetattr(keyboard_fd, TCSANOW, &tc) != 0) { + perror("tcsetattr()"); + return -1; + } + + mouse_fd = open("/dev/ps2aux", O_RDONLY, 0); + + if (mouse_fd < 0) { + perror("open(/dev/ps2aux)"); + // Restore old terminal behavior + tcsetattr(keyboard_fd, TCSANOW, &tc_old); + return -1; + } + + return 0; +} + +void input_close(void) { + tcsetattr(keyboard_fd, TCSANOW, &tc_old); + close(mouse_fd); +} diff --git a/vsh/input.h b/vsh/input.h new file mode 100644 index 0000000..e003be1 --- /dev/null +++ b/vsh/input.h @@ -0,0 +1,25 @@ +#pragma once + +#define BUTTON_LEFT 1 + +struct input_event { + enum { + IN_KEY_DOWN, + IN_BUTTON_DOWN, + IN_BUTTON_UP, + IN_MOUSE_MOVE, + } type; + union { + char key; + struct { + int16_t dx, dy; + } mouse_move; + int button; + }; +}; + +extern int cursor_x, cursor_y; + +int input_wait_event(struct input_event *ev); +int input_init(void); +void input_close(void); diff --git a/vsh/logo.c b/vsh/logo.c new file mode 100644 index 0000000..a1c2b80 --- /dev/null +++ b/vsh/logo.c @@ -0,0 +1,261 @@ + +/* GIMP header image file format (RGB): /home/alnyan/Documents/logo.h */ +char *font_logo_header_data = + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + ":76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + ":76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + ":76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F" + ":76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F" + ":76F:76F:76F````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F" + ":76F:76F````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + "````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F" + ":76F:76F````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + "````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F" + ":76F````````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F````" + "````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F" + "````````````````````````````:76F:76F:76F:76F:76F:76F:76F````````" + "````````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!" + "!!!!!!!!`Q!!`Q!!`Q!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F" + "````````````````````````````:76F:76F:76F:76F:76F:76F:76F````````" + "````````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F`Q!!" + "`Q!!`Q!!`Q!!`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F" + "````````!!!!!!!!````````````:76F:76F:76F:76F:76F:76F:76F````````" + "!!!!!!!!````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F`Q!!" + "`Q!!`Q!!C!!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F````" + "````````!!!!!!!!````````````````:76F:76F:76F:76F:76F````````````" + "!!!!!!!!````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + "C!!!C!!!`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F````````" + "````````!!!!!!!!````````````````````:76F:76F:76F````````````````" + "!!!!!!!!````````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F" + "`Q!!`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F````````" + "````````!!!!!!!!````````````````````:76F:76F:76F````````````````" + "!!!!!!!!````````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F" + "`Q!!`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F````````" + "````````!!!!!!!!````````````````````:76F:76F:76F````````````````" + "!!!!!!!!````````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F" + "C!!!C!!!!!!!`Q!!`Q!!`Q!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F````" + "````````````````````````````````:76F:76F:76F:76F:76F````````````" + "````````````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + "`Q!!`Q!!`Q!!`Q!!`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F:76F:76F" + "````````````````````````````:76F:76F:76F:76F:76F:76F:76F````````" + "````````````````````:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + "`Q!!`Q!!C!!!`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F`Q!!" + "C!!!C!!!`Q!!`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76FC!!!" + "`Q!!`Q!!`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F`Q!!`Q!!" + "`Q!!`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F`Q!!`Q!!" + "`Q!!C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76FC!!!C!!!C!!!" + "C!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F`Q!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:76F" + ":76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76F:76F:76F:76F:76F`Q!!`Q!!`Q!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`Q!!" + "C!!!C!!!C!!!:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F:76F" + ":76F:76F:76FC!!!C!!!C!!!`Q!!`Q!!`Q!!`Q!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`Q!!" + "C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!" + "C!!!C!!!C!!!C!!!C!!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`Q!!`Q!!" + "`Q!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!" + "C!!!C!!!C!!!C!!!`Q!!`Q!!`Q!!`Q!!C!!!`Q!!`Q!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`Q!!`Q!!" + "`Q!!`Q!!`Q!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!" + "C!!!C!!!C!!!`Q!!`Q!!`Q!!`Q!!C!!!`Q!!`Q!!`Q!!`Q!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`Q!!`Q!!C!!!" + "`Q!!`Q!!`Q!!`Q!!`Q!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!C!!!" + "C!!!`Q!!`Q!!`Q!!`Q!!`Q!!C!!!`Q!!`Q!!`Q!!`Q!!`Q!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`Q!!`Q!!`Q!!" + "C!!!C!!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!" + "`Q!!`Q!!`Q!!`Q!!`Q!!C!!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`Q!!`Q!!`Q!!" + "`Q!!`Q!!C!!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!" + "`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!`Q!!!!!!!!!!!!!!" + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + ""; + diff --git a/vsh/logo.h b/vsh/logo.h new file mode 100644 index 0000000..039306c --- /dev/null +++ b/vsh/logo.h @@ -0,0 +1,13 @@ +#pragma once + +#define FONT_LOGO_WIDTH 64 +#define FONT_LOGO_HEIGHT 64 + +#define FONT_LOGO_HEADER_PIXEL(data,pixel) {\ +pixel[0] = (((data[0] - 33) << 2) | ((data[1] - 33) >> 4)); \ +pixel[1] = ((((data[1] - 33) & 0xF) << 4) | ((data[2] - 33) >> 2)); \ +pixel[2] = ((((data[2] - 33) & 0x3) << 6) | ((data[3] - 33))); \ +data += 4; \ +} + +extern char *font_logo_header_data; diff --git a/vsh/psf.h b/vsh/psf.h new file mode 100644 index 0000000..ee92d6f --- /dev/null +++ b/vsh/psf.h @@ -0,0 +1,15 @@ +#pragma once +#include + +#define PSF_FONT_MAGIC 0x864ab572 + +struct psf_font { + uint32_t magic; + uint32_t version; + uint32_t headersize; + uint32_t flags; + uint32_t numglyph; + uint32_t bytesperglyph; + uint32_t height; + uint32_t width; +}; diff --git a/vsh/video.c b/vsh/video.c new file mode 100644 index 0000000..f02e0ad --- /dev/null +++ b/vsh/video.c @@ -0,0 +1,130 @@ +#include "video.h" +#include "psf.h" +#include +#include +#include +#include +#include +#include + +// Framebuffer +static int fb; +static size_t fb_size; +static uint32_t *fb_mem; +static uint16_t fb_charw, fb_charh; +struct ioc_vmode fb_mode; + +// PSF font +static struct psf_font *font; +static uintptr_t psf_fb_addr, psf_fb_pitch; + +int psf_init(uintptr_t addr, uintptr_t pitch, uint16_t *charw, uint16_t *charh); + +void video_putpixel(size_t x, size_t y, uint32_t v) { + fb_mem[y * fb_mode.width + x] = v; +} + +int video_start(void) { + int off = 0; + + if ((fb = open("/dev/fb0", O_RDONLY, 0)) < 0) { + perror("open(/dev/fb0)"); + return -1; + } + + // Get framebuffer size + if (ioctl(fb, IOC_GETVMODE, &fb_mode) != 0) { + perror("ioctl(IOC_GETVMODE)"); + close(fb); + return -1; + } + + fb_size = (fb_mode.width * fb_mode.height * 4 + 0xFFF) & ~0xFFF; + + // Lock framebuffer + if (ioctl(fb, IOC_FBCON, &off) != 0) { + perror("ioctl(IOC_FBCON = off)"); + close(fb); + return -1; + } + + // Map framebuffer into memory + if ((fb_mem = mmap(NULL, fb_size, 0, MAP_PRIVATE, fb, 0)) == MAP_FAILED) { + perror("mmap()"); + // Unlock framebuffer for cleanup + off = 1; + if (ioctl(fb, IOC_FBCON, &off) != 0) { + perror("ioctl(IOC_FBCON = off)"); + close(fb); + return -1; + } + close(fb); + return -1; + } + + psf_init((uintptr_t) fb_mem, fb_mode.width * 4, &fb_charw, &fb_charh); + memset(fb_mem, 0, fb_size); + + return 0; +} + +void video_stop(void) { + int on = 1; + ioctl(fb, IOC_FBCON, &on); + munmap(fb_mem, fb_size); + close(fb); +} + +extern char font_start; + +int psf_init(uintptr_t addr, uintptr_t pitch, uint16_t *charw, uint16_t *charh) { + font = (struct psf_font *) &font_start; + if (font->magic != PSF_FONT_MAGIC) { + return -1; + } + + psf_fb_addr = addr; + psf_fb_pitch = pitch; + + *charw = font->width; + *charh = font->height; + + return 0; +} + +void video_draw_string(size_t x, size_t y, const char *str, uint32_t fg) { + for (; *str; x += fb_charw, ++str) { + video_draw_glyph(y, x, *str, fg, 0); + } +} + +void video_draw_glyph(uint16_t row, uint16_t col, uint8_t c, uint32_t fg, uint32_t bg) { + if (c >= font->numglyph) { + c = 0; + } + + int bytesperline = (font->width + 7) / 8; + + uint8_t *glyph = (uint8_t *) &font_start + font->headersize + c * font->bytesperglyph; + // XXX: Assuming BPP is 32 + /* calculate the upper left corner on screen where we want to display. + we only do this once, and adjust the offset later. This is faster. */ + uintptr_t offs = (row * psf_fb_pitch) + (col * 4); + /* finally display pixels according to the bitmap */ + uint32_t x, y, line, mask; + for (y = 0; y < font->height; ++y) { + /* save the starting position of the line */ + line = offs; + mask = 1 << (font->width - 1); + /* display a row */ + for (x = 0; x < font->width; ++x) { + *((uint32_t *)(psf_fb_addr + line)) = ((int) *glyph) & (mask) ? fg : bg; + /* adjust to the next pixel */ + mask >>= 1; + line += 4; + } + /* adjust to the next line */ + glyph += bytesperline; + offs += psf_fb_pitch; + } +} diff --git a/vsh/video.h b/vsh/video.h new file mode 100644 index 0000000..7c21dfa --- /dev/null +++ b/vsh/video.h @@ -0,0 +1,11 @@ +#pragma once +#include + +extern struct ioc_vmode fb_mode; + +void video_putpixel(size_t x, size_t y, uint32_t v); +void video_draw_glyph(uint16_t row, uint16_t col, uint8_t c, uint32_t fg, uint32_t bg); +void video_draw_string(size_t x, size_t y, const char *str, uint32_t fg); + +int video_start(void); +void video_stop(void); diff --git a/vsh/vsh.c b/vsh/vsh.c new file mode 100644 index 0000000..2857ce8 --- /dev/null +++ b/vsh/vsh.c @@ -0,0 +1,165 @@ +#include "video.h" +#include "input.h" +#include "logo.h" +#include +#include +#include +#include + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +static int running = 1; + +static void signal_handler(int signum) { + running = 0; +} + +static void draw_logo(int x, int y) { + char pixel[4]; + char *data = font_logo_header_data; + uint32_t v; + for (uint32_t j = y; j < y + FONT_LOGO_HEIGHT; ++j) { + for (uint32_t i = x; i < x + FONT_LOGO_WIDTH; ++i) { + FONT_LOGO_HEADER_PIXEL(data, pixel); + v = pixel[2] | ((uint32_t) pixel[1] << 8) | ((uint32_t) pixel[0] << 16); + if (v) { + video_putpixel(i, j, v); + } + } + } +} + +static void draw_cursor(int x, int y) { + for (size_t i = 0; i < 8; ++i) { + for (size_t j = 0; j < 8; ++j) { + if (x + i >= fb_mode.width || + y + j >= fb_mode.height) { + continue; + } + + if (7 - j > i) { + video_putpixel(x + i, y + j, 0xFFFFFF); + } + } + } +} + +static uint64_t get_time(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +static void draw_progress(size_t count, size_t total) { + size_t w = count * fb_mode.width / total; + + for (size_t i = 0; i < fb_mode.width; ++i) { + for (size_t j = fb_mode.height - 16; j < fb_mode.height; ++j) { + video_putpixel(i, j, i <= w ? 0x00FF00 : 0xCCCCCC); + } + } +} + +#define CLICK_COUNT_EXIT 20 +int main(int argc, char **argv) { + struct input_event ev; + int prev_x = 0, prev_y = 0; + int logo_vel_x = 1, logo_vel_y = 1; + double logo_x = 0, logo_y = 0; + int click_count = 0; + int err = 0; + int res; + + signal(SIGINT, signal_handler); + + if (input_init() != 0) { + return -1; + } + + if (video_start() != 0) { + input_close(); + return -1; + } + + uint64_t t0, t, dt; + + t0 = get_time(); + + while (running) { + t = get_time(); + dt = t - t0; + + res = input_wait_event(&ev); + + if (res < 0) { + err = errno; + break; + } + + if (res == 1) { + // An event ocurred + if (ev.type == IN_KEY_DOWN && ev.key == 'q') { + break; + } + + if (ev.type == IN_KEY_DOWN || ev.type == IN_BUTTON_UP) { + ++click_count; + + if (click_count == CLICK_COUNT_EXIT) { + break; + } + } + } + + for (size_t x = (int) logo_x; x < (int) logo_x + FONT_LOGO_WIDTH; ++x) { + for (size_t y = (int) logo_y; y < (int) logo_y + FONT_LOGO_HEIGHT; ++y) { + video_putpixel(x, y, 0); + } + } + + t0 = t; + logo_x += logo_vel_x * ((double) dt / 10); + logo_y += logo_vel_y * ((double) dt / 10); + + if (logo_x < 0) { + logo_vel_x = 1; + logo_x = 0; + } else if (logo_x + FONT_LOGO_WIDTH >= fb_mode.width) { + logo_vel_x = -1; + logo_x = fb_mode.width - FONT_LOGO_WIDTH - 1; + } + if (logo_y < 0) { + logo_vel_y = 1; + logo_y = 0; + } else if (logo_y + FONT_LOGO_HEIGHT >= fb_mode.height) { + logo_vel_y = -1; + logo_y = fb_mode.height - FONT_LOGO_HEIGHT - 1; + } + + draw_logo(logo_x, logo_y); + draw_progress(click_count, CLICK_COUNT_EXIT); + + video_draw_string(100, 100, "Test?", 0xFF0000); + + for (size_t x = prev_x; x < MIN(prev_x + 8, fb_mode.width); ++x) { + for (size_t y = prev_y; y < MIN(prev_y + 8, fb_mode.height); ++y) { + video_putpixel(x, y, 0); + } + } + draw_cursor(cursor_x, cursor_y); + prev_x = cursor_x; + prev_y = cursor_y; + } + + video_stop(); + input_close(); + puts2("\033[2J\033[1;1f"); + + if (err) { + printf("An error ocurred: %s\n", strerror(err)); + } else { + printf("Execution finished normally\n"); + } + + return 0; +}