From 66f2ca62469eba4b46e1f19f54dde9476b08d956 Mon Sep 17 00:00:00 2001 From: Loic Guegan Date: Fri, 30 Apr 2021 09:31:30 +0200 Subject: [PATCH] Debug interrupts, allow to show bmp images --- src/Makefile | 3 ++ src/boot/multiboot2.cc | 9 ++++ src/boot/multiboot2.hpp | 10 ++++- src/boucane.cc | 18 +++++++- src/boucane.hpp | 2 + src/core/idt.cc | 1 + src/core/int.S | 3 +- src/core/paging.cc | 23 ++++++---- src/drivers/bmp.cc | 39 +++++++++++++++++ src/drivers/bmp.hpp | 18 ++++++++ src/res/logo.bmp | Bin 0 -> 364954 bytes tools/logo.svg | 95 ++++++++++++++++++++++++++++++++++++++++ 12 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 src/drivers/bmp.cc create mode 100644 src/drivers/bmp.hpp create mode 100644 src/res/logo.bmp create mode 100644 tools/logo.svg diff --git a/src/Makefile b/src/Makefile index e4b2a82..486b8f2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -25,6 +25,9 @@ $(EXEC): boot/boot.o $(BOOT_OBJ) $(DRIVERS_OBJ) $(LIBS_OBJ) $(CORE_OBJ) $(RES_OB %.o: %.psf objcopy -I binary -O elf64-x86-64 --prefix-symbol res $^ $@ +%.o: %.bmp + objcopy -I binary -O elf64-x86-64 --prefix-symbol res $^ $@ + clean: rm -f $(EXEC) find ./ -name "*.o" -delete diff --git a/src/boot/multiboot2.cc b/src/boot/multiboot2.cc index da247ad..0cc8a25 100644 --- a/src/boot/multiboot2.cc +++ b/src/boot/multiboot2.cc @@ -69,4 +69,13 @@ char mb2_find_framebuffer(u32* mb2_info_addr, FRAMEBUFFER *fb){ return 1; } return 0; +} + +char mb2_find_mem(u32* mb2_info_addr, MEM_INFO *mem){ + u32* addr=mb2_find_tag(mb2_info_addr,4); + if(addr){ + memcpy(addr, mem, sizeof(MEM_INFO)); + return 1; + } + return 0; } \ No newline at end of file diff --git a/src/boot/multiboot2.hpp b/src/boot/multiboot2.hpp index f340269..f86c894 100644 --- a/src/boot/multiboot2.hpp +++ b/src/boot/multiboot2.hpp @@ -19,8 +19,16 @@ typedef struct FRAMEBUFFER { u8 reserved; } __attribute__((packed)) FRAMEBUFFER; +typedef struct { + TAG_HEADER header; + u32 mem_lower; + u32 mem_upper; +} __attribute__((packed)) MEM_INFO; + + u32* mb2_find_tag(u32 *mb2_info_addr, char type); char mb2_find_bootloader_name(u32* mb2_info_addr, char *return_name); char mb2_find_new_rsdp(u32* mb2_info_addr, u64 *return_addr, u32 *return_size); char mb2_find_old_rsdp(u32* mb2_info_addr, u64 *return_addr, u32 *return_size); -char mb2_find_framebuffer(u32* mb2_info_addr, FRAMEBUFFER *fb); \ No newline at end of file +char mb2_find_framebuffer(u32* mb2_info_addr, FRAMEBUFFER *fb); +char mb2_find_mem(u32* mb2_info_addr, MEM_INFO *mem); \ No newline at end of file diff --git a/src/boucane.cc b/src/boucane.cc index 5f8c851..060f9cd 100644 --- a/src/boucane.cc +++ b/src/boucane.cc @@ -8,6 +8,7 @@ #include "drivers/vgatext.hpp" #include "libs/stdio.hpp" #include "libs/string.hpp" +#include "drivers/bmp.hpp" u64 kvar_kernel_vma; u64 kvar_stack_pma; @@ -16,6 +17,8 @@ u64 kvar_bss_start; u64 kvar_bss_end; u64 kvar_terminus_psf_start; u64 kvar_terminus_psf_end; +u64 kvar_logo_bmp_start; +u64 kvar_logo_bmp_end; void (*printk)(char *str,...)=printf; @@ -28,6 +31,8 @@ extern "C" void boucane(u64 mb_info){ asm volatile ("movq $__bss_end, %0":"=m"(kvar_bss_end)); asm volatile ("movq $res_binary_res_terminus_psf_start, %0":"=m"(kvar_terminus_psf_start)); asm volatile ("movq $res_binary_res_terminus_psf_end, %0":"=m"(kvar_terminus_psf_end)); + asm volatile ("movq $res_binary_res_logo_bmp_start, %0":"=m"(kvar_logo_bmp_start)); + asm volatile ("movq $res_binary_res_logo_bmp_end, %0":"=m"(kvar_logo_bmp_end)); // Init data structures asm volatile ("call load_gdt"); @@ -54,9 +59,20 @@ extern "C" void boucane(u64 mb_info){ __putchar=vgatext_putchar; } } - + // Booting! printk("Booting Boucane v%d.%d.%d\n",VERSION_MAJOR,VERSION_MINOR, VERSION_PATH); + char bootloader[20]; + if(mb2_find_bootloader_name((u32*)mb_info,bootloader)){ + printk("System informations -- BOOT:%s ", bootloader); + } + + MEM_INFO mem_infos; + if(mb2_find_mem((u32*)mb_info,&mem_infos)){ + u64 mem=mem_infos.mem_upper-mem_infos.mem_lower; + mem/=1024; + printk("RAM:%dMB\n", mem); + } while(1); } \ No newline at end of file diff --git a/src/boucane.hpp b/src/boucane.hpp index 20145c5..125b192 100644 --- a/src/boucane.hpp +++ b/src/boucane.hpp @@ -20,6 +20,8 @@ extern u64 kvar_bss_end; /// @brief Binary references extern u64 kvar_terminus_psf_start; extern u64 kvar_terminus_psf_end; +extern u64 kvar_logo_bmp_start; +extern u64 kvar_logo_bmp_end; // ---- Debug #define DUMP(var) asm volatile("push $0xABC; push %0; push $0xABC; _%=:; jmp _%="::"r"(var)) diff --git a/src/core/idt.cc b/src/core/idt.cc index 25264b6..d2b7ccc 100644 --- a/src/core/idt.cc +++ b/src/core/idt.cc @@ -1,4 +1,5 @@ #include "idt.hpp" +#include "boucane.hpp" #include "core/paging.hpp" #include "libs/string.hpp" diff --git a/src/core/int.S b/src/core/int.S index daf0224..861d5cc 100644 --- a/src/core/int.S +++ b/src/core/int.S @@ -6,7 +6,8 @@ .macro call_printk msg mov \msg, %rdi mov $0, %eax # Required for variadic functions - call printk + mov $printk,%rcx + call *(%rcx) .endm .globl INT_DEFAULT diff --git a/src/core/paging.cc b/src/core/paging.cc index b21e660..b12fb4c 100644 --- a/src/core/paging.cc +++ b/src/core/paging.cc @@ -36,7 +36,10 @@ void paging_enable() { // Setting up new kernel address space for(u64 i=0;i<=0x10000000;i+=4096){ + // Higher half mapping PAGE_MAP(i); + // Allow access to RAM: + paging_allocate_addr(kpages[0], i, i, PAGING_OPT_P|PAGING_OPT_RW, 1); } // 4096 bytes stack @@ -145,32 +148,35 @@ void paging_allocate_addr(u64* pml4_table, u64 virt, u64 phy, u16 options, char // Solve pdp if(pml4_table[pml4] == 0){ - pml4_table[pml4]=(u64)(useKernelTables ? paging_allocate_table() : VIRT(PAGE_ALLOCATE())); + pml4_table[pml4]=(u64)(useKernelTables ? paging_allocate_table() : PAGE_ALLOCATE()); pml4_table[pml4]|=options; paging_allocate_addr(pml4_table,virt,phy,options,useKernelTables); return; } // Solve pd - u64* pdp_table=(u64*)(VIRT(PAGE(pml4_table[pml4]))); + u64* pdp_table=(u64*)(PAGE(pml4_table[pml4])); + pdp_table=useKernelTables ? VIRT(pdp_table) : pdp_table; if(pdp_table[pdp] == 0){ - pdp_table[pdp]=(u64)(useKernelTables ? paging_allocate_table() : VIRT(PAGE_ALLOCATE())); + pdp_table[pdp]=(u64)(useKernelTables ? paging_allocate_table() : PAGE_ALLOCATE()); pdp_table[pdp]|=options; paging_allocate_addr(pml4_table,virt,phy,options,useKernelTables); return; } // Solve pt - u64* pd_table=(u64*)(VIRT(PAGE(pdp_table[pdp]))); + u64* pd_table=(u64*)(PAGE(pdp_table[pdp])); + pd_table=useKernelTables ? VIRT(pd_table) : pd_table; if(pd_table[pd] == 0){ - pd_table[pd]=(u64)(useKernelTables ? paging_allocate_table() : VIRT(PAGE_ALLOCATE())); + pd_table[pd]=(u64)(useKernelTables ? paging_allocate_table() : PAGE_ALLOCATE()); pd_table[pd]|=options; paging_allocate_addr(pml4_table,virt,phy,options,useKernelTables); return; } // Solve address - u64* pt_table=(u64*)(VIRT(PAGE(pd_table[pd]))); + u64* pt_table=(u64*)(PAGE(pd_table[pd])); + pt_table=useKernelTables ? VIRT(pt_table) : pt_table; if(pt_table[pt] == 0){ pt_table[pt]=PAGE(phy); pt_table[pt]|=options; @@ -180,8 +186,7 @@ void paging_allocate_addr(u64* pml4_table, u64 virt, u64 phy, u16 options, char } u64* paging_create_task(int npages){ - u64 *pml4=VIRT(PAGE_ALLOCATE()); - u64 sum=(u64)pml4+kvar_kernel_vma; - paging_allocate_addr(pml4, 0, (u64)PAGE_ALLOCATE(), PAGING_OPT_P|PAGING_OPT_RW, 0); + u64 *pml4=PAGE_ALLOCATE(); + // paging_allocate_addr(pml4, 0, (u64)PAGE_ALLOCATE(), PAGING_OPT_P|PAGING_OPT_RW, 0); return pml4; } \ No newline at end of file diff --git a/src/drivers/bmp.cc b/src/drivers/bmp.cc new file mode 100644 index 0000000..6f9dad6 --- /dev/null +++ b/src/drivers/bmp.cc @@ -0,0 +1,39 @@ +#include "bmp.hpp" +#include "libs/string.hpp" + +#include "drivers/framebuffer.hpp" + +void bmp_draw(u8* bmp_data, int posx, int posy){ + BMP_HEADER header; + memcpy(bmp_data, &header, sizeof(BMP_HEADER)); + if(header.signature!=0x4d42){ + printk("Invalid BMP data\n"); + return; + } + + // Do not forget, each row is 32bits aligned + u32 Bpp=header.bpp/8; + u32 lineW=header.width*Bpp; + while((lineW&0x3)!=0){ + lineW++; + } + + for(u32 y=0;yipm%VPnX5n(+f5{0GUig2wP9A*7 z5vLFK47YswRyRgblteZm&m+$v&n6OyjgL7ql7AnTE*=zEg#$?op%Cnz6B9;0m? zxqm?Jqr4Zn2f3U6yD0A*kUJ<>Ah#p8 z(YKs(IiGhc(Uq`Uw*pSJTT7gF|9_S4r#*^BhhmY|Gi+g$LU zE}9llb|M`?=xe8(kIX~b=x?QL8IWfBo2bu48tH4`x`BEQg$Ykm{ zv`wO(O_@cRMO!9i1~QShe<0^}b#VHE1Nc|7A{hs>odU|?JiKTx3~9T>Iczw5Z7Ph{;zWX zS7`e(*Ix=k-xs<50`zn%WKQGe^gg$wuOv-aekx6uD)>TlxTeIuXuhDD1O?ZId5 z(bm@Xdir0-{ja6%HT=!)_4W0;bN{R9dlhZ}L;rtM|F0!Wmh4tpS-Bf+ujKj_85tQb zPbQNf<%9{`pG3qhaZB71x5O>=E%hz+E%hz+EqS6mQJyGIlqYK6D#b>ieM|e6_ATx5 z+U2#&YnRt9e+N3j3PheLPn0Lh6Xl8WM0uid5XM0mzh(TE@mt1k8NU@`Pvwcmof>y) z+^KP=#>*QoZ@j$m^6v9-pO5=|+~?yypS$3P?oWJSkD~h%-Jj_GME57UuiAap?yGiR zwfP{-2Vp)4^Ff#o!aO$Su`!R0d2GyMWBx7kZ<&9~{9EP?G;g4J1Ea{if#wao7a!t1 zi1|*@o{QnR z7@p_jc|M-!<9R-wL*_YTogew7Zigq& zN9G}I3FA&rXB^z0=>A0aC%Qk;{fX{Rbbq4z6WyN}MU&ALbI=dXcWS;<^PQUS)O@G% zMDv}R@6>#!<~udtsrgRLcWS;<^PRdsaj$mwCmMIEKhd~T<4%n`HSW~7Q{zsJJ2mdq zxKra!jXO2&)VNdQPR}Bi;w<7!&qU;j@@@2NKT&_8{zUzW z`V*_!pF0~LY84_+lqbp)<%#k{d7?Z~o+wY0C(0A$iSk5wqCBy?`?ql9-ypvx?)U`c z_^z%l<4%n`HSW~7Q{zsJJ2mdqxKra!jXO2&)VNdQPW30s6MK5}C+bhspQt}kf1>_G z{fYV$^(X32)Ssw7QGcTT#E;`k{TM#f12`W@o+wY0C(0A$iSk5wqC8QaC{L6p$`j>@ z@@B-hiw}o+9@6NlN2RjXO2&)VNdQPK`S??$o$b<4%n`HSW~7Q{ztcC(09} zNPnXKME!~S6ZI$RPt>2NKT&_8{zUzW`V;jh>QB6syvLW|L%kT0C(0A$iSk5wqC8Qa zC{L6p$`j>@@9p}ND^QaqlYTT)Dr^cNccWT_Jai_+e8h2{k zsd1;qof>zlKT)2TNa#=0pQt}kf1>_G{fYV$^(X32)Ssw7QGcTTME!|B!hd=ce$*rJ zr^*xMiSk5wqC8QaC{L6p$`j>@@ELgB$-`?Kdec;S{2jRRR<4%n` zHSW~7Q{zsJJ2mdqxKra!jXO2&)VNdQPW30s6Qf9fqW(nviTV@uC+bhspQt}kf1>_G z{fXP~Cw6vrPOY!6ubwq)R!4bxd2eZH>4haFC6^Wz6)i3-EWEa$px|a?8Q0e!mrt8E z?PB^D&X_S{0e{oP-}Lqo$9;?mE+&wg4E>ZgKjr%*o``7`yCXgi7epO8ON z{{wBmr~bR1o}LrA4)R;Be}ib3*DkMJew^6lo0^*Hz^RMSmu}0-%38lY@M$EU!N2(g z_;@2aTnBntJ~(tL7$k0WcYh9S{w!nhnIMeIr-`BZ6uA9Ku0KI+)qz|eh#o6XlqZf0 zo(NVo6c-mSo-}FF+L2()&bSvGTMPHRq^_>6Kz&Qxn!x|S=_IlVc^-KVd6v1ckuu0L z$kWIMM13pG^sTnGwmERK%X4yaR__e_*&hD}xOzYMx}deS^&IA;JW+j1+=`+rSPPef z+yA7zY(UaZ-@;zif<5Wh%*@Q^w+EJt#%J)|H|6H$F0HMt4KZcfx8#ZHTWN}0*or!_ zS=~Kx;>7J@Qy&ey+Sb2`zPO^Yva(#ey!I`04SC`VI@d0*Z=sWPqnoVS)|%fDZCP1) z$w`xnHcP1Qh<{^Sp9y~5SyNLJ{E_+-qv&JU=nnwPKZ-v3k^cVv{TY}2*&F>~u0O>3 z{$O8U-v{X5ucM=5Kd#@;{qMuJ|6cmv!@chgf{p)O-1AQQ-B+DRJis`tM2wf;f${R# zfSTZgcWi5ozu2~%oN37^Q|gn&#ffA^#g)mLn%j~M4Xcw)O;09US~e!z+LGIp*4B-i z?+N#YznMAn%FUkAjWgLXKrCoj(vCH#LokM)*)%f8y zZev}K2qPv>E>2FH){(5NT#}qScilEIX(+53NqbY%6Uo`LZ%&q!^d!KqFYoncVyX3LKhSh`5?TKxz<>9vF=U1Z-U5$S9#16x$RG8M( z^f=$)s$_ot?3aXL#I-(LU0t0kZpjl_V^6Zyo}hdjc?=v{N4b`^wUmz{k05K1)yTuh zD*9GYK7@!{iNpfNVY}iM>#~-ZhU>wmO~b{fE$_?CosldrzZ9Kn?WkeZP*@heQ}}M- zJ8t=%M^JyB_^&SJ;0cKOmi8@iD~iqsx6cEI7E<<8_OmwnD0`6}+7grzZJP`J(?!#E z#jSbs=Cxtdd1wUd_}}Y4=vFl~%ag6Go5mboZ41Y0Ywt-G7B>C+`;X)|W4FJ9eMEm@ z{N;(-<<+-F5x3w#7h}hIW+dx3BO^0eRMeKNuYX{dz^ZNGSAG3T@T(R4${Z1fVfT8f zqN1Xd_lNc^{fY9#QP8)rye-5n&e?zlr=5t?0no z<&A^TzO|EhBDzB3^y$-Y9f^*WlT(zOHS317!Kuk+%F>CVVl4M=o%CyI;LEIWFgY7H)_Ti2baU*xmpFcn3 z>oR`JczNR>wxd6h{T8kG3?CluS`4wGEiKQCJIoryuJGH7i=&ZX*9x%fc;imp=fnHU z_^s^_2N5s}Tg-#Q!K}>8oMcti(s7GdgSa(VR#n}U%*x6c7JiX;^(OWj9Lpm!Uf%tQ z#+?G-FCl&_AC_`>M@jG{LSN1C5sHLmUN zgP1hjGUVDRD5y#KF5rFfTvb)oL~+afTfD!-tzqC6@kCwZF-UoiPHt|wdR7V@Yg>5M z((*L9a%QH)u5ca)dAv@JaQ?g+#?<0lSVVo%(n1gS85A+4uefF z6%Qx2YZ-R>;9oon-Rnp&>V9FW6l`vGm==lU>WC=TWNQ11U8*Wac79ooK4{~`D( z525W_^nH`-Z&3d_xkV4Aeh_U3as4&!|0?%?g|;tq{iPuEeUa-gQ2#vroK^7xdB3B@zdM*Zg4wt1~OH+4f4!>Oqe_qOW4S5hJO`4h#yYP)PGcUxg1q=2C zYxiL+_6`E3?gck^J6QWR>Tg}RaN(ZBM(@czZ=wIq)ZavY=r@A9Z&!EBtYmHN-P+(^1V7vs#?{u|3vR{l z3&PI0k+t<_^d0pram)PjTje{2AErFJqN3soF>5rubO(8& z`j+Qn4CHCXceNx=&kB8oh0VK6e!oFX*cHlyFJApfH6X3Ax>Eu&nD+=SeS+(6&;l-JRB9p$yi656hzyc!u3c6rW? zUyY0Iu|*FLc(ovOWYdQxW)Usm-l$AEq>axb}?%QW7Z&s z6&1~k#jaqZCnh-L>k8Oq9EASFaj9>Wl$6BkT4Aqrh&38qSC-p8S9W`iOLOy+at+uvJDP!pebS`s;u|W#X3meBg@X2Divf_edOW zscVfkZY4RlEneqh&-E?hmbf)ua0`BMK^$gHnpCJ=Yn1rmmikw)Erw?i^Q<=2*4F-> zctG<(xUV|x<4)Nxa$C$=NS2jdpj~SW;8wt}va<8zaEr6Dr_2+#%m;zamG-y=zt|9m zTaArt#H}%cTaAsYV{r@L%9_9xnYZTOGLKE#?(=DGZeA3JS^4?ZV%8YJtU=D0pFcYm zyXMT9bCS4a{w?(Gw8bsXx`@pSSXp_kxHZObtFm%QEN+okaJIN5ZlyDBVGoJTv1RV% z(f1<`>RE$jcqVZ=GP$|J9({SDJTWcxEppQ);`UJI=2oa{jWJzo5W8}7XAFs5Jk#~y z*5T%tH%~M3Ano$lG&D3^9{1iZEnO&XjZxexDd`&$x56{6tgJj++!D9a5w|KTDsG9x zt(uzK#jP=lTh-OeV{r=`SFN}uZlxn`aR%~zL*rH!v8EfvtucyQA=hj;lVqspmz$e= zp|~Y(r6X?T<>jp%dfv|vv&JZ94cfTk&RZlVZ@IW7Zlxn`5u^C*P`Fi8)GBU`aoh^$ zk_`3yu+2ZkoIQ*fRL_0&{KR0JPmBEW>`6R-s5u|n%gQbkx5hYbh5c7?&yf7h$BJ8? z`#PX=r3G%))zvMIoAc&>7<+MBgZyx?tg5;p7Ps&z{!QExx6%o>I3F=K-&TG7O7*QV zu5Z=V-5-lv9em&WN^bLXxTx5hYbg=ZA^3=0bjtHmvGE1hs_#*7)U zv0E)I8^oPiCgJ}TV-Wsx5vG!+uA(;V$hB>mdnh{Nkd{6Ic$5x zEpaQIaBKSX>30o9}8+RbA#_HoTTHtJho(#r9qZ}A?E$SpTy-oU~_^9F9OScM(+ zIfaESL)y2@8|e9o>3V*md78(*P8ElF=4n>n8jt!`++LrI49~@g!;I1H^IVMR9SatS zTjLS8QpIq24%yh%$7*VB8O2?2b1}fRad__QdB&cO(b&_{(*9W7^4wSL zTj^@wVjpS(`|Wc>`4w|>E5xlaid(t4<#D*RfoFS!`j+}uI_g{erfza#t{MurLVHuw z6JpmG!>;g5;+|PfPR=zv+rz{yaVuSMYv#YPvWMw+aes#H}%gTlx93V{vQFoH-|nTjEwa;}-F!-PzgM8{)95v2l&q zHAb+jp<#6_W^o4j8XoBn#4T|vopFo#-Bnsz8hb`Y*iSOr@k;*tFT`Y&mYyGrTi7e- zGhe?iZi!pzj$5s*try1WTazXgCRpVqM>z?)p~1ty!~X zT_1;AlP8y`bB!u)ZA!-NM-0ynKjI8=tNrc7f2ePb6Wn62R|1>vkTIxXT~t-wq|P-; z*fnd`4YB-?_~ED9+S-m5x5O=Ut$W6czQynCEG{lykxJ)kYuhAt?R4x)t!ot&6z~Y6 z!^JIei+M1vaBKej`Ms%hF6gAVwUcoxRh%jBnrE7un~xW_JU@{+kk;pr@m}U9{>Ls) zy%Xs`=ei?K=gQ8`XYbFGV%JW>uIA>alHvUDxMwn;YyFVtr@rO6uj3N8g3cAU*E+19 z>C-#Kt(}BhMMd*tF^gxl5nby9FiYI>yy~<+_jM?JYe46UwQ~jRnltAPv1SUDJAyq2yrXg zn=#m%82P<;=Ni8?&isiy`;H(1wh+W$WyXxxhOJ-(f$KqD7#j_9bWZo}F zid*8Axb=VaEyiPBSy@?ZUcsPqg}uH_O^=CPFA=-u&Rxe?PL0K^Fy5TQ)WWklO5E~1 zALhZhJNK2}{O^6$)Hla(QU04JQlE#-U|xQH{=;Ft#7Wq{Xin!BfBsL1)5^^)iyh-I z{_w-)=vY7H*@#=7L-qjkK-}8Oz7=o_X~WjpHEGhM^>Mfr`odWa&CTo8yGE#Y1>a#_ z-prJk1!rGNe%cektsjY7o}b7(5VwZVw*qc4KCKN64Htk-&!@t!{QN58BSwH*EiD^~ z!Kz7#Ss@k}AMZrC;g7|wo_8`YJ@-}ITEINmCQls1Ey@sZNo*Hp0iXK$oN(>9P0(KP@72P$6PjO|)6*YJ6S~=qo z_%(O#qbc)g4URAN#Y+NaiCf+8?(7t|mQ|}#IQ*mW> z_LO8@-Ab`*E9|PTU&&tSDZ|pUa&vQ+;2Zfl<1TJ>i(Ba1tC$Po)KAILsRC%goG4&YrzY>>9wXs;Z^QtgM`2Vb_k!EvRunzVd{EySB5xpLGc0{8;1<$|?WLK0j4M-N)?k0w z!%|*;DQ9dvm-hG-bgc67OH%IJ862BX@*cQ;-n@CgW^98^OWf)ex0na35pjz_KU#fb@gEM>f&1s8WH8sn@pyFZk!Z2RJZ^(Ea$GhMd##G$u z>Uv+VxV474Aa1cR7>DhOTVP83%DIio~rb>S0~%Fx+CU)e+Nu3HwntrqZ{z><>95 zrcG<#rEOoqZZ~b(d^lm&h%k#d`1Qo|mN9n6id%gjz@E1sc2jXHiXLT7Jc5W@I{~*C z=UQ@b&coN3GI!UO*fm%WvG##u?J9XpLk^$9!ln^n)!=W%XLx60W8;~;N{$n^q7U}< ziCfHrwYiGIeRpS3X>xK+b? ztR}x*FS+WU*ka9&ST_m#jw>s#Vb9~}oqrZ`^n~v)oITKSzm>6AJ$v@-d~7%2yu)9K zTMItSJQ25|=rP7Y+=`-wjKiqo7HhMbnD060Ue}^~JsY>Chtn5)bRmW{oQ1k0b+Lev z!QUBTMf371M~t;w{}z7xMs%z`#^Mh=m*d5){{6x1{TUB&D~cXxorqhB#CeRvn87XP z=WM=D6?vbV+1q&M)@%BuT!%Ang8#0#xQAT#S795weY1ZuY_4S>^+TT6Wn6Ker#%SIHUahFC ztSskQ{EpvwqPW%hamM0faN`4ra}c+p=qc8KxD`bgu{L%&++r?QAmwnVR`}zs*nm>z zQyon_iu<>N-;|s)ORKA^3z(C?517Ro4VWcvb&6Zeho_MZ$a+NFN+d2~9Cm5kVx7<6 zH7Je+7H=*2H1NSn5N!T3J$3($(DDd@jG?kKk0WyNg><^a=Lp9Egr}AUPb= zx5TYWSsNk$<;BP%)=1jm7Qd%72*0lc9jvjqxOg$Knd`<9PKED6EW=uSgO?EFF@^Cz ziSK(7-}_I9xYgD5DaPcJjD@(BNNi*rf;@w$Z;b=o;yD*1(|KP{C#Jliyu5tTlqpkg zXAe=zbA`4y&hc+PiBIq*^s3H)Rp8XAe7BR4KZ{#EpJg0A6NGX3G|x-iilXNjk7t=1 z;#L%0##jUy7y1^zb2DzS7N+t1i?}Wvkg0I8ITaNZ7ohXp0G{0QQsY{;#5pkPF?i|< z>}`vQIqSrCFqQ9c2LJYHL8za~_c?{SxYgbLIc#|1)`STYm=8&06Y@MFZbi}MtdoB- z7UI?m*fo{$n8J7zAo+*{D23={)%fu`XUv$<2j*M|&%BKMI*Y-#Yw=s&j4T6> zme6(u`4=uhe_Dt=t((7T=A5-s_VfOO&pV6HJB!ag6Zs4EGiW=Vy0{fZUqYYyBIEG| z#^dvhjkuLez6{tBx1#7O=EP#;O5_Se+}cXtV%_{d++v(^k;$y39Qr3w&!)_x%%Uxm zG6R`NTfnAsS%1Nfehz(q=lXBd|B9SVUEJ#GI*76O8e{WS^r^2fCST_IOF_h~%H0+% zTJ%bC2fPA3{pCq<>uTmjz$|eKyFBACBu``=iCa;06u5FEV{rsy^}_)I!bw>`j&YE&A;_HSYRHTF`f@Xo@o9p^Vqnr8mvx>{fYdJ(jeNmJco>VpnXgG zmi8^~SARm%Io zc=;~l<(UV@%Ns9myu9)9#>;D$mnTNiT;7S=N)H(uU&dHsp_G{fYV$^(X32)Ssw7 z@nm!Yd7?Z~o+wY0CngfduqRl5qCD|Y-iwbQYmn8*!^kT3d99*+2ziiwJnr*}qE5!5 zBM4*DPB|Z$hqN(1t&}YT(oBC7b>p|xw?4=IN#nOBOqjrWNFtk%=aJ`-XIVEJDT6$N zJdJEX)+6RSHQ#9-IGV>?$VDbo&tbkyqMl8eMVUogCS?XPk+y#z=c3b`%k??*{hjN- zQU5D)HubY;JB#|6$X}?RLEGunPYc5Jsa&5zU7pz8Ep8=~@QB_4sC`SGC{KKOQl5A<94YKgbbq4z6ZI$RPn0Lh6Sd1nkvvhJC{L6p z$`j>@^28{TC*n)gpJ?1E=kDlF)SuW-%#r>?{fY8Kd7?Z~o+wY0C(0A$iDiK!$`j>@ z^2F}7tONat@#y zzK6X;f1>_G{fYV$^(V>`^(X32)SuYhEl=#}*-w9>{zUkJJW-w~PmCh{iTV@uC+bhs zpO}#$Pn0Lh6Xl5?AYZ*av0I)fPc*+gx%B0U^29s90{w~l6ZI$RPn0Lh6Xl8WM0sKq ztz{pAai{tdJ%>!5_!c;l{zQ4A{zUzW`V;jhUc>vq{fX{RH11S?qC7E?=+~d9KT&^T zuRJk|^e1+A%M;~^@Pt>2NKT&_;vAhqAJ2mdq zxKsU!^2F}$L-i->Pt>0n$rBR^{fSW|Pn0Lh6Xl8W#J)cLiTV@uC+bf;6q}hmu~(ib zPn0LJ4?&(NPkcp&JW-w~Pn0Lh6W5^^=}*+3s6SDjxM#0C@lJW7JW-yQ_yX%df1*55 zo+wY0C(0A$iBY6KQGcTT#EuSmVid^}BYC1cQJ&b=)}}u(k&q|K6Xl8WM0uh-F&n)| zf1>_Gd7?bA1wWGe6OB97pD0gUf?gm`lqbp)<%#k{d7?bAr$>LH{>0!*)SoC%JXL?9 z{zUzWkvuVxn87>2b1}?!Y91T+CmMIEKT)12Pn0J{k^aQ)Zh2xto+wY0Ct@#>Cq|L} zME!~S6ZI$RPt>2dXNNpdo+wW|jl9S5M0w&X<%#k{d1B%?{HgLpd7?Z~o+wY0C(09F zh&eUx)VNc5qC8Qa7|9dmiSk5v@(26miSk5wqC8QaC{L6phL}_1PW2~#5&hUaHe2Pf F`CqWw<%a+O literal 0 HcmV?d00001 diff --git a/tools/logo.svg b/tools/logo.svg new file mode 100644 index 0000000..ee133ab --- /dev/null +++ b/tools/logo.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + +