From a6e1d338a864f15557c70e07a1a7c5de4a552f38 Mon Sep 17 00:00:00 2001 From: kishimisu Date: Thu, 7 Dec 2023 21:09:13 +0100 Subject: [PATCH] Improved C.Basic support + various fixes --- JJSH3.g1a | Bin 135992 -> 0 bytes fx9860-emulator/headers/display.h | 12 ++- fx9860-emulator/headers/syscalls/bdisp.h | 5 + fx9860-emulator/headers/syscalls/filesystem.h | 2 + fx9860-emulator/headers/syscalls/keyboard.h | 8 +- fx9860-emulator/headers/syscalls/malloc.h | 4 +- fx9860-emulator/headers/syscalls/text.h | 1 + fx9860-emulator/main.c | 11 ++- fx9860-emulator/memory.c | 75 +++++++++------ fx9860-emulator/syscalls/bdisp.c | 37 ++++---- fx9860-emulator/syscalls/filesystem.c | 88 ++++++++++++++++-- fx9860-emulator/syscalls/keyboard.c | 27 ++++-- fx9860-emulator/syscalls/malloc.c | 8 +- fx9860-emulator/syscalls/misc.c | 6 +- fx9860-emulator/syscalls/syscalls.c | 2 + fx9860-emulator/syscalls/text.c | 28 ++---- 16 files changed, 214 insertions(+), 100 deletions(-) delete mode 100644 JJSH3.g1a diff --git a/JJSH3.g1a b/JJSH3.g1a deleted file mode 100644 index c5978191a15ce1d1aa0951c209d06775e5df7255..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135992 zcmeFadwf*ood@fYj;PnuG^*7ilVzqt&*;Fcir8YbJAFA|HZ!(Urqew>hJ!C+xrL?{;y)=+WMMG z%bk5{6yL>4xLE*;`rg4|Jcgk{N>aZdwk{6O?iWy9G7F3;<#9y z8FMivX*CJF3y+O6n7cXs#r>SV=LNj~7-`!$ePS!8Pyacm&-@QgFZ>76YVg$49L2ZA z^WyX36XJ$t~b=2$s|yr+@1ePXESBTrpS9nYoQz9d{q+;C6A%+!5|!?*HLVb3fs{+zZ@G z-0R$%do6n`-nCcm+iUgC+H+>F6~9(rsod+_bH=;kN(z3rcuTLW#qSoc@rrS;^Qzig zbY<~JJo1-Zna|4SU75$q>8=<)Qloz6l{F|g%bR{Bm(@$XvUIP-8@0!ZQciFDm5h)0 ztI^)rD>-{DXf66m7Fx4-)mQRqELU`YOlUYKC9Kt~)nIL&mE$e3OH_^?XOEe)XpH3p zs|@+hBxx4G!)6kqLWPn>JU#n&+H~O^%`#28@lo$_VG-c)DByl*3KoXdONGZ9M&-Nh zR!gi+uKUbxb*d{keR+kZ>#@tx6&jbixO}%oUcbj;+$QL#jCI_O5|tW#hQ3@-J3Td{ zHqNH4SoIFyvECW9XdKU4H2!i^L1nD1Cr9NB((l?ai^f!tG{^dB`7)1Mc-0w{N_0kh z(D`hoMlw}u4CNt>DMKu^>Go@dNNVq7bVr2s)8htu^Tf+bEx@meIUe2{`R}|fdOxpx zp5&3j2US7dHlC$Z`eHqGIfE^Hk+JUD15wTV(I_E1q%oR0UpO@37;WZ7o%M!&)L`=b zz$QCoWwsR>ckZUtO?f-UcaHBG-#xx(T-h~)oH)GCGL411EFa7gPi)ftLE|miYXRP1 zp8}`!zrM-oUweUY7Eiwd-{&48c<*`HAsgSd>T=3D=E;AvGqn8OEsq0^r+d3L#5Sn+ zyevIdes#-RShb4{>Y9s~k*fDcUY?8TJth5!_R|smp@at$q(_~)6(-LgZXMy*_sAxl z|6%th+Y=WZ>WHh@vjJ(@|I$mLk%GV$_&^zk8{!0CO!u6x1@38t?sm>C@ zAla-j1IKH#oy|=u(}m;Z4lUZb;4C-N2o@Pvo3oA8hDDwjXKt?ipj5cVT+A zGiZ}5G!+_sc^&U;Zf5oE;ra%uKi9CjGTTsXOzMdHM*VVcWO}x|+=jhDBUzWysooU~ zMU^h|?6=FZp;80PJu4>K6NNdpW`c@MYl+<-Cl%4Uo#X($C%70}Gxx5X$8!E6<(p$` zud$pCF2*_PXf|FrUSQK&4f^W+QKo3rc#w;+kJ9Yw<~Yku)h(YIO`aBczRgIjpPbbZ zfw$T8=Gn30(Gc%#NH-A<{(2i{&=lawz>~}A&vXO#7!4&HeDh5z^ZFsqu3{KO!dp&%9yt4pL6kw-s~3>pUpuPIP2T;r*(FLll-YIU+nQw;_b!{+=aNgA zS;qpJmDQlk3$vPKitb3F8P(096~m0KVMep$d+Ww*#{F3wW`9!UQLT77q)Io{jUGyP zkUyNzoFM3=TBmN6se^yl_ z25*C?v$spjD>Y83QX?1*IAvHQQQ zy)GAoakJbyMjX~^FccimKz=o5XLOF*f~xYGF-JiyZ>(cqBs`e0?vK*izgIHEJ+a-@QhGGG zzBQxR;rWszilK~YTC&^nVFAGq5WMN-2f0>0;vQAHvEP~tI;ILm7Mnbu+AJp{J^VA) z02gu3Fn=jqo4%`OMSr^M?%4FkTWNKAq@2FQSobqfmG9q~;)%4KJE`hW;ZyCI<>)c? z@5s43_E3afEzhyU229?3E@nFQ*46;0gu5|jl#fJt!J7a+kGXLgqP-S|+i$!?C8Woh zj4*ruBIz+&;d~X1()tWWX}U9|Girx=#z=^pi4=$8lcD3zs66w&T}1$%b9_jMtK`wpjrIrT)FQ zsb4=;-wNEmkgk?%S$+JfJ>$(u=dtIqsP|hTyGwl}aW}@XvOkzCo6*h-Y#fy|j`_U2 z2EWcotI^J{=Br&j#r;mRcV)8i_HDV;xf?t&t{$nhxZhWroGt4C$sDhctOgW#&wQ#S zEmqcNdj26n%C)OauahJr=UCYICGZ&AIr>$5CY`5#9p+@6EZBPt{k9}Au{IIoQc0P# zLN2wu0CF$m=99eKvUK10H`5fUSnT_Vo0c(m%A0hTGl2CsM6=-U)$XzEvv{4m385ID zr@i%yKUZdvOv!;>{$kLa!Eu!~$_EYBzm-fl81utf>2ecz=yRX|6~6t_e&V0R8-07F zy?`CP^Ip^ZUfZa&3wgmJm2Fhsf!PH!Rdv_o#%5l~-eo+ZwRu4wwH@l?&(OCg-Df9E zum{0IB7HW(2lgH(e}9IAq-GKl`vjD^jK0JC(Ts;O9>@qqE;o5zs=4OY+>UnGE%M!y z6yPeS$lQzq+z>AUtD`RSAFThATHr$&j#1Sz|MIx>)fshzBqcnP&BfFWGkM|9T+FT> zc{O24-H_oGyaM7Cf;b?PrqNSH@DLFg*{X9 zwEnR-i2ha_qjch}zus0+!EqH{4mgIn*RY;yeiNxy3c8rAYT zHu7~+6)Ulpm9A`1`>+e`LAjECD-fu=X6))%BA!6o%URpY396Vy8LL^0wgFeP4VZ@8 z7FpXOK@}9Mh?QP~wgFePy=1!WBYZGoMNS9*%eh=kc+GR-(xvRxfM3mkBs7w)5$qMD zEHflodA=pKCWQ5yhumOqx}0u7s+3A)<>W@tMU+h?%@So3!e!%F**Qe>8`PmllFp5$ zQz}vS;=5eT?Xi^LN-!nZGJ0>&)N}e5G9mXAbNYYKPGayR{=T=`eWUB}z7}bf&?GkB zdrj0WFaN{J)u;Pcepr6WafP!Ef-2t!>|B9;)ofQgM|WM6%2t#gKO2?{pd~Z8c7x9# z8vsj`k@d7M_1hrreJ%r@EWgaRSXvCsYC?X#?V|osMIO{w%X#4vi+ou`$7);TEW6rv zk$aRR2-ZesxI}8*0Z@#jx>kH9)u3#7^5CLTa(E*tAL48ToO{Ky+~uC|ShEalz=~91r*LoDcH}3c##<;!Czr zp=y}x&Hgybzi@;f;%pL=#WU33b*{=OJH;R1lbc$#rJ>HzHr1r1VubsB$}q32<43AX z$$MHREda20+}Ihkb)Agvykobu?pM0}Q@qo>$0-_v8dHu({rGI>sA(=J!rb}IcC)Mj z)q;qp&*_Ns#?5!|X~386u%5PcYR?)fAD%BYn!D_!rjFDBP3c@uk}SG!NP4kUE)q-K zH*9t0SaIAj)|?>bNd+`FX(@d!RCR5zn+?lA{~)72a~_n>im_=r?>bUm_slJ0Gla)B zE1kv$Y7`zDWR7%4I*lM}dIatpavb`{BgMII)O>%&2(V``f>`=YiSLRIkDwJJXyan| zG(Ml>o_gx3pRD=!r=9|B>gDwR1byHY==`Lo{=EKVyk{#xu0w5o2AI$7aRw8dX3@yt z=VD`2r!)tuym65S9{aAO2UReKD)i9X163&wuR5|3-%?~B#(NjWyJR2>5L<-t=3%^e zq`YrHEQgJkeJ)g?9Q1s8q>OLOYd$Ln>cQrf9iEp4^NN8M)parfc=i}@c4QCV+To+`PTXE#Qc`B`Q>BW#TYjpsrVc7Gq7>9Pd2|L zD2GQ{@{ReiT_G)^(q1tozR`SoMd>2XlE=$RlR5xDDFfJrM+RJMbrxWpbF3u+ZGo<0 zH_f9@YF|o3IXp6Clp1jTsdBIgp&a{6i9a0&_$}(#>D>*iY!RDxocmywck|A6z~pJ} z6<*Jm{WZ97*b|O3&NF7tSPsp5n~-hI2Cj&<;GQ!MbA_gwgFbyh{a?;iE&pxG@~CRB zd9&s?Y(dJK%t1QuO_?X{307;I=4ws(g-3oT7;Llmf3DYeXl$c(*VO;@$lK0Uw%H~v z%5rJVdz_BZ!`dVKA#F1_Kqw0Kjtj66nVFt_;gPwhtI_K{s)31LKcAau>$x4xBdW2_ zBJ1s;X_@X%+#i>Mas*swgi66IR54j*;)DFFYC-)G@9c7PSvXSx>_%rrV~r6K(_N_B zC-&8j`Lcw0&=k{UND|m{0d_9*JsrEHhDIJ^7_6`a^d)-?TDL1R;yV+FN5o7#f>Gv?DuF=n(P$54vWiuX>pmdPBTQ?y^Z z%Sm>alqJ?2K~rWy~hkSNo}Ntvyj*D=4xHlNVUq7P*3ru;P{xriWC%GVz31 ziWy$FCrYJ45|vS;Fjlu%0yblI45z1NckM9Wq7@8vqoJr~?Q)Z#tG(W&J*5(ofB9X= z+-H8g_9M;vd3(<6K67M)@T@cR$K0~ZEjI*%^;bD%z=R(HHvu!-#ntbfQZ=f*b2eX- z7x)%57|Eg;`Q3)^uD-_;HT}!Wms|!_fO>R5H9s4HSIQ?@Eh3z6+f!~ioUN}B zy(EmR3H6$0)YCMC9L5n8A?sw_%D)v3xJmb(?UJpr zWpB%a8{YeVsdQ@9Rj$qs`dwP%03Mip-;uU~hB@T#z`ph!n^Au?Y|*He?xl5wrjhNd zlc4$89g-DthmpHVatHLeFQwXWi3Dh)+Q1fP^FV`YRWq+%2&>hktkJCctv4dTAJCRc zo{Ir)Q{JR)bclP;>}qLjIbch!YfYH6BD}ZC3qIe*JFLuVca&K^RiUU7ljl0L)+i6p z#Rj-W>pIOkP3h4PpK1(psW~m~JY#C5;@1e}i+#azW&i$}`*87|Gi3e84!$ay2RY%n z;(o!n!y@GDws?OeRPOA#Lvx2_dB62E!!y_`gS?QtugBVTx2A=^TQkpO9j)tvRHA#E*}Y7{UWBUei5E{>$RXNN#{ka;4J&gO@CRMXnnbbx0~i0VFBgRo~^rfls^PZ z5o~DLp{R0L&q{n zqKM|kVlg9$AOE6xcfXfz3=&-`-t?e{q0(p#^P zTn-%T=L|8)oc>un3hf8(_2Ri6)^ED;{tD?hoc@U=oc?dh6bd1pf3Mwr^Mm$1Eg!ZY zwOwjocJb5-vAL!hIAoH3;M9@D+@^Il>$kyLxx}VZU$8gB61%PC27Dkon@-Tob&*+tL4K@W+T z_MOO1$qE0T(eL!xx!A14O}f7C`@SdJpl3Nil_CbYh2~B0D%vdaHk*Y~^Hc$zQlM{g zzO6y%03)b%sSdoV6jIM-(6FFXPdf&BX6;b450W`jpjE>MSyKs{KqyYC9*z=Ax0{%5 zTJ6895}GAhkICAD5v-C|Hy9m`_sW(P96u_pBOPzQMp}t>tVAho`=v^j_Jst_$>Lc>3)T_y?Ws-FV*AAXWBUw`|A1rM*O#CxUn(BK~8;0EcHJG*)>=Rnbbbklpv=1 zA0WT5@>!(nKl0&`2FZ6set_&in1Q~;(xG-rpv94e#*+0O6?EN+LY+;LTil09X+eNn$U>K9{}C^Xt6X|wwi(zJZ_?oZ@;S}DNw4oZQ4lKL%0 zzcsAiy07(H9qt!07y8Y`UccyC~TWydOPeu3uIU$2uC~Oq%6b+pp}|Il-43-?M{u2iXNaEWT9YE3pSf z_)FlK=82}sMp%!hJv+M6YO`3y;xiJS9r!ssu_2Xz4(NF4$QrS9*e&J_aiG#xx0d|# zHWTbPsv={ED`J#$-#GUuqfr`?w}^&e?j(mj%;a_GLgMhpJnZ$kVcBrv0~3o2QS}?) zkr#;yjTit6yEr@)UmZJ`DLy~41i#ndw+_E^!oR;8{$1I(a42S%bU{a$!K@ zFC64@%nnPoS;8LHsdCIti*KpjBrVM{>x}YJ-_jg&R-(KVF*=EeTd*n^F=+{P-}uQg zcr}{t{h~k1*7afL$%)QgJDh~kt}~d8N)o(t3Uu~hb{hEE0#I~3-U#3&2i`kh%=6DD zid3MoT_kIST`E-h(-7qmgx&$Gu1)gKrTkE#U2Ry_xwuH>EhsX!m0}H_!$UF-#YddQ z^Aeud@m#?3CZ6BY+*iN*?thV;<~Q#@_0(?|Jt3Rbq<@-x)4$&42%6_~0FLmM1Wy*8 zQZfIMCd7eu2j%804YwF_UTlGC@sD9E>IMhihu9^W8*DN1F66a?p6?=REbsE~#te6g zrDWFw<;^4S3!5EU_C(5!VcDApESzDVJ7O~=JMC22<}i9o3Kdu_mZ5bKwK=Ii73uyY zTvm-XwxS0-1Via=zz~oB4nVA3zQcWkxS4k1sgm*yWx7N;KU^=upBqv+5{=6`B0?G| z8`350qjGkE(RNWv1HC1z(a;R!6lNcm{K+(fU;(gZQKud=Fcs+(*@O1Oh2HfqCO@#| z_>lpHXS{L<_`+=1_X zvc0@i71TIK?WE1AZKF$Oebeuh-@K4C=+%#vq@i^A|c3!3R>XN8qVo zdqiY=MD&Gi;Dn`+tMcSRhM$P3BW&tOlha8~v$ab(ii{)cfWvrvy2%=0yw3U>UT03z z5~7mRI3cCk)iWpw;l#4eCBTWoA|v4h){@Z?l11@;mgokp^Q2BrHlNA_qNTUu%?up5 zm6m)H4;<5Y09ZxRU*92F8hQRCl?7xX0hw2TW$0enraxFq1;)Ab1pSD&vjN=NO!`!f-~+7vP-F;GF-haLy0IdA6KM+zfC|n1nOo0QNQL zHXt4ah$D6^$dQ~VM-f~J@~0L7%5yMkJl=&|Oj{|zM}ggiUg_FD-dt1TxJ!Z0g{nzD ztH|opH1jur55ev?ptsU-%n^_OIE@^%p`AFEW6b|>m|y-tSuHlYDA^sx0A79&wJ&{hn3{L;XM~)mngE3_8(xN z4opCzf-FG0kBeEV0&Jo^-^W^R4X@=rY%Q(+Er1Fv2;bN#_fajya!{b6=oq$U#GOf= zxtp!o-89;eLWTGuNUDq{MoHEF>Ovi9cWnP}kT!;Cvns@SrKQ;C^7>xrU-Ehhb_;04 zW*Q0g4~T+}qhDzFEOx0fSLItnBcVA3xm+D)q4A`E-ykM}Q5rr3+UM7ku13AVUQp}< zwkkEef6F{$F@VR9u|}Ve@15{iMr?yPE+zZtqQ@m--Zyp|^R!LwHi~y(bDyQmomtx@ ze>}riqeE?6-&q2>P*!ACa6{q6v?od5Q}!F-8sUy2uPOYO_N=1MU8o|utn6P#w_jrT zxS#OxpK>j)P`&g0X2^VNT8|7U{5ed0U>|2PUWQ!DWW1Vby3Ftnw89%xWc1xB!;fx% z*?*@5yEetW`|p$yQ^D3}BgIt^eFl``0wg&aJ=nzoo@|heYN_O^AeW=Ny%(M2Xt@IZ zq`tit@$JDv@%)vD{*{w5^=1D3Qg`nkONBK{fj|mZ4g)SCjDl#bp!ELJfO2rM{xR|JO}yvrec?TqQ5cJTfQ;2 zL3T&fjq6T!q;%g;77E7m;)B3)#O0hj6&3DVg}(U%6R2bG#y7}QW4k54LGE2#jaIyW zc1LutlXD5niHnBRQtn8(cmlduX+uanaUe?A-8NhJu1jtukJ>s|WnBoZ}I7W5erT0%rfB`wvPA{Mt!xbCddRB4I}HpaF>%l`J3tk)al zpvsgZXU?cq?#*gV`1U%ziT~G30smPH{!3=`Jv+RLF<3>-fr&6*F|2J@tRJ z+WSa@Os%>j7$$C;!$B+C!DbP?6AYJ!9}rizmeAno9ES`}AZ zPJTCP@vpYE1NC-K*JEutk+yw0t%|FTEe71ECD}1DOT<3eJQhfnwz*DEj-iS(qs%~p z{2k=uJj~|nf#jLF)Ph@s{j0^k&u^I!OxVS|r=Iq%e^YqxM#1WlgZUS97ND=R-i_OH z#Jnxn71)K+$X>nWI_=ob=my!p!j`x{-Ns>;t8n(A)Mk{{O#Y5o?6d$ffB}7a`Qn1! zz%1RiKTph3SpXCEqiCkl*qt zQJhva?K?TQ0daLMdAsgFVH=`qS|edo%(5HFcEGrlIhfF!Iiex>QQuMq;UO^R^uT;B zgZihrL@Z`&dS5Z()Yo6f!(!<;!&>0dx>ipA_pfsX3-(i0F&^ZbUxA;apEK-vo-^!T zjqmT_`55p24Y~yW^Vl2sMj6-_4SS!!Q|fz2a)Z_zA)(LlJt!RwzsLF>kPe66BYlUZ zBjNXXy@!->Dx(Gtv?+5?#yxY?#pMjj3*8!NuWujO73@0n66xDZ@(I0n%moZ~&w#@tvf=#&qnbG6$5EvDx<0Dkp#A9_{I>U?`rJX zI_A-;BcC$iR+###MiRXImh0hiaSgJ(aUcV}U*a7q--d|A4hyiT0DEk9l?J}I={)mv zUX=zT)p~!$_HKcvQZ8k3{i<*2g+^1%Mgt7|>!TUSfUPw=nux`|{F$Rco-iX%H<^dA z?kv{&Q)s`8BsdZqL-2eoL!XJ>5-vvJVXxL#fQ3~Apa{e5@`-t9YqXBfZN;W>bEZ{v4f?<30G{Kp9jBMDB71CeZi za4!NO_uC;gvfVaMJU@~)iL)3(8t`?&%zfr-m~Nk~;0CwGXO|pPd&4FnqKxg6Sl{

JzmAR zgL1jVCDW{#oP$}n&IYZ7M&yp5^GBgHY58_&W3`__qA~@w`W!@^XcX+8!BgXzSDJl> zGyJbO8uY)`zq%Y=GHRT~KOsDooeGN~&z|}W_r-uEFcu*dG zUqfpio_(C5X$zj=?uTV_ShiRZN+UQlhH7sF)rOo?wBO3v3|CIhkoXGaht!xQW;Kv0 zmnft57ZU}Z9yiTOE*i+BTz}Cw=Zslp%-}1Fruj-RP3Z&co5^JWZnwaTaaDz{khTh| zqBPH?^Ub4(V7mo)R??fsa0;A(xCL?Xkqopad<};a*jP2h>AcH}wU9ippGp2|{faZR z5v;LCTcYrQ_P1gmHy5$egoA+JfjBwA4yy>;H`%>$U+-6r9YMq$!&US>lip)r zDilA@t)YHdd#UL6N50qO*TeHj>plOC_Rf8Kd)SAN6R^vp0h6IH&3|Ev*Amui7Jv zLw^g+6OaFIzoGH|n8{39>Bj+1jPgApM$Pb>c8C3!T~tPll1|vpDV|WYe_VcqwSPR^ zemmO7<8S{)`zVE7rj?Gd_8{$}R+bt*mJtjx48_cHV4Qu~O4Li<*KJ=(w3Ca{_6 zN;Tp3>t?iHGu1w{O2q1oyVO#3!t3_0No zcf^OL-KXUxkj0?KGc0#OjuEG2*@z2@EM3J}VvGBcw>X@48#&h7=+-FoiQ)TCuz#Ll zG%Or%g8c=5)NAz?=CAr!=!F-0Yp*;aRK#Sy&gox*uJ~4hyT0=khBPo6BnC zod9jh3nXtIpDC69DhInQ!Ci2xzfC#MYeRhK$%zj|<%C<-?=ZdGVDBUO*^ZLvr$DMy`cc~GT|=j~hGIi5htE+E zw=6E*IMNy6ju_wyAtRCvO*t7g8AX67y@`$#NVG|?FC;-XNP{;a4OZ|p*n87pfm{F@ zwgB-I3xJmkybHklfO+uu7O>bpitkh5QR9igL;hgcFYy+ICmN3i57n6!h^06gr&CJ9 z-2`dw%R;4CCpg4LG0!yydYHU;A$1zULnM#f8ikFv|k8$5UTD;t3eyoGq ze(ZAB0lFADSncCWIX4IU&)}W$H&-$;OoCiO$tRt}RqI zKFC|IRbdOsi>JytYUQH&!ui0ZRnB9u#ZPECgzt! z6#Qu8*yd{o5(uL8qvU;Y^&mH$;*@H}>9iHqLp-(^t~War?HGlnFUB?sm_^A;KpV6T zV{M=ht})jbovvurqJ?>NWAZ|rq0B*g4(_+WH|zJC_0wn8Zy1x(+t4!`!*|GAjrqco z&T3k{1)wR%2qp_NfQztC9KXo$)$qen@Ie>jW%{SUFQfFFKJrJLJ_a$Gv-ffOxFei? zP6emW0gX;lar(Kdao5Q#+;sweCq&`d247B@M;F-O460T)15?y_u=BHcg-J{qxL@8r zi7A2m9YFyPAIC-=zkRH6^k6O^2rmgsQCq)g;e{2# zd*mmn|AKweilob15F<8DpJABShiiNjW}9ZhZ1Z(_1J$#S$s3z$va#D;>Ojrr(e0yA zU&-sy!nL|j>8ugP2!Baya`;3eB=SKOMczZ!tb{E1WmvXEln`*L)W2p@wxpA!kjb#F z_Hp3g+}ghoHj$juF~>S)N$H3SRPbMAr{N6wD7<9RbzkB$LNe066bglZt^EtdzOnNA z>c-3|9lOKjw=2Dqt)20ESYdJ6cs8Uk>ZgXNB3iHZ>qsJ_csE3}^5ApJE#~xDiCAyofv%j>&qqF;4bFi4lUbnPfodzKH#~;2FDmgp zs(g~GCgUj;`H5twS1(*d^Qb3rdbO7Pc}nc%F9ziUfc2;?mhR@*|65@;%?J0Is1e7- zVJ7fKFp4`jG%HZ8`pUS>lgQ>DAE;1$1sKITMhBvC=S4KzNfB}*t#+U~zHhP}hslCF zTf(iu@6B3^W-yPij}p{CRnG@1z@2zcZ0%osTO@egPzsT~G9>BxuZZeWYoU2{7?*09 zIvwL;@-u2gSw92LtI2OO$O*4uk8UJvAzzvcXE4E8Hh>0vhO;*7hx38aS|$-_+!63~ z-Yvw{-NYPb%a!5C8`V^VJnoVhjP@ppX6sF|kauYPWwt~XofCyJiR|tSoI&ua#Akys zqRx5~aSW=Oo3OO1Bt$(RQvNHvCqY*zu=U7=@SiP3wCN4uejq&@Udz?KW8~Xq zGbjt&S9|1#Fmtu<2*U~vx{uUSKZthy#o9^cgGgvjQKAUpBYc@&)CG^<=DxLi=%# z4~ubo?FHwkB?X*s3^&kNS|%)-W}B&id@^=pj!G85SHDmw7x46)M>7y*y>6O|xIBv1 zpXZInS;p(ZD8!i|3K=OWN~9u-O;18ZOVXr$$e&0vBdOk{{<(y=1cEljpSus;R%gJN zS=?rQMT8=2uDL<;Ao~FEG)1<@9dbA~2;59OnGP+}2#pgFWhZMo(ob%IX1T9JIN92v zdwdnNx?GBAQO;JpJc50hi7~*Ih_`uM%q#zk?-Zn@vJ~6{^BQc%UM*6v4tT>n6^+CT z`PCb}DNi{gJ9$cjV&*T!l77;lc6|@Js;cA0raYU4;;$(lnbWs{H$Q-JyBj(EcCVs` ztj$6!vPy}sy~6d+-+NAc{?Bv7C;r4k4~e0(YC^ZrCHzWwS$tVep>aH^Bb_N&Atxy^ zvpBr{j5MdQU3{Ve=i3e@9e^g~YIi4CAq#fQuNh@ji65L8jG5j> z_K*zv4&+Gk&BGV1ue1rLm7Q?f0ju^RPpsbrPomAn(XN;E!3n#u$(EfZ1iP(d+lAB@ z6?B&6bf^pe;NcHt?hSQsm`aZ+1HEJEL8PymN?(W@Az1p6G^87+(&wxQmp^iOZwRs0 zrQp-5!R5hOA|kI@UvZ96bBZTcjvHv6q|Q?8z=Ts5XjUgHZ$ojophg7e|HH*o3R!-;gxQYB=qpFbA^BC;2xZ)yGFOC(B6Pur`$OT-QB3* zZ1-GgHo+1z=YMIUQ*p!tep!Slns)u!+ntbWD@M=2f&bY+c&nVmxte)t?c|LFJ1rkn zLjTtdPPi}cY8UHl?Z>VM8o)EkDI%d0=Zoz@zG|s2=xhi!S`XAa?T^^QXP$F#D^w%5 z4QrzYJ#JK?RM)+@pCx8sJI*L5v|1h54*iaD?xGxN(ZG($9MsuCISs);GPe!7CXW-G z^VpjTE$>uX4(trqFIuig{ka2sX4Kz9^}!$S$7ofcmnvnfkOJ0vVr1<=Q+S=!DAS}y z**;A}(E5GT(wi} z#`tuSh@0U?`PGI3b*ge#jQ9k_<;fzG6w}yUmW7Zbm$Gv(X||V6@}0Ox?9ZzA7k{u= zxkctl3-)Aws6xu8yD%G^&fxKK;4LC+h9g|Tp$&2lopx!RAip)UpI&eTMV&L}_$pvf zEXs@yZIjcF-}Ut}bY|v2!Vu0b4{F%EW+(^miNm?Q2_y{;X^vk<{oIfSe#|A_bS`F5 z0(LdoHw@RI6F@^ZTnA6>*})n9_kgm`-wp3`vX7EX_-u0Tl3_i~VhCPeX>mvezkiR| zH+)F!JE&~{FB>fHUPCc7ZqkRB>$yo#CZ&ogox&- zD59Uy9Q@wZzDt&&VS^8+k-aCpe?Yy_Y9`gd$p)1>?N(bW+qq;bHoX7yX*xx;tJ0?M zy#LuC4iC=8nGRk#)3M8Y#UK5%+d$u5cmFpY^9CI*StfB&c^^OnTke z@`CMM2a_7Q5Tjv&|6(52?A$ty-~j*2DlKnxG<3DWnlU)n7Idmx;!GDDh@SRjZy9aq zlHx`pr$G4A(ESwI#aE=ylHv`{TF^Z0APd z{K+b`w~F=TlFJ*rr1GK211i@!1@|Zh#TE>gS9xL^yLw9w%#uq4X#PX7TSj|}$%Zl% z`v4z|TAAbV>^>tIaNg!^@Rq&@lEl94=P)|lLA3pxymT19DV-!Q#Pu&4nJwlGJ8-{j z4ojPlw49MRq-~>HVFzo12~RG?J`u3DAo(;n-34;;FefDs)w)1?z8)ttqLQ`1{xnvz zvFY`1wqc6A>;z@4cEb2dvF)*za%L3|2JL$e*1vufFYfQCEkX6 zl??vy9sr-~C5t=kZlmCHSslFhY3ROV;G4YnNnq(xd2W~|B-q+vvz{HyT(PJ#@(^Ep zt?46Sj#qso=}6K*l#t}boh{udk~WOfDfuTml0wNVOs66bO`vQNEBn!rk62kRtZ3jw zVZ2T2{(*$sm}oCx2b{)9OtN$E-cyhPfA0!9Ex?pV8K$h*-F~MASW>gH-0^bY5PvsL zgY(|Q&@E~lFXLB1L@H?cA^XefB2${G6!fiz>gIp~%8Q{vfbK)aZ&R#Abf>KSG>P()|O)o(O8hzlhjH_m11i zlb{d7kWl+T3*H8uV7CE-PMp|jVb_*Rxid<*0!i9vE?s|CRDqV`e!8%iAqgIzEbn3^ zl5|9~i5}N|U|&O**mpE(JMlJd zdyi}Y9Xn5H3MG>Z>6}`ami0A&8w}2u7qhguVLPKoo1a_-I<}C;gu8b^$3h91OKD)< zo}l*KJ?~uh=}arQjXxgV_4(fC}BnJI@=59mc3w{inTgG!rQWzIqjCU4BCg zM$~8>2mM)CGd_dXF<%Gj&jYPX6Y_}Gy+O~n&^Kuo!HmI=Wcfbj)|t9dqZjnzKcTIC z%1#%(4VqP`+y{hxlK`udnkd}zMV^S-QP8(UuljJ(VWMv-)@$C>7QTC~;%`CeH0w2t zG9{F}+=MveQBXK4{n25fa43yiFfIxPM&&RLcJ45(`-fz?4b!=EptGMdI{PB%-21Me z^_s)doMJ4uXTc)4b=x)PShc35zPvne$Z;`D>2PP0<6@vjp>&{g7l}6$rGtg!8r9YL zU^_}VUWxH(j+;SGW7)gvxCQT#l+vN4cUb*{1A7%Z$ZQyURmH-2@SqwwLv=UgR}Eyy zYRJzRfRe^boPNpby(nhmW zvk|_8oFHy2%Jmw-1-?e}YDZTjb;Q??7QayZ8qv<6I>;|CP~M4lf`%r7JB&g8z|GRneUuk3G5*vD261h-R+Q+Q&p=(;;U&sOHavkA*YB^JIx(vecTU zD)JWxiv92#3mL*n_MI*)XWw&$RsJ=wr5yTTb^n?V*El6cGtaxms$*NL`VlkZ*6cnb zK7X}HUV%M}TQy>F2YLCU$}JwkLF`K0J>9jwp+_>2lx4aJdZ}WvlL~vf5OtnEIG^eX zhcJ=hyDe@r6hU;Zb)mwx-z=Y+w00&t7 zK*{HT$E1TgVx=P5KAcb&`{f+ev2rni(zQH*8% zmq?8nQYliKcL~n!B*?Q_d|%Ij*ddB@`NAuJ7P9i&O(-2b^cQn%{S9w2EQw)W@FnRr zknzY)@zecsCF?JP_4gI|2@v173cnT%!FyDH!zP8{0V%h)tPwuL1Bpz^oee3slx%)O zvr$HwJCzYpMm(@^stn`*L$ei07;?qyu>UN%suJH;@bMn-?agC^MUZoI8NYBb*>@Oe z>ELf!!Znre08#A0)fWaCjSzA#Hm>0ku9pDJt zBJu)4bkkRUhj2RC^;P{YbQA#+QUGB@~z z6lkMdq%oNrD?I?LXZOltpS5}aNNc;F#>MW#2Tx3AalpJs^Y!t14>zUXwq!U0|Ldh` zcHXnPKwB{^1T*ApN7p0-nkN8*tS}6gO~Qcmk#B>60)ggL$3mdQEw*NRUn zItlSh=ViGZL5?5mFPLk$zHE zHd7z@aCLXfT?&?S!i!JuyG!x?^JhD`n8$al$+#VlRcwHh_Ps6 znA4l}RKDHvN1W#QAT>T!*c7M|R?9+s*mAR@2fVMH=^9(X_u3mBj`p=&Nx@o8{z(Bh zbhRaV3kD0I_Yyr?G$=wI&GF_88klVG*jK&WP3~RX0ZID~I>)E2VV?IK-g`O2ICPKk zVny%x*T)#nF?s%&(FU#rtrCTc( z&>5`Z@3RtP2ctKSwSiX*3gX)%@UIMS72n2vW5b}Pi$V!cu7*sW4LcpQSVR)RzwXZH zE}?j4(6>m~B8xjzj#2NdW^MP4E8=dfpg2D<@wY*OB(^IUylx~tfl14lL6lv}5R zrYCDK&c(2S!2Sf?AwuX0MT*AYJV6&yV@^$C={<;L!JMqPmwF6-U{feU`D-JPvw0!0 zW=wDL{ApO#JD=>=Lp8nghEs{g4(&r)8c8WgOGR2LrPY5%UIW*i;77?L8rWNKW*+_G zum9A2rn}ss1O7xqcS#-2Y-kBUK9Bf{S)Pv3x{_bu&2)v|lHKHu%gWt3Z@i=29flYc z!>>P~bA8fs7Qd`vtyGxNN+>}pK}==oaOMyq1t>+*vof<-N)4rqtYLE6+e0F)FL6@b z(^V`jCM^r|&vZU2EfSW|ja}WV{dtHxyD2Zks^;3Xj?p2bQDf#@IjzgVQFq)G3!PLL zg2Z!I>|i9`7YrHk*4&pd1PKXmI_37$?)l2fdm_5$hH;>fMS3hzd^wI$n|6P^QQe_C z#LMx*6+o_?q!{>bI@;C}Oi1>?`JyRX(s-3(i?cw+8^=Bvuf_u$C!MGBC*Ai=^|vM5 zUpz$$c(ls>*sQ;6#%NE(p$R!pxJvzXn~Cca&UUX3zj4BUc9%@08m3a;wSLy3K3!m1 z)EOZxJDp$q8N8EoyBCIgiVhr==E#XIoc4&Qe(p{#M#1k6P2Dxv9V4aKz)`18ef+uA z><(~-`7fU3VqW~Y@D}$}TNRt}G4hvSZkP+nsKP*?-oI%_PyBE&LdbQs3n{`oPOGV! zEEA{Ht$ghbU}#!Mi@)kHR(m6`p^2~L;!_p9Pk3AScj2OS++nhCW<-Pl=hecJ+Hpq` zMVjL8X(Tnp3k$aV1^4BR%4Jm=2~QPA_R0{4xDVXPOMhR5{<_M5S~iNQ_&1F6EejAQ zJ!kWHTSEP(u(d?s4Cm+Y+AI!6Sw~w^1l(@X1nP)br4(UB&F8pt?Z0csL#ig;VCsBL zNcCk8sN`&=ty;upfETm7S+`KUNX7v~@zmUq=QgMzPku&w%&qNS5QeT6*2svC7>bD@ zXa=gS*AGp!ToH=nhr6}mGCGvm+aUyMs0>N6bVpC+)ZQYSJmfauQe_R{mf|ZX+(COa zXphIJsXb@7y(HYyio4(#2jx6k>zBZ!7++ECr(~%DrHT};knSxQy6>D(BVSrLRfBLZ z#-~FKSoB$qtEj=FhAE6yJYsj=?k=4ARfAtSQyBN3-I>b!Dc;p3yc>R3xw;TD__op~ zxJ3t~hI*Paq{nu6$LJj6953wf@V~8}hQX`#m0mXQL34sKf5?SVferjuxHoD$XhDUv zoaj6LX4&uo&=8kZ2*HL{JhBpW zLD~~|vGtaG^U%EM{J~kbY;zb2A_}eC< zkn2VPo#F0TVJPx19^hi01zZ#ks`v%THr27u&v7K(eQ_@{6eGef00qFod_U&@pjP!#@Kgj-SL_6J#Fjc+<=`HLpMzqU2XQbbc z^!q6`2JNue80r`8??(D=>3b}l#c+kwwpU&2! zedPp%(*Adl{vC?$KsvMRhTFdf>G!Z36x+#`Fg1QF(ybK1hw?14BV2wH(l=53C(=zb z%HN6fJL#@Yq(i!zYG*yt*VA89LOP_YsdNj{E#&J#IwXmy^fgFdL$`M!eg2H}l}KMn z92e=#OCKK3GNdmftrzLIeOPH{&?T8@NBT@OMteI>GV%1cE!;Q45{SdQ4cNi>@3^Z% z*&}~uFsh>0!O(`M!u8-K;Tjs5OLL>HxhVy1eA&p0zof%r}{OYA$J z#ef+;tFlD1-}RO`?AvBRcp=K|uqsr4-)AwStt(2@Y%bTI`%ziX=L9w zE&K5e-0h)qi1F!O6Q(_p5-+=2VAX$O(c zDP9zVliV7=uTJtP=;649mUfkge{lz3s3orp!4Nq%#AUq4yesgvv%A{=^wd++_tz_T z3ii;vFwV;f3MK8+4?$wU?t+iCuV`pCz4a9irA(zQ9a_NBmJH!`H@qdIT>g-Vw|k{U zp$XVGa&b2t?wTD^L26M;I+j*~v}H)c-GGDf(t@e9=5X3ZDUE!sBe)43u^YofMF-#| zaJ7?v6gR`+j6IV9A|QV!06ScVR(6Abv$QlyL;U`IhyIPC8WcRa0eSmI79Pl+H@4z| zfA_{AMNa9g33L8Rvf6r9v-%cknK^bxsCStX{Y1a8N~M@v=}>Zx3bV^3`Ftb`{=4ls zFQgSuKtjVUcYs#`a#z4ku7aK1B%T;vk9@WcBf6#!5uwQbP#$($Ea5`xsHgI zSi{Bqa+UBFXnKM$AR5DK&j~%?m!;Pa1CWaf25zZ>H7@^e1VdIn*PFgvyaF3aHg~I;hII?nz zM#D|mI8DNN;j-|m@EYy?fF8c%^roW}H^Sc2nq0K^ne8*RsesNh1j>Y0yfLnG_zP6k zWL;n~(sjs4$wS%?E6ozN!uaKQ#?QHmaTt+T~i$1c3u1Aw561!YPa^@Yj<$C@Ax_*-gz&e z=D!2FpNVlXS7`y+Jlj;2h@_Ux6u|}m|1h(Y&KKHXdxX#0Wg6h*l}JyWvY$d)awSR2 z+-Yd3i)oPs%*Wls?2*_buujnFgm^(Ei;c$PPVDO##KP*1tC@`)H}4EfxwjpJhGl^a5YGr&(K3eRE;AM4v3j<&ca|AJ2v+H(QGT zdfhE{X4%~7Edvda^iLn>S$#uaH0tBTq+<6Ze-+UP;>kU9R~W(iYTvymb1%x!E>}TU zy9e+ff6_47nHze9Hs*8Zq+0x4lW70SyqsblWdmZaZ{P)J7EJpys)tgy54N~@|V%ch@sg^ znl$+w2{Yup8NRMz#n<)JD(H%fV7pwjy-SHOb4bSv zAlJ-xXSgJ!rw^@_(r3oAkc_@lI{s6vFwUqQ|LIV@JjbP#=1j)BQ2t2V`^m#r_}N+z z!LlA_!sCV=?0t({!@OU)4K3bzgN?9f8a@Y2BYP$3(P0_+o4ziCFRWib_J;CzbD+)U zcg%jju;w$sjx6>QgE;>%Wleoe7NExQoLZ#Bmoo7+bUE2ZO<%K(MjheFa!GhLZJQ-KNw=t+ z7(vg4unj|UUOsIxHO=UG$_fgL>3PLs>cwBuReDw|rmWwjnHE#;e;k>h*aP%)YT8!n z{S9oTH<-NsFE~ecM!4KE;gTE{Q-NI%f1IW35LpCwo;f^m2Rtpy%N?g#?7?%Wb=}N0 zs}K#&dw+_Wr=jcIZ7gT@($jXSSFg%6mO0Pxc`Xw*Nj(VrXAP``md=~M|aO!I`(Vl20v z(Y@)Ayx%!iaQrT$-}8<1O~(p>7uA!gx&Y0N{$i%y>rvUxdDk4PK7JSbwF1A&j@<>{ z+*w+i>6nDNF=j^``#g)KOb}frEPxjxLokTv_x4ByMDy-~ zUMBp(dVN*-cKIN`UbCKCcD6yrU##Ft&I((I;&qiE21+-=Z62e@f|>rgz2^|$P+)?e zTqxxh|G&(=3tSXeo-bb2)j;!5x_Kxlp`W}2>887R31UMdq7nsKj7gkK)75|vWK=*+ zqGCofv8RW@glsmmlbx-E-I>h%XNPReW-@d0yv&Tr#NCTsCS!I?w&?~$L$aY;5tXs; z_f!GmV;;Nr-p~F&d=B;Ks#E9u&hPxr@BE(JUS$OF;c$gHcz$sO)rd%xd(s7cIV^7- zJfc-ldvc5^cC~#0m!%KKINrxE5bn$N6hW??YS)Ce!I3BL+E#I$yw*w?-Z_BlU*Q_? zxn(CsXgOasQ|BhJPIwMkdFza)DEj6phfJ18B(E2qw@nt7Di7?JG)##-?4)OU_%(s1 zC-C6!8>S$E(sfFdHc(V2V){dO-4HA|?=iF+i2tsrlRQL2@?lu1b-U(l67C1TCB&F& z0skkgBeJHWgepUyq`F(5^w-NFHx%;v2-+mCE%GH1>7@|k6l>kAOiNtjBtuLwY{7lVRBAkK%266w3-Y^!Oueox+fTw~mgY9;jnCdM z?yf-7?iYO2XhhbnCRSbU)qYvl^F0zJ;=TAt-I=~cMcVGLM?JSDMIr<=q0_F?UZZiJvZ<=j?$k z5jk%<`MYJ!d+qFQJF#NzA-)>4Z&AM1ePt-h91qI`2h~a}6QE1l>vc%X9kjlRS3awU z+#5M7&C^}7F$u{gK}CKEJkqC^y{rFWPdMUL$1mIud!zH*g8lcEopOiGU5D+@m_6Sg zUv{eJzR|9c_=Uvw=RCKxGYZm)=WlwcU(q^3XE3KA`z3ShI`$gZg&4Xx4$j&m&b~>a z4idhzx2fzDI50n%|4K(eX-_yLCbPANb;it|cS^|4VR*4W-tJ#mc${XIn9l2st^|0| zOI-^5k8mx-|7ce<{x5(}lpi~wF#L~0d~pA9WPv4_jRCW9tRB!`Zw{250`|Aw^~Hp_ zFGdjy?%~B{@4D6Gc{bP-XixCSI^$<%>|%mBABO`XF&C(&02kevK!Kjj=LxT$YYKEm z5l=+ebo!wqFUS7~R~i1xTqXFwz*U6*a#ue7$GeR9PyeU-q@U|k0@^8Q4sa3sk}-PZ zOtNngte#><4)7RbPr48jOOTeL#o>1UX#B#K@FfN3xv+gPv*-IH$Kw!VK6$QIGGk!5 zS&)O4#q~svFnrwHZxTd90v7QLJ+N^QAuGn+B5`U&>-WR7e^D9XFMkU??YqbX^}`;y z{)b%bh5K&t&A(eG*k0y+@@*>bcc3Hxu=_%M;R_zqXjc<>R@h(l@`lj_{kLF$^~3$~ z@P`*|NwWp|w?)vWlY1Gk9bE(Zmkzy2x}Z17e9m$~;?WgM3%Y_vVzrBzK4BaLJv=t6 zD<@HZVB;jr2h3wREE*C;8>gPIady^Ukp8MY8uyGQ7&+)V>`wdqrv2f}ZpB1b) zXKb9}xucJVs3UaBe4WonGjx*hb2BziTSZ?$)V5QgcH5wb{(E2wv8{-3HR8X_^$7ki za6OFwa@Q98k9RfTKf#of0``+97vh{?KY3Dy{{;I_3fPYx0s9vTWArAlzYr@Y3mBNj zSuU^jWc|cSEv$+r%;(Hc5eg|-3q1vEp%jq|Sy@Va{_#|XJHg4@CZPFz${oQ)3=xY= zf&U?ECM=A|J2MtWD>LWoKA$}%`eP9~2@50hvxny54AK7xP1@7@?#;cx?}d7L#~jZ> zf0}=Z@#KrU?u~ILw(=(MEajk^kSlsKpWf^-?M)J?R_j`p$w&bx`jw=?S>o&)7VSyTV%Y(0P3p`NA6z+??p9E zK1!=4#Yf#kuw>PM*72(Ke)y-*S;CW5-`T1X>Pi5ZqF_Aa9A*b?gz&7)S9oN|B%*Mie@3q>? zb_pmu&AkEn#?;LDt2nlSodzR`|$@ zGJd}^xSi?Pq^we|=AKkmJe$u3w*-r;s$pN(6IoO$%HA3oL5>l{ZOfEAn_gT6TwybP z&uWCTM>1c1HdQ!F9F`0}B%E!^d}+7}XA+;3+mNXlE+^;1VMNw9T!?dIvg0Z;E2#7I zHxT*ZI~4p(-+PVt`weH}S7tb(F2EVp6~hU*T2z0D@8o2_t;G-@YOFE zN>^@RZDEV8l;kG8WUth2Q>K|>h{rX0S;~DwdEb^Rwm-CQE`j~B1E5zc7-8!#m!@R*tro*4TzX}R>#4o zZqrr2>5RrvAt{tDF{c#udQ@yLmGxWNbet-rmWcBgFo!RV7;#``9B;zrG}U&cJzkk# zf8E&U?iRJ<;E2hXLNa`i1|^JlQ?34I<4P^$2$)6@hyur|xBI~#Y(icwP=(=QrG{In zTus{{=h;f-1s<90%W!(+8tD?u_06Rit!H|THKkg2>b73Cx0)&{trloXQ4ZiJRTeZF zlj3ZXjo;M9iMHq-3-MF-MrBHDS4s_TrY>L$I*e-Av4cOBDoyo)y)nprcx6vIWC?xl z4?f8UE`>eYSZV?Mza-Wu-WrE7^5*tu_J*{G>A>7{7>y_3by`c`L`DFFnYkD`o5l9F z(}Y%W{{uR=nnn+k+D_92B%-=IZfGa3nen`e@$Q5@QYrFo>BtfAVu<~X#H@P-dNw1U zJldvzeIyofX`+V}V)xF&i|UEE3gNA=R)*R3n;@8tbGQ>uDOIrettcfZ$b! zaj#RW;Vj?+((11wgISHn$Q6^_68X(ogWq6>%lW;q!}3bFfA=!nr(P+c|LqK()B&dZ z8aOvo{I2$sPMygMA85ho+$G83R|b2G%|i$K{I z!rnq-n?kv!)qIjnc0u4>1Mm@Nd^+ez;8W^HHd`X0hb9y8lt$x6m)1}CrN+V@>B!b$ zXzdZDH4~8yqHo3C6ZGRRbV)yn5O!7YZ@7>!+APS`92_G5!p3xR)_z0yR``XL*lW%_ z(Iq`6#29clz)Icry;f&0@s;k7U?s1*u-sEPx)XYd1nh=iCtB9?=Fuf*%{9>@T6iCr zM^ZmF0$0qSgLNaR9z(%2xVd86<#FS)>l{XYx#W~&2_)JDBm#?h`nmFvR6e5%654`k zytNMU#rsISgOM%70{$GV=mpE0>$a=)6O9`Bl*1Ua#CRI8n$Y?Sn4lS=^skaP zU{8%~A};!;PAc-5gi;mGzK*e?o{qgYuK`}>S?YP}7@a^%XL&P`m;X@=mG=(l*A3|5 zF`lz~U%@x74{~?p%c-~!?lg{2&5wW%EHt(fIle26Sj*+LvT~d&iS^*jd2-_;V;U5PW&H4@PZc`iq#jz^5Fd%}xr5-o~kqk49= zxQ2hG-Nl^M5-;)*nq{nt?ucB;EAdYLY$~x=7(q;VjDx<3Co)T?kLV|zKKLH$h9m6OVjZ5v`?$4*peQ-uT%CL{p)rK8-pXP2!P^NSgfo!sU|ZB2X7N z4U&1v6Cp-d5@g|qfh{<8pg_0s{w2!r8|aySjW{*LPUN@0qztF*V(UL$Ky1&Q{9h#y z9i5XOflt?G(d169y~Oh`J!P$EWS~y{n0C?$fE#cI)J6udu(WlJ`vO47+JVuluWw>DwoF zOzxbdqFX2Z;hqSM;S3^}sVz8{;vDhK>~cDvnFh~ojE{K@hu3HdGPX7=WD7Qu_wSu= z>K(o#nz5ezW@hMJ^*MJQAlrrU3=nOCRZafPrRG+Dg!Up^f;?KX)uo`8GEmAWP|6tM z6H1+F0Ajf?-GxBf#{=yil=5_07z)ns?*^m}x>u=YCp+L-3vrh#`JcAi{$p=aqZ zy@Q9H>5o>Fty8mB6W^V!uUIj@;&(kv$#+(qtDh*%5uUDuzAmtrHu61RK~@W>?SR$E z+*|%`xxS)qV#WCt7kVV6DJ#ZGz9YPqAI}1c>7u9UPqvr@xQBq@8OGPDiVAkvmX6=5kBSqE^uy4Ng-?xGpNYYpZ8$#aSpVe z+-a>*bkoCKtw&41!(E8jX??b!X<%1i`@m{7`D;75K~4Vdm)$SRdUVH0?iN$vrV%02)4v-wIBl?VhGT{AZ^`NGNdp%r-I#uKzvZXvvYV4$ida}aJq)tgI+t*IBC6%R`wc$D^Z{FG?F}D(} zCh9#hQ*SS{I`e7}-)OX!mui+Wt=cT^Xt;T{<)q#0_l1@(3x|KB_zl>Z9@MM|*Ri~5 zYremGXF(}z@1(Ws^8M+Co@@JP&LMt*?KQnjAXQn)wE7~9FIVPh*KwQB(!l#96Ba$* z@WKQ5l9AM7YE|YrzQ0$7cI3cfF1gXFRk424c~h(2=;*T>_R@~*@*GvN zHPC9!-|v<lNA_C7ijk-_Ni z`H#owM}iLpwg<|ddMwnv;87Y+_2KyqPQUPc@7rsgencjG${j!)rjRS@kzm}=!*ZH>?WjU?`+xm*mz4-^{QQ@~Sef^tPH%*ZHZ$h(F_Pd#P&I|91zj<-) zov}ClGw+rFN^+eBTy|EZyLxS zd+P;fA9`e(*Q2D{d(?7r+d$Fz=A(G}oU>0~ZytZ^n;nQfER5w*^VnODcZj`?6>Hr# zpm#X?ipH9bzSU+vkNAE^TL=udna`ocACh(9-ec6-ev6K7#~QiKx-o8;?iw$#c1s?* z`6BFEORb=wE2zlmpQ9fG)DjEmrBbq+sSjAxl2a&444E;fm?cUxiR*7D&q78wr=QR6 zn(7Ei*NAWsVIkys$`JEB)lMRxkY;|XkpoIMlLJbS-fl?LOKd^NWhRt59P`{?YYY%? zO9A?YcHTnmhE9h%B#|uUaxr4+wd9RyS3D+dP@M{P7&BE9+vttYS@)E(KQK-MA zuL0vo#`nzx^El>1*yhU^*Fa0(n=xd@yIw>Tn0v%Qy+vJSXzJiEpKG#S7v~P&5_Hy{5#sWwQu)78tV*ps1OxEt~A!D$)EC94y!V)QM)I0 zJXX1=W1(`5v)y*N8UCS7)=gKcWSy~9GP_OnfuWTd*BLtK`hZ!}GucURp4>UmYo?JEUYBhK0IK!S0KlA@Xc< zYt;gM<74!(yaRH_ARE*~FITaKWB;xy@mr4k`wG(`dCxB&m3JBLp1lO-Vr($*x2z}u z27mWM(4FVDgU4&_-6+JuJ&0I1yMmiSHER7g4@s4)NpBqk$R#qqfw^x6D?o6a9o zHh^FLF|x-MdF8)26RTtU8Ut2!S;_|42AN@;@YGFAera<&%M(f6rXctPHTi3_Dm8h} zUkt5H;a0Awp^i9@J`nKABU)+_BbM|eEu6Nu8)}GGrR9q!0?6JpDA}qh2}6Wl$S1;C zt0Q3A>QEiaQ)M5?>!8g8?h7r}+Cbq}ss%pu!h5RkS;n6Tu!D#lz5BRKQ_?NVP(8B< zF_RE8k(D3YqbfPFr&GR>&xh`^mjPWJ_ib3~5#IV+giCvRRbElaV!#(V6)$b5E@SY^qli4Ws%){V(@UEe z_g)`u*1Ep|DAOaB?Vl4E1FItll(oMACKvreb!58i1rfH6lOiMm-<k#tt?H~WqC12S1z>%`v=xxfnt}9aZJ@lIJTR-aEI;+h99<#8EL`>(6ab52#S1b2QZ+cT=onCXSQ}DE; zU!>sw1R7;5%GUyp*8s<^d;;CeB6N!hbg`55tt!sI-HYU|ncN#D`fPZp(A>Y1vz@`c zArWTcyjZJHBC7?7KCtck+t>>h@TWDV{cfi4dH7o%q@GaL0N)=RmYIuIE4riFHncu0 zj=TUZ#tW@bth1Hvt#H_8Mnb(Q*b%A=R;tNg-qyTAxAcqHm6=}X>tmg}tSc>479`X; zZ&^ zR;H|0DwHeg1Mq9g914}kDB~M0D<4#D;&v+QsUxMltH?QEoARbO433lIGkF;U8s zHuT{8-V}No{EQ(W$&aW#ZF3Pb@6{pk&kWpM_ zo35KgG?u5kitaUQ|C4wWH6M>&WajYb<~%%_n!}^u93BC;X7C8O^%vuj1b9R%5B_CX z@8L$&qmfMit+} zC{~5|5yb?f2tGAiZ^Njhf>Pj<{kZHF?GdmlOT;Qx)mW=8o`+Rq`)J_Q5%Sf0u0RR&SMUKD$K!m8||2b(sViNJ=TcX(zs79 zN=SPyb$0+Zm^w1G1ABjrg>7fh ze+?1!Hx6~JTYMP28_Gj$@UO8FI)7Vck7{YzjNp4F#obCtDcD28>ejVVbo& zzKdQicl-c8eNufMu)0>AohE1h6|!yyV^xQ-s_O*3WH*@FdoJvw?5-{n*re|uSl2Mq(>kH28_?4lp{HyAlRXt;c|kYuv-$lbJv}y+ zfBuf1EuuI;y)B;C+mU_V;s@MMF6723VxBm6k+-mv3dNuiTGUhiS z2WW$>Whrv|(5<|q4P&?DpoFb!`x(0uYn;(Bh}AJ$Cud&)4Z}(#uJ7!-h=Nmcq@n|y zBin}28|{?bJ=4&SQS?FR3;OXo`T?oHM$JWe*A#fh^6na;Cu@bCR0%z?3O%vh-4pFE zJjy$JvR-YQ5c~2+dXjQbf_~ItY|t0XHnBHZGra-ro7bD-dA%9FS8v3z@ygL3(7jzO z^qjy)!U+?Z!Oq^UuUgS3%)99wR=e%UlrZ<24$}S!;9=uX7r5I;FblW(R)HoyNamQ& zu;cfzmmzcS+>vP_Zy%G9o(pqt3U5h<_LqT|vs*DUzkwad%v{`Q0SmjF{Z(~jhV+@TG z!w$}l@C+^W?vvb0$_02JAFcjlz(Rltfyj2yp`F-!b$z8QZw(%q9;W6&q-6#otN;-^ zAo7R+k%#{eAtLPjg*iJ99t0xCrp@E`gox&lglz;&2zK0>xgu16Bey{XxH1P70aIq6 z^4YziLU4s>-*^)ht*sFE>Kip4Lz^ad%te#fzMti8fMp9x1d0qL5+&+<)|b*RkOS1o zAEH3h96hp85w1$z}fG7)K8++m0r8Bk)W#o{7Zo z3to(0_KWmw?o^zoaNullz7Xdz#Hz^2Hnnf--End?RjCsTH(jaYzfaE1h(abFdv3s! z{<$?&m97By#ADA5*bKw^?L`^17SOGXp#|J$BiMo6YWHk^h|U_8jL%^{yk04-lA0Z` zOSd}NX03zBy0w*IRbl+2gOBd8?qs)FtFx*jt0Oaux3g=r)<&+4%qrGJrbiYTT;GI! z^NCw6t<;g0%$C8Oy6E(1$5G?+z|AZ_gQ$rbe}S>kmEu=H+aG?BHWpsZ(&EN zjgnJM8{^2TZI={=MMO!K6oiFGF-tZs(qwK7?~cqU;UY4=j~$=%0$oL~Nvn{%Z)E?- z6`Oz9+{V|N*0|~`>D6hHth8*zG+0+;aIYJoxUUaW1r|45SnGN;|1t9pH$%yq9&)bR zaT22ess0Fcz{^)@tKCx8roG>3TAg{|D}I%!%9Tj!GF5eF9-wE=v~y>O+c9%5`2gL2 z39~2<;}S29OLy#n(9BuX+!>-{&Wzglflzg#D{PxvtfLxzu-o33x}x8YhL620f1#H$ zFC0`F*0?0(8{$*sjFiDey2$FxYFV{R=dPsIQI%;sp=;efxowi2bRdsfdc~r;mdWNx zWLulGLj&8DaET%~(0_h}%Kpi+UuOR#>)V-sllATNb-Eqt>oOind+cYQXFNnv5g{rz z1Pvqh4pb-vbu2|;P8<=g$c1=r!BK@mri%2GOE7{S?c zslLi073XH21d&b{dwip|Jj@ctf2TRVoN~rjW>qHZip!H)7z@?Jtj=1UoK|c}G89n(f+)4=g2-lHuPo=&SwbV;4-JLpPz&mS!G`$t?6o`+SLE~)2XLoS|8 z>%eoC!x{V**6+>O z4y%lK4mM~w#+FkqNjcT?2YeZ47Myj4`eFBffndsSy_7xPLihg${fZ=i{f8{HaD0^f zx85qWm9`}J@3bc0b~m}-amO9wYaaweI*B7 z^&oguP-kX_Ix~bib2i~!2G7u(0?#xFR2t8SM?$SRE9ANqo#mytm&(J=Jv>BlJznY( zb#4pJzU`IGu0oryJTSTGYQq!<%07E<)0KwF%~v0o0&QP3pPFOO`>}qMuO6)4VO_T4 zPtfjf7aLqBpw)X8H2fzXOVRU1ITueCKjnITu!PXQ*zbwAHSvFhc&MFyY6A+PoqAhKgrJPCAd|tI}j=|xt;;dOC&U}9N5fcRm*#> zt4cuiB0=>cK2iz*|c|XWs?4l0P+(IgHmLksn^3f9fCLwQ&b&xYsJhAF5ks_+y z<)Db=X>H&?T48@Ok1kH8$1S28K@n%?4WW$0^L2*q5qfn5goRXK=ZLhcoYpL(lJle$y8rjZMdiiVwH6(r zToxImfYgU7Vnadb7S_C{{TH$BejD^Fj?ga)Ey)56yP?V!XqZBvVG2WzMd6}g0|7oq zxF0RtkG`9>Re`pNtCvXG1n*=rCUbLh2Gaq-+ADRFXP17olw}-DMOH;*MWkk}>cO+g z#_9@%J7d^pT;tN6O;oMlJC19`u*zt4h3%y)~TH@q4}CN@Nv;=7Ohy&HmvPY9Ol&q?aWF3|DU-bej=HUx)<&L&2`)JwiZ82J0E6!#ZtIuZQ+r`6UW=NXO z>Qw8?Da@OD6al(4GH!)CviAa((k!&+hH5?LLImbX*lyX`#b)HfwIEO18M!4=l?=1< zBvrN*P`kagz|b&YD>E2lrTQlcvbyLRj-D=7_LfgAU5jL);K( z;4GHv_wkFwRrS`2I9dZk>AaX+|Fi24k`(kJD5^Lxl)QL zmkP8}2aYppox&}pWQ7lv{Dcdqvhp8W@k`Q{b!f{h-MQ7KF2Km0Na#lYr^0#=i}heZ z|0h%=){t}f?PL6Q7Hf$I_Xqnw^MYGLo|vxy9PniSCHU+Y;M4M7fDbhXA7MoSw~*?m zFnf_l(H?zfA>kGFgfQPS$&TKFmERIso=GyNs+L+Ptn+Dz+$mA0=jM&nNA+LvV*ded zQHk0}1S(L#d37!j>$IfJqYP0ZW!NKdJ*YoPwUpFNYNe2Ej*1rO!r#r(h5rhSfIYw= zU=qe{&3_e#W+*x4$X|@Rg)PE&_x~5ZJ2y7KnRURKYOLOb53vAu%78ni+$sUTN^!n| z%fH39VDFiWcXIH~GO|+PoWVIwvH}h6x(DB~ejeX~XuE^Xd+;qKb9~DWz_&aEZpAI| zEf3)ATi{y=PcplM2H&#z+5^|`=214rZItlq2iN}@KIL2J>GPO-FM&^a)nhGsp(qnP z#jpGynx1mKIk=MWDuoRa>=X-DM@G?03Rg>`B9;_1gh#!5j~8T$+FVJgx>{gD+v zTUgnb-L>MU3HNpPUhxS8=CAso*Ewh(;kD90x3wH{S>dQ5_=;mH@HZLHEG8DKSTpDC zXUNEb%7owI!}?E%d|3Z!@L`hv{|P=!+7HW2P**HJ^Y{vZhpxR`cSYQ3BBPQdPC-_G z0rD~AE){JFn@4N@(shyY(zD}8MgQ4#5wucR7X>=WFLf{$s*x$rDo<8p51VIjq313o zpPYwSfuO(O60B*YRDcJtrp?k~q4jyUs4$==ITO`Cg1H>o|COJfh0z}D0NvC5zr%j< zcS8UPVA4f+f{2i?{(v_S={2Gg+}4ggPn0X%$^L@8Lf0aKJ$r`Gswmu(md|lhjZ1}d zx=((eJ18M`X43WN#x!1-$LJ;_N$Ot!L?pr;G@(TrWJY1v=QYx zlk~s0!idm3#=<|AGro-?`dSA8CKK)LA0az&Xf2mQ!al-ib| zA6beOwoLRRLs4W)M?caO1vVY}p;ee|8uUZ0FtQp2tu_k1FuV)Dns?EbcfX7BZIo}J zd=up`%3~<|Q1+s$0>-OjeJ#lzqAHEae#pz@yE_=o#koCJlGg_R>sR56;DS>3ZIc73POtAq%&Hw zR9RH?#zltWE>I!J@}DpN&4yk7y=@Y2SPdR0k5SQ9h<)?WgOUo&no* z=t}G;h%Xo-R1&%YT&YZiEf>v6o$Q4~^L{twY+s!S%P(mYJ1Wa>aZ`wMttHRiWmWo> zzTQ^hsDL(sskb&%ATDY}Q^n>ATe%5(%dc5E$(Hi3m2a`)+q-+!(3I?g)yGbs1rd(6 zd18lUjW%NG+{p4VN(sE>t*wmRcDIj+O7{D(9;K2d90#~9!&oL|xBwj!@oB$s;1R^& z=#JjOJN@gUoBEuHvVEA|OP8I}YkIC|DxoKP%KZhGM@1HF*(a-lBxGMimD(0?3{J|=ioBPua+RkwQObZKKs-k?j;N$30B z6DTyd)H~JIZnu6RbJ|#2rOaH4(#lE?(>g;L-*df#o_{}KuP>CK^~$9kcH1ogs%h$e=;pD-KBsckgmoh3t?p}VT{)0L%S^yt3)IN8&SLcvq@Qr6A(xo7px_U7Pc*|}@a zXR>~Mv%@cD2REJ87a-3Tytg{!PNr#+#4K)}1XR?HfYZ%@Jj1z8Sor#_J4A)31hihmS2kU0`XEiPIWKe$sAX>(h<=^cie!I z=GHmz6f`8IC@<(WEdw@9vtM7(Fu}Kxx&Q&HJFZI#&#YK@Wi2?Kh@4yXuCU{g#-l;6@S%~(hGa~GHU<6AA|pK3r*cFN zC)~e}@($%{x^l?(+9tge8^|@zUh$ZpLJw1B?_lO7ID^2knAtnt8?$E+Q_an(3&+VE z1ATwI0=S_0mB(!xQL%&z)!|I{nl=$AF@4+@+?Mps=!OsHv_gKH1Yo- zw2ChurL5T_VFJDs>MN9WBEG?|h#`63Hc!la#emsh#H=vmn1|!nU5H+R=*l7%3Ev^u zG;=*Qa}3TLrvWeF$y=*xtM#K4{SP;4oVI_{wAs2Y@3cK$TlQ+Ve3#8Dhkrz-J7QGU zG=Lbpgo@Lg+>Rq3qS3uMQ=UI7!YR+K0HXO#{NOu)4sv;dMv6foE+S`Gx2i@a1lt z5%A;=)^TSR=OjfT#ci_9rk1uj$4Hl;)VxY7}`Wu3D}5?KEF}pr@Dq6L1pDI zt+Xkdm7U58Ww}02pKiOVFK9vxL1*xRE1fZ$Cq)`wfAz`BETSlM>B#umiNEkLzZ|jh zk}&5mZ}61Nv#ce!Uyge*xF;pBB3Y~5a_3jhnvZml#6)1$BKOU)%YHMfhWH6gtM#2z6f%q3eHW-NV@4x5SyQ{Soeej=>yklR&AP7% zEBlqv5Ufhr<{%$qx20Pg?2*R0%fNRD!MRqfX2{%)zFz>$FFcPz#)0rQGR$YPhdHkw zW#SOi6?Xn@@I3?0BGjVB`+JfRie%6k zmLn^5Dq0Dz57J7+X@GAw@^}co#l)8D5^_oJ>|-r#>9O*fm4ID)H?!S#teiu{+uc*V zcJ+cmtu^&M`qzljsLJ~AC-234tUgvwo^yqfqlKb&aS`u9)=cecWPTl^%2F+TF9z8~ zQpKmb!oVO*T*~A0 zO>b-n>SnB~z~dr{`Ujl+Lm0}Fe=lekfASOIM`Lsiu2t{U4oAU*lXw3fak!}MeV*)! z)WK}hVm@0{Oj`WTs$qEY3wP3SrvY~ghG||0jZUiAns<}zy?3r`1W(xD+ariYE_DV+ z=t0Ipa2y#WMLI(n6$jqg>u#n6r_|b#DZ`72TV0&HH!{{ADVXzmJf-VSq#J0)J6Nkf^0whws(=V)eVI zcuoRVS~@27ljSWMiAF2a&61>R~V^1?;i=#bcwDpYc#9 zeg45>aQ^;Xj7Q!GD{s61eC+**qM(<+%FTZ$bbq4jI?e*5zq3?~8KW_xpcUKD*J_k4 zqRrWCjH?Spbrbc0!7OL|N7-yV_+Z#gXd~4RnkbhJlI6_7EE9aY%5{Hf8Cf7ywq z?=`QAIl&fBFSuDYo%NEvFZspfmvXryLr)B)(y5YEX=+$%cq%OaQe~-;Jxf!gpsQhe zH+Zr*mB#3mQ?2huAllLzK8;I4IG?iSVGt$vR{I{(%{dr zd*7G2!|mP=dYn#u z5m(FH=}sOto>tBK;GXD1vR&3DzH{1P!?lFvIl6s{wKWXrYa;<7_5VYz8M!lF7?(lzD4I!y|tb!jnV#!J?2cNSgZSE zUno_Bw&%LH2>o5=*8IhNkfg>9g;L{s)3FDGr6XzkGs+L4x6g$<_^!6UB(z;V*LL4b z+kLmSJuWOs0*t&z+Z#Q3Gi^7_wOy5}MccP^hYMrW3fpY!@ftVnculzPbeWECcjX=b zhD(3E6S*!AxU!FLab+EE24DA2@UU*e@gH$qjpL7TY{Bs#a9o4qi#W14!t(KWJC2Cr zc6>LiPGKW+ybVVSj%hg7QIRh~TU6^m-s)b|%jkAEE#>Lja&n!Q`|OZ#r$zO zzRhhw>pQz6h1PFzg`xEagg$)3t@zV*IV-qw=l!T(D%LMxn+@+wi{B^T!!vs4#y|dr zKl(Oe5pfE%Hx_b;Kl6oJAL80dTKqmbRnz>+KRw-aRQUu?q5>TykOfp~+)L3G8Ee&w zP>B)d1m*#*r4F8B_Fkq86Q>=vMs4o_)3iNKE2&k)(_9j4gz?_oT$*`r-dwtjxnz3g z5hLEw;GH;{w8f1~_h^a#gpIj*qE27H+ypoK%B$;~N4t=hK#A--XqjE88xbG1?{`b; zFW;J}LaQW=n5lcF8f~bT;+(ghoj<3=C((X-?-bgu;TI|us5@TCkXCeuIhp#P8NQ%( zK4m;;)XTPD{gAPUt4Uo(AS(1Cj0-y?6Czom|GRAH?X=L_SxRAb8s|fI^uQ}V0lJ9P zg+62`8NBglXpPfY_Y4=Oi}R-;Ua3L(gded1h1Luqr*nAfil<9FN==+d8-ORkYwig` zFNJ6)2Zb|Q@7|t(Ro!AqPJz(!^(@qnyeIxULj60dO|w#o&|ah-u-OT@LVf`ul=q=W7N4cU;AfgZ|yTrMf0(f-jvkYZ$q}Q;iPo^kx4R)f zAz0bEzSX*`cV}-~5ZTHj4ohu&(Jsbs$Te`wzd#(<(gJr@BhOdmjqBqa@r?uE1&?YV z4{;d$k+4(`v-6Viy@&_EEubPB`MhzoFYfpOH)EIYet=`tltaF^P_5z%#W!0SA61&( z{qZ5%z8mY(VzlHNkOdq-RG}>{8KD}0R*W6q6Mh3HTWH2rnBVZZBn>fd(hzq??;$_yf1gjaIQ)-aMDg1*?X6#sXA#$%(xEv zcy@RAF2>Gd&6nW63fLRJc(u@`g{T>DLz@`VHewZw=s6OOS)9hvi&roH2isoL&)u@k zd!1~-8(jEe@VK9!LoqDIxY{{Vb2EcFNX;y2jQmYSg@B@!vQD@QhV}m#{M99icQsrJ@`A)3g&rCUa-x6Q2S8!A{XPi^S)F z52xtM#KW(j?s^x#Omzp7*a4+kt8$!VZsJ*iqUm4f8xScZ9Wg>oUIhA;+|;Lgo#a1u z;3?RF%di7S=I&vIlhIXoCw0k|zhWCAtF&8E>*FM;;2cty0iS8WCp06!VZ7NK?OpC& z_R?2JhCHiA704Dt3lg6q!e4q5VvpHR9(j+WcN~3;w)at|A^Ew!;wg6`WCeZJX!B_{ z;5^#IkgN=*9P= z`b_%^%hssZB=$r%1nsr^=p$1F?W`ruaBNBkE6koNo%FJop;wn9mr&x}2jZKC}q{N(e7nw1J~0yPcU!(zMlgCwlB8xT!^ zLEL4A*eoM9 zM*^?|xZinlU1CpoLy+CnK`%F$`+BY)qEkO&1F1!RRg+h{rH65ztn*?2An{2xTQDDU z$#aM3Ca+J0>#!k}dW$PU_)VJnalH>T%7-}q0s9Gaj`^nq2b&bQZlwdp7q-h8wPk-8#pdG{rWPUoO^CF>jU3WcH8pZH5*#>wWxjH6SRr2evg#CQUuy9d$6hhJ<- z5$l8q&FDiR0?)dO#-h|;7?V=paB5Tksw0&8`u`26k~nd7!`wh>V!aQs`1)K-gRfKa z%5R(lGf;;1r}^j%-VqGz!z^G;+ensj=TZ8!FLyaSlreJ{f`?or!MZzPmj5HNdU~YtC{S&DN@IbHeJCPCH)Ps3(NWz78Fh0X_RipM0-2n^&-%fKeBT*r z$fsh)sak)-kR{sr8dj;Wdf!3loJ;wh%c+|Ax;=XZD7(Z@)t77V9Z)QCE$H5dR8bhJS-L820IXs#bT-0(6 z-ie%hheRjCugKjat-DToO=R+-9*4k>#JIm?r;O1~B1yw>j51t&o-)QjhBCH^GK`-< z@gBjW0(|Sl2s#ADfRfqapMfzlhTaC_Y0p}cbpSBN?n8ia7{9vFr!LNoL(D~boZ1!i zQ~-7>56wUtZC)|kX6Ttq1xSB9AJU#8JfAl+7W!M`a>gX|Fy;5gwi#F-{$sG#0oEEW z7w}Wgdp`~DkCJr}FiM_n!x=IHqg)c;nlSHK#MqvDcH!K!WCTV5W1r@ixf*0JED#D)IK; zWf6B=7u<1uhkcW*{Epu(cU&9pxX!-)I%ebT*UT5Smxt)`Fx3(zIC|m_(hzih{lUp? ze}_zWVfB}7SBS42**!lP-1c`3lg_UbzmfW@wyD8#`2Dei<%XlKwxa57Zdj_Tyk$iZ zuBBdjNL3W?M)oDa%Nf3+qtuWbcfyO3&J<=OW$dcTa#0!4)rsy1;?YcHWkmN+9SDt5 z7wD`9TnkKxjcxAi3Rw11Sz1_loXJ3DB(0C))PAY}d%8k{tIYJ6QSfSJS2&h6^+LiR zahUv6hj%blmFbo?n$*i3@mAuG3Jw<%a$W!Rj?kG!c~+Okd4DAZ>;JUcfT}{SmK(Q2 z54dd-HN;}ixuA$~e+g+d@m7z>dcZwK_BjTtd$`}9_Pw)VBa|=H|Cv8DLY>8qxBnBr zAVU{*2*iRI`mNBcPfhKW#@NkM&EK%W*JjJL2e1EI*6)|)^|`7`SXWg+pUYgZ*achI z3-Eee2jOP^-AS|m1@Xko(yBvWM)!oQ3U?T} zDv5V3h2A05cprSLsPeRjFK5Rs&0AWpdr9=kA$@}rZ3Aq5LSioccJL%Cg8Tl+os53bd|hLKSQin*zk2g6z_% z=nrAN>Lo|%tUhJV2jWgYqmF*7JEFg zFZM75@|w6i?WA4z6JH^Hm6IWDHC6t17D#+x4^8ZP4W;4{*0cAt@7}1v`ns^|GHhuR z#8npdvr*ody(;nQjLerLTHJP}oDCm|@PykgBPwcvXqf^$7kG)l3h)l#83NM+zmN#c zd+=~f*VrtlfHhG*yKefD&MXvbJiEXj<3aw?kq8s=6u_!E0;}m9H&j1$Mv4_SU9=_; zb^e5J5x(`6_$?Cq9G2-q+$>Sc)pd4TP-#1$(>t21%7RguEx0pKQkt#eR}g9IC7C18 z8sv-ihLq}qDI_+Zu~eA``2x*tR+{TBlg!`Vu!bwUX+F2D&q9^ScJ-|sYv%LEoqi%2 z+1}@)Sglf{H}YA{e3L)_v~d4Bu)>UP;rX;?f5BT)Hq)M{4g)92uJjqQRkq;cp%SC(8j<8>SE7X> zWnTT2y;I7xy32b)$^_d4^pig%c1?H6q6iN$5knal)`~px)Ekrm!WYBU)A~`rO#kth zfS27b#t+)TBEAe_DTA{+4H=d3;HD?0kYAEk=GotcuBxkN$*%U12*TAmP0-@SL$+{| zlXS{-UdV##-8Yu`gy)VxG7{qcuv|4nFPD?3iO}@rc(*V&kwd$H95J>kV$+2&;O&FZ zG=z1@NTwIN;e#$M<(TNrh6Uu}x(SE;X+&Z(%H6S^_>pkuz)ao?8Q#H8fcqCO4b1?< zec%mor@WHhILCysNUwJ2v0fy2w|HdqP5mP87WyWzX;GJqOLQ{Olzv6Dqm&`o0XU3P zhRe^MdC0p(a?^`AzLJ{)hRK`^qz-<*!TkJ@r*;Lgv(qiBXyVb@UQb1%GIPC)nS~5rStF~V5asaw5uw+7S!?iM zO4ER|w*z}YrL`{TDIxVLY1Atd>qX3Ut0~5GdbZvev-vd1^gO>_+XU*9S*hZp=3YK~ zAL?=rG{4kzl+S88iSr3hCT3m&ET6CwbNWYRpQJVMYjI{lEL<;*(S50%?jK!d9 zd~aO5{48+qWf2#zZpAp@8u)3rc3l6lxt7db*Cu4UOLy0`STi2Z*xAbNWY+_`x^*?M z$$*A=(GZi7*YG~bM^U<0EJ&RYiiuE-yEqwF9{^=??ySL{?S^<$Dw!nt2`DEu!+^g%kk&#d2 z&E&cLA$B+NM%+j<@0^6)H7G0gQi$d`LP45ViS_gbh5bFWCmC4((+Yb9-+MLIGbOhK znN%!A?a1euEO_`X!rcU9bRs^_$VTE`NcJG-ii%9&8^Eve`C}Bf0oLY59M|D!7^cQT z+-mO}he9GIY`V&Bti9e6TQ{YPs=wB7WApSU>B#N*c44pUU(Th$-|+WE?XFKu2Z?`O zL0V)~)RO!}89WRN5=X(cL`TIe$xkc(SHs&yZ42OiSbW&Kv5*#mj z=kX>2ts(J*UnhOgk8adFa|)8?F6r$3 z^}@dPvCz6i*H?CUyg4RwC8>&34YA$3<(WuDgFnRDME0_Q6=ZgeQiund=@O&~`{)A9 z+79q4;$8`Q9K?u246@(cpNJT7GEhVB6M?p%vfe(RY=2^^XW`)lPj0in36_seP<=k< zfxwf2W(M;k$ibJA&mh0y>i769{nl^n#22eP+Lc9(d`0w=Q#+Y6YRA8iRup88M1r3B zo8Bq4^+eeR?6GXDX$ukq8Wx&&ha9os*se6k7K|enT&z{m1MYQqh@5rbNZ+(x#zUXH zo+@}_B$CWs-zt9Lh()g^=c6({rU@&{reK$9zEp+KzCZkmGTeae&mRDf>r+%7j76{> zP=-(vnTzP}c-NHe8hmX~An%YP$HVk1I;`>ue1$#G1uG8yq^F#(K%8+4a=ZoiB;d}X zt|`B){<1!u6ugy!cU~lQc#6q?flJ5~t+!_ESMz6A>W!Lz%M-sRJS9WKv=k??&Y*kn zzqitpqd``S9%eZ7%ql)hs{wbSLM-cwc2#^i8?3w9uCg1|lG;24O?1GvYv2Kdwj&QE zq74fAln5WSTw~YPT~))!iMyYQYzscKc_b>r+S>0z9FrN2u(|vn*@$O-}FSd?$cGg^{>R(PReQ zm_9GspImnW#xWxipphpXp1q;v8g__X2@Uy`#v%2Oa}|xjXLsyTu|wMRxp}o$**KNa zrjX~|X$wc}&^)Z-VfqYDP&S0+rR{XFwnC&}sj>y(JM5*HB3eE&=2&o}%6!|>NENNMHmOQVC&97!{ z;5Ddr<kG1!Qc2R||ap<|JAM5*z;7 zoA4cx6>e+mLR7evr|;_wm5=Aw)Y@wigJLD*n6=u+TVm1-?B|e*R&-CS zS=n+BJ6;K)2E@9Y{HH>EIC21lkIG<6Ti-CTTILLpxQ-RsUBukVxDYx{2+z^T7!d4Au+Eu=_LiHA#KVvExn{k?wZCTkPzCG=e0==dWaY> zX__D~1rvn!|DD~Hv=%mnw(0%vK0B-5&irQP_nY6$&TB{W+xd)f`5txDK<_~OZ>^$X zDerVx$)cj!A7sUJ#67MmEmHqZ^@?L&+0^Nirk}M>w_sONLh{iOH5bxS6F%u=zF@u} z!a0ZJv7?R)OZka2vtph))#S(b8kzO@UlB8_NDmL)1vZ7YrrG$=?Scj5Up~QU9Z;j&ndMxHYqsn%dO&#}8azTfx#(~v#z8fpbXSACW zoqO!N@!wO-NuT5J_{kY@QbZ=wXVeDN{V}}FRIdU3t9DZ&z+G!I0 z1$1?4r;#&^7|U_EfuEXG<|&&p?#*=<<-H)@CpolL)J;0~xctq(DL;X41cbjI-@CIIPg7(1z*K?I0lpRR^H=L3;qG`@xgC{lMLRn_vSBFxGrjZE z^wZIZcl4;CC%WQQI<}s|_eIu6(ES$lpAx#83@#EU@@&{a39{!-L!wEhWqRqJPXH&eA9|1HZMcf9cA#l)0*nAZP9 zNNnrxalftg54ewL{SbG3>qodpw|e=DNR}5q6Pf4=p~A?^s{|-pTzws=U{cKK`NU1>!vGXk=OXxRmv(BJ6J! zd>9YboUo@m@Vyx5`fVFqf!R7n8QN1DDvIm{_BcmD{HGaYV)Ik#yAn%Ff6LrW7L648 zYA^b(@SYk?n)WmK^pXL@Aq(LDFsdw2>oA@gpbW(To>m}wBOH+N%#Z$ zXZgCrN5(>!IP9Mv z<|sOnaz^+2l<99iHr)}QkaTo5m8JA(XA}o{j?6fXv%`U-*PBKX4j)ajcOOZrJZg-o z-a}6ho0p1rOkAl`950SXLs58@$q#3 zDrTIR=MA(DA?-0wHe{1zeV@=4Xq!kjqyWdd1f7p!4w}TcasH+5Xo?-*zWo9^5-#X_ zlsd2{2>PuZs(Cs6&qjOZ;nJZ;r_PUFv?8H?ethW)BmOBd$3M<<7wrv{4!bQHDBa93 zy6>fx4s{&LiY~i=`aJ2Wkv)w4kUcJ8MA3zKXp6UAv|oURM=W+zMAkHv6*#Ieir~vC z&@ZLqxF4|_8CcdHp@d|{P%*^z4U(8Hjuf`1Hu}2FlBY)j&(*)WIMGpzUio=yTFfV; zy$n65zc|MbbBz3Pjt0EvEt-{<6_3*y`0^sGwa2UdhpMGc0Ncyv5UPVkr^{muc zE8^{T2lNi`W%4E-XH|UO=od#>Vb&zf{b>Hg{|m)EP>UT$6VBj!+t9@Mz-TcRo+hYU zYYNXl*85-g{8?=r|HP=G3&x~^^EC(ai28HPmQ&WvK4UmCPN@5@eyaXFvY_{jM*T-; zcft9Y$5o%td~5tPoDG$1EQW3uG?Q>b=LKgk-C=Ow(Bg~Bc*l0UE1h2HFFlAe@uJQQ z!Ft?V)dz7R-SPW|&e%ZTwvP^z`(j;h!wSCaV0z5Dx3GpW(1|U9ejNRG!Wl=#5w^jE z_eJ4*{iv&6tTpydO-diyq(dFPQ^?N`b(fD0tJs5;pf{nDi&l$vIHc@w^7~Wcsa`&MBkVbo{gWuvBdnJhiD9ODaBcx!5Jw2-EcHaFMA5Rm>*}@g4tyqSL-l0F3r$!CeFq2+!fNt{f7IEc`;YO z{hSnV)401xHa-iGCR{wXEG#^Yn@5@v9!JLEa=8bi@6Z2Bvx|E~;2)qT#6&C{H67g3 z+`(Y_G@PcdBcs6ILO96y5%;g=gl@O-@)$|E`ll+3MV`l4DWXD#)Zo7#-CSRh z2N)once@c=2pcf`vYV#BiPQto4=%r(=lFJ>O1B%-5(g5J59zww1?~gOFNO~;zZgEa z{1W}3@=M`^mcN@n$)CTW{3qS#Zzw+vzmfbj{6_K%^f!>7h7XU489QRskTJK7xy^Xn zZAKEn$Ha^=4jE!3=RKa2C%bVM4Nv06S@1y(dRbg)afI*QE%YA3ZFjd2?y|USHVXo& zL3|N+eakj(lrDs)-7{*GP8XsFF*W`9T@K;9cXxMRzP#t9?^irLAvV^CYsQpOy3|+{B|wH+s#Ter5hHWSj^`r2#MZd1dw@%#`nlYE zQ#Xi2O)v9#IytwSB{ZMu&3Zyjp(CYHv!wKNc>kN23|Moy2lIW4`Y%Wd?S z`Xq_OSbzJy4?6~Y&_blZb9!Ebys5CdK!KPIM5RJez>fl9S;A;w(-p+k0Ha&P;S_LK zLiksUGdvFgX!mCT^uPQo`4jvq zt;etTyL_(QePR!!KhGO^(-q#Qzaj{*{VLzZclDxQzZw##tB2>Wd^r{!+Zvx~&B>0g zb7&}E#sO#gjLgZlu7hYOU&bz)uA6(#R0*>RT0SUu#78b|Ku=z8rXLd`DN8 zfNR^A6X2}TWb(ND@5EC4B=E5=7pM1cqd4P<1ir#W7+)I2)f6`-q2a7t6$^gG&Pee4 zu}RBC_+wpXmycy5k=|2XoF{^dWq`-8g*rmI|SATYe z_pfrH3+U#5*LF8iJrOF;&-bw;dZ-L{wW`U`FUM-z+FDL>tC-IpIi=!=3*AkxOcx;%g!WeE*3rRAkGSv34$!mZ$4^*NGN>B5?O`0q-(( zc}PBR&z-bSi5(wgyZ?0NQ<@iEoV^P+OD+uUm8!~S)CQiU)!~6{uTa& zU%)B<4%hrEkI3*ZT=V_g9udkDGQ=`p@ELvE^84Xew$-{*)$ikZBR^m8nOEW_mM+z{ z*8^Y59N@FA#$87F>gkx>%|Fkl`~A-ZidRSG@kg)tFC)mm@-igluklCuFGo{+UtBH4 zU6=V!KBV|+T>`~TC-~mk6yIzU@PvK*NmOBkXAAhG`9C|Qr}$c3EiB(>1@|0;&HJ|r z^uCoRc1pNF@0)*u)^Hjw@+;o{WIoTkX=m)gKzz;6^`xHXx39d!^OsJn?7cS1)Cv5B zeRm{nk78F31^#jZ&v&klVz*J;mucz*zGJg(!nWoKTYl?{Fzw6-zB;>nTU@z-e+TqC z*6K<{{7KVygmBOc@i&>;5#9-Uuw3(7A8Tp_{sQnl621=j_UhMwr=t6Gjbx1^5pqmpNsBQ5sXV@Ondo%ym9e;7+pOWb~z)R z7nq3-vjG;<6b`c&5s!{$H7Ru>KEJJ<88L#@WVVZV&Ij)^@$sxCJ6psHnkNNu1LzBg zh$H?;{sJNSYQ#UuYQ`Hl0}Z#RbVB^tQjtD@YdGNPp>W^={SCqed1<)G#A*;fhRRRz zv10{Xq+hFJXnq3WW?vBRmtVCk|MKz(FR#D%iG=eTsQjh$M;GtJ^UwsJ&utILaLn08 zmjJiyMR+W$*?x+phoy<5U6C6aLGGk*?TM9_<#=T5%GVJb z3V*ix^0P8r{ZTc(brcFGAFad&q!12%Z+s-f)lbiPRgTlR)CAHOvFq{h{DkLMq|CdGG!bj>mc3qu)Y()?s_SCLpZyYNP;T^kp$}3_l1wRfhi=@#W#QZ(lw4x-5Lr^Go*0a4B1D z9?=mlZ+L#B==8<$A7lJj6E(?L`LKp*6!G)##nrWJ2;rX9I<^G|X#~Q1Hrsl7Qv2a2 zd!j@5snxdlp4NW&#GX?j+_PE7_5}DLW9K)=)%6JJiMe!9F{nIaMcfp?#ic4y3>qFA zC>7W$tPHU-oWYFJ4V=;rZ0I+XWX2yV+m_b-iTmWZWFBALK_x>}ma=PDoFb-z7+e_|gQ(6u{|10E{t!_ldZ8XHX#h4DY3=5O=^^ zA6>zb8E>#YJZs(;SH3TVZ#1vgO;{7cxAbndo!TD4H}+5&bC*+j4~TMENDLyjay2j7b#IOHxJL7{F6RT)OKpl*)}(_(GN# z_m=M6TUvYLmb4^&ze6P3&1f#51+d@OiunG?sTXV$dM2zh1@Pz#*}z+*3blJO@KWGy z68)Ui3)yVXgiSqC{5hHtQ4@MZewZHbpR;!49TR$x9!KTJ>@;A+>;y16TcySo@Q~_* z=sUt8l;TOjuWe#gXZf!tG^kD9nGn0t!vpSUfMdy4vXL4GugdOmIm z(i7S%#ie@Yp>ArhI!KLMKyZ^vhzsC1vPIHcy!Umh9Mg>Ct%WzdrR#O;oOTp zvkdDbIR~m=Jy12gs=FhF`>8tTMD-gds*Kf_Ppsg!s-8Yp{rZ@y*y^=-4{T@j$Ex3W zu4-)c+Of^t+DM9RuhIic$5Mu;&=k0h zp~MN-@@xoW2zUld-%r}l8$yAkogUQ~G#$Jg3miHo2Alhhu%=Lj3{w9ub8vEb8DODO zqJziHVjPjxef$M#3YIm`tw{5`-gCU?a69ntkjbxpPfanuYlSqpE$NACc+Y+H+}l^r zxQ|7r9jkC({b1JB3U}`ZvwGok+hRY|)-z8>`59E&%vxkWeb;!m}DPG9(AY7OeaCrGc{BsDpjPmk}KmIJjN27dH z;_uHCzg-)|k1Ym1N*%J5g6^B3?@j$rtO$2>&)pCSGiD&0i= z%=Tb7f0n}`tD*3N0{;-bfXn1}Q~6}_3%H#AGn!sV-h-bay{#c!OfMvVIRE`LeOY?p zxRBmxeNcYSy-4rF5YE%|o(bXZNywipemK2Ye)9M<{lW1oG=H-6+>>vdUO4|hFQ(5g zRNn80mFIqn%hKO1;GyT$P3dLjGvM%k_LTEYW-D z#tsup(pX~hkhzD*T$YTzn;UwN8}@Tf^$4eJ`=)mLEZ4#1AAIp`hwo97J9Q=r7wBgB zY)f+;T*ZH}%H>-HKBnvVK+oW&%V&(`z&w{f;W6LL>+^*V%lNc>pO(i9gco=5r=~vm z`AAnEG2utZ{e*Zp#!Y`}H~w*y_0`mQ^#t!VVowh5HU@rr>hR!PmPD7=;KD|lKJsE8 z+0Bs_!nAPe7Pn~+rg>qETGCTj_3UYZ+0(NsHNA!6=T3-;QJfGVMoWtjN*{3`h8P;~ zX$hf1j1v;R>eqX~Lo_XWe!Yt413n@~Qxbe6)Q`=ICQphX$ve_B+BZ0X5uq$wOLaNS#Od*;=5^S+%$i> zJJjgbyY(I;M5Ds#z4|_5pWcVN*LdD2){VfO88m($;^WQ46o@I>=fMO1dK&7L_#+h| zMeHwgh0nD8CZCC>ezWnhEt+|f=>6Qq56rC#+509foII&@#WSjLQA|t}Km3YH!)Vk@ ztT+v#&ms`t_kr2BkX@d#a863=itVa2J!91KdS0dFHEJkS2-Bue{8ilf9CJ%EYsp^7 z_O&)^RTHl;Bck|WgYjR*@i}I`nRR6^H1)N*c$JA~#@-5l_6n7MNR%pilLnvr9|G-A zGf{H^`3K?;`G@OO(c3jqrciur3-+t9gzF--lQD8ci|H$7$ZkSBI2(&FeONfd{4nt9 ziN$cHhp1vb%#&W?FffO8s(ad1S*@xKnau4IR6mTt;}V%ax+P&?-GV$H5tmyK=@8=@n9ud{JiH64 z$}=Dzz6ZTW zOyoI}z)DO`?;mISt9*7~k8t3}!7*G-^lcEKWi;Q>g{^njnL*?kj1Q;fpVssLt>^uE z9((rauS?*0>wGq7`Quv3M`9)N@n8$WGaN~5HxoVnkTz_LHS5_$oqM-%hPlONgT}VQ z(6R2h&ehq(tjdn$=I*$IyjujK4OngQim$0I(3>QBqeQiR1@YrL+IvW+UL8#!zA%AN<8*~$JL{f937D3;4#SIHuLA)A-Yo=a zpd3K&0=-G1H%j#To{`GnAj+UaDuaDjup=Mz7eFtS1Nw0UyHLD_ z6hx631Kf##Izl3`kI(4k)w~za6C|LAjSdj>CeRyUYj=U3(#XsN;HF&w?_6EAm)8)l ze>gpC5llb-mS{bo^@36+1Lc(nsH5o<#G8w8A}9j_&=}m&i3T0pJf}5FW6@Yf zY+l`JX|*(`VE~hi-fPf}lfrEyHg66Or+mlh=3J!oH2jpU)w)bZFR)25?XCFf{S(CS zwL-YiF7UF3`HI%9b*njgF@#nUZ2#ypiQzn^^fwOYc@JKq2B#PJ?7KeP<>wfVd7Akb zW_!yf%eEIb-oJ73#why#0=-;*8s2uYJ!W&%k0LrFI#2X6y2-Y2oje?zc6Xe7KIXMMcKq?RJD!i}ILYmn^QS3nNZPjSnNPRu-?IOi zPq!`GkVLr$_^)9$K(<%ne;EJ5;UC7l6tiR9j&;v3ThkHsf9vGuKHg&6vj4e{ zw@&`f{c?IK2e$83(u<}dN*AE-IKR>Mvxr|c{o;?mdgW(-{BhGOPrm%iAFQKgAm$(C zvt+Yvvu(#+Z_T6E+bg$U={QeQ2!x}AHf`F!IciJP#{C;@Th^_M>7PFGvW$E8yCrK% zUMk_p9#HA!+t6N26rpk=Vt@h9FM5wJ%$H+4|*|6mS1?*h(j;8 z@Yj%3hfK7NoYC$T|1JvhxqL1}8=wr)kuXDfiJ_E#Pmga9u01_8{AT%hw6q2XuFK~J z;~Mz;G$d4id1#t)P9jHIM%azJPcE%lD9zGHR+AVVm!kEM{zmO4czcgv=^a0ygBb)N zLRiePsU$v~OfIB%{D8`h9)c_*)1 z2GA0M^;q;$ZxW`Cs67O|kDen&LN^tx5V|x&Fqu+9IQqlnT8mYcK5~bTjKTIoBoEUki0x;SM)^t~bquv<@db zVn}NYX;zUZGs%v2^5i3=mA;7#Y)pOFY)S4PL9*A8Oe?*^4IlW+4TOx2B?b+dSWoho zk^C#9qmNtT;c%zFW^VoxVs0VqY7(QNF%e(GsECS*WFnXdVz~V_?aFE}$>j*RUBn0_Fub$FhB_Cf0&Wa$um>^? zbMszq-BR#!>*oIkq?3lZn4k}49AqoEm7F52+?v6ng#2A_@*Gm9Uj4*?Sj6NZRJ?%M z*;!)l4}p*YY?qJsyD&|-DO2V5uSf}1?Hkj0(I9nbks2Mg0vmLM@o2e0DbNTwHRmD3 zfheN@I>`o3{b@LSMAJKG|JIi#_x$?Y)}N$bd}&<3dth5l)FievwscSQo@i=Qd{Co8 zpqj%(%7ea@Q)A=TjSQV)k~Fk@wEqHr=}4dDzc_=`922Tu8Iz;y&yGBnq+)IAi(ou>b`#EDXf@??=3@;&&U zVTO~j%oO-$k)61jaV3-ExF2C=flnv=$1=sZ2Mnd((I0102)U~~7cha`dsjKRzrpvlY6Bl#t^FuiuU-npXpf4yG*m*&uS3ry$;A4f?J_T2(S?_bgTZ@;15JpFFK zjr8Ve_~73yQ1t#4y?;gTU(x%QW;u%9zebu0iL<_c#&;LQ1pkrg(OE6!hc_uj??1BN zmjL=}{i_*8?_bgTSM>f9@G^Z((fb#c6r`IegeZFdir&AV=S~;V!u8O@_4K9b!{2O$ z^9xT;hAVpicpl;9b&CZbYUMZQ^7QAg4dUT?@Zoy$gXz^PdjDdH;)BkL-al!R>QmsK zi2!btYG(i^lF@nsF3}zg52p|3e;uyq{VRI^{dz>1UcRFDuju_NdjE>vf3Sa0^!^pS ze?{*fuZFB_Hm2zPOZ@|f{ba$eg6G_d-oK*vPgG~lqOB-;|D8l7{uI4`)Q9W80}%S| z#5wdKm+8(y@yB}*9~*PS3_NerxIii2E%<*OzsX|xE724^e?`y#tJL!kuU~(qI$j@q z3wrH}-v5oii=gQJr>;`;{uRA{==Upn|INa82nN#2AFJs7D|-LIf%w+->Nx?GKCKp%s0cD7g$q4N0Ohbua? z{q$z#TC|D|t#Wp@a(1?$Gpn4Pt(=`Lj&1ULxK5%>XS3f~(#qM{oKPO&Ps$+}sh9Nsk zThXCKdC1O|R&;1-dGJAmTRk` zL#v#ft(={0X59T-&fsTrD`#iZ_Y~#qY~}21}(trsOZoRat8NA}=)i zY~}21}=)i?BGe?W);0km9w+8%Gue%>BF~rc6R?)bs47U)s7?MaM-t^SKGlo z%^fr^<_fr-_fZKG;tN{KA@T4%D0e+0t=-p!c3|_~W zc(FcFegQpVlb187RUsTNjX}L`Nw4@Hq1P-ejMfuC z;tqV>?;#;PesX%0Jic-^Eg_zuo^QAwuu-a^Z64CkXm1h^rw`|U9j@rrDtfj3dPPOA zR?({!D@6B}Rz7BZYFvitLW7#dbNsPt)f?}=+!EEwTfOXG5P7w=%YVO*Wu3EL;`zLpkUa&2|T|=6F z(D_FJkftAL`ZEE=fO0?`0JQ$+0j~iL0nQSt*O0^`Egtdt0s!RW5s&7FM>_mZ0Z4;C zMUs*2WHJhn2*?I31k?dG2=SB2%aXkpejfq;Op>9algt=IeIIv}W3hi&(fxL1F%V)?#)p4a>AXV9m)d zD6p0kvUAzYLdyb6j&)&T;%tP?FSHb7+lq>EMWu8*u+^0_$AFC zq$t0z*k+}H_gPCoo1B=Cn3Ry5m^70RTV7UvVXmdvmY-KdCCIVO&$AY>nN~INYg+Kb|i0NwzUY7Jc85|jKA8=NX%9dr~SZfD}??anc+|m+fh$jVQ&OH3+M#A4A>>$ zw0}ru-b5T~y8)=XPK=W@2Ls;Pl3 zce0D@<%^t+4Fo(Pj|#G=b^v|~cnz=@fHsf*Bw2M4HrlJKKS@@DPCW`R36KInTU8^j zx)IP0cpd;=YUDxvpMbOKNNpr+A4%3|0LYIf7BB&j2rvV30N|mi18{&10LZ231fYC0 z?*l#poCWlelH|x}0Md*^{K#wo(u+iT zk?jD)k9-pVn#e-{&_qIBt<<+@r@);G0G$^7ozNd6EiGi#J_`W77S9I$$=bbuk0iI3 zB!oDFP)(^2niBrwUk6hJ1R0I(2H1pt5DMnETE7r+fb zo^+5;hdf1T0I`5X0BEB?7qu9`1!WXuirN~)lcO*$P&uQ(FKU-;VU>q)q#5mE9+d|s zDG>&v+-%Vzb0m9~WT#sS3m-x}!A1)%DlW;j2Jr>9Ot|xGdG}dZF=kPLCBrJYMVcHd zjXwkYXgV_`J6W=GEg9LiJnQ@t+HDkr7;P`df)*S?(eGI@ifQK@jDdc+#8zx6ve*c9B0HPutajEklKJ)arIn4# z*!fM%NPcC5)853UJ8PU3lt;nD#>z(4>ZrFjfQ*pbdN#veRaqnOw`QOlMdxQLq}e3t zB5tweTJnmK^I|J{W{d??d>VAHA5RM2sT213{3p z*jCJ1GYV0Df#kycf^l;T(edZl@&btk@n8&^?|%F&HmOW4=!u1ATSUjQqGHR`bUFl( z4D__cC3q+p#6;PxB)t&5ECL}9d~ivg<8Syo$9UfIxqKGeR{Uem}ft!=D#!vE3Q%0)Hst#Yyr zwN*j~3m%!y277%qydBQ^MtfyV16vK^$|@(i+>#phQDO@Fa{ zNt2VUv)4CPIw88#A<#+umR34zYQT#&fGR57e7letJ5p(?W2q!!K52d&jg?DfIU?0) zC#=1qvPK+GKv+-e?RAyqLX&~4v|TN>msK`4VZ@0iLewQq6DzA4SZ8xxRc!-JStv~c z8pvy3L`j;^AVqnnunm=j*hLvwA-%d<+QdkCZBrS_C?4$-3Ds7z&Kj0BQ5r_$W0Z?$ zEsc$kM|d{0P?~B`ri&{bO_UILHRNo7S3PbbK~`lA(kpk?*Hk*|rH2I38!H9LD9v(v zT^$-ZY%u+uVXT15LBq%M>@+u*1b;|8h`m^BK*5`yQ&+8y<^Wp+GTNJM2QuCK&M-&8+^ zT}s&zJn-dsJP@X`qJn0<(J4gDzzhieKrwAh^DUY4t;Iz|n*X9d4b6yXhcq3XGP-7= zkEV8t*i{RCHN~e(_?$pI$(||MMNWs%pC-*^3+kPCvXHkHy<@;Xc`jS%gw*u_unYQ6 z$7I{N)LB)FA{RPgS=bE9tG2ueb&|3Q(#)L8TB@B@m9h8iR`3L!kRvZ2vl;}HDNRO_3NMkt=p zS%h#gfe;>O1>jUrU)ju-HwCIV8neBMre7fTH|5ScXHA1BD#@ko9Wt;|BcSnuepU7c zCmLT6m%2L|Cn&2EA~y=nIKW@#gMOTLis%|bu^O^+A@lfVhL>*1%-ISLfRSmh?hZMPV$5R z46qMB#AabyvcQ%%pDkJdK8Qvvxi+vZV$3wF&9})M~(|gQ8r#uX^NIiDkjFJMfUn~ z+WpxvEZJ+&H<2uRB}ND~)Wxz5_L>GdUX)eUBEoVuDG}X1otToqI|KTgMhr-Zg}h3Y zcRAiz0~Okd1}O0r`Z%d?ucvi|(#XRwNvfvxBoqCBFqkGkR!!ps@tGoiy__@^l$le^ zQLs~%YOFMFO_v51bP5fDbkXe#y=X8$$+X4Q*&UA|M^ZY0*CKRn)g-?R!z(SeiM8TP zuS}fj16WuUo1|D9;mGXH#(NiOr*J!(o6y(49IvsH2<`rgF=Q7`lfw4o*lEu5AQrmwWCM1S9eTs9GTV}fi_t7NCM z704TqG1Z7tP>LfVV;Y311Sc^na0}c zN;>#K2FxXzX&fpDstHO|7>68mQqvfkUz21}2#nmJ>PYj@kABu1shSb;5M)DLbS!48 zm$4Yxt8md0E(@E(i*p;)70d>US*$FS6k-0HkA^2W$$fZ>D9A4s!>z1!VL?tl);NUr zMB^2O`K9OQrlUhC&d(KRpG8(QOY{%Y)G;vsl(?fV=a;0T!3r}?nxCBfy!j=<40odC z0b#aD$i1}iq)8YZxbMaD?uXMCFPVo$nirr97dh+2;V|e&=aPYm0(hm@;(?*02%R*0Z_o+KLwVuuLKJt9 zdQvXoG;z{olE0r00 z8+>i>n*&Gz%ohFdfjmN{044*l*4y-L*uRp(*1)|Q&;oc8@Lhxp>A^-H-!vaUWq28| z9&U+eQ##z40La*61|YqrS&|HCaN~8nDHZT_09M|b=FqUe#q9ja&L8dkmz|GF)=uR9 zN913~qx2(80YeDKKt9R1P!1u(1)cQEPXWXGLvDN;LPA@r&P~<*bsq zNyyFfa`!LlM_)dCaM@2enLHfi46vuXDNw`Vqf(sU65K&~;1`q!?w~x>Kadvnm$`%T zP@W-wo)5{xAKT}i{oUU`&HM*GX4T!%_|5}gd>D;1`9E#B@WfBv&hFU_fA67TfFKEX7X8jPd%aOIrnY((xCmpf!}sayvXY3{x~|-{G+_8!vA8v zD8IArsn7oX$M;3fJ}|9&?TfSWel%rK!c*JU9UhtW>z&W<8hWi(Rg}?nK>zTYKUz9# zTjSw3HhETM@11n$;T=t5bH11Nv{oc|GXcr8rqvI0Fff_c+(4P6@W#(M~vx1J-4}Z6<(L76kv{>6}^aSZCR}Lki@oiPn?WO8NIGK@$q#_ zPe#rjy{=*7_*v?850$RjuT6|Tqs|&eU8foAFz17ZhMUKFbct>2?SQ-t{U(D>}5pvXCO z$-|2j>^IygyE0E*+RIjGOP|OZ=3(xR+Le0h(#8=2jX_FdI(&u-K3e#cfX6+jE_IF} zQ8b;B5)YZ9T~C&&59>#XDZa48iLljV*z2#kX=K| zx0E_6FD<^b=u*Wc$EC7M_DlJ9lc6NaLuRPgkD8IQPccursor.row = 1; disp->cursor.col = 1; - disp->cursor.flash_style = -1; // Init useful SDK data load_data_file("CharacterSet.data", CHARACTER_SET_SIZE, disp->character_set); @@ -92,7 +91,6 @@ cpu_t* init_cpu(memory_t* mem, display_t* disp) { cpu->keyboard = calloc(1, sizeof(keyboard_t)); cpu->keyboard->repeat_time_first = DEFAULT_REPEAT_TIME_FIRST_COUNT; cpu->keyboard->repeat_time_next = DEFAULT_REPEAT_TIME_NEXT_COUNT; - for (int i = 0; i < 10; i++) cpu->keyboard->row_state[i] = 0xFF; cpu->malloc_manager = calloc(1, sizeof(malloc_manager_t)); cpu->malloc_manager->mallocs = malloc(MALLOC_SIZE * sizeof(int8_t)); @@ -183,7 +181,8 @@ void init_loop_html(cpu_t* cpu) { window.instructionPerFrame = $3; window.instructionCount = $4; window.rtcTicksPointer = $5; - window.cpuPointer = $6; + window.fsNeedsSyncPointer = $6; + window.cpuPointer = $7; }, &cpu->disp->vram, &cpu->disp->lcd, @@ -191,6 +190,7 @@ void init_loop_html(cpu_t* cpu) { &cpu->instruction_per_frame, &cpu->instruction_count, &cpu->RTC_Ticks, + &cpu->fs->needs_sync, cpu ); @@ -216,6 +216,7 @@ void reset_execution(cpu_t* cpu) { for (int i = 0; i < VRAM_SIZE; i++) { cpu->disp->vram[i] = 0; + cpu->disp->lcd[i] = 0; } free(cpu->fs); @@ -223,6 +224,10 @@ void reset_execution(cpu_t* cpu) { cpu->malloc_manager->currentSize = 0; cpu->malloc_manager->mallocCount = 0; + + cpu->disp->cursor.row = 1; + cpu->disp->cursor.col = 1; + cpu->disp->cursor.flash_mode = 0; } void load_new_g1a(cpu_t* cpu, uint8_t* g1a_content, uint32_t g1a_size) { diff --git a/fx9860-emulator/memory.c b/fx9860-emulator/memory.c index f134c54..3d05627 100644 --- a/fx9860-emulator/memory.c +++ b/fx9860-emulator/memory.c @@ -20,9 +20,8 @@ uint8_t* get_memory_for_address(cpu_t* cpu, uint32_t address) { if (check_memory_bounds(address, RAM_ADDRESS_P1, RAM_SIZE, cpu->mem->ram, &memory)) return memory; if (check_memory_bounds(address, RAM_ADDRESS_P2, RAM_SIZE, cpu->mem->ram, &memory)) return memory; if (check_memory_bounds(address, VRAM_ADDRESS, VRAM_SIZE, cpu->disp->vram, &memory)) return memory; - if (check_memory_bounds(address, VRAM_ADDRESS + VRAM_SIZE, 1, cpu->disp->vram, &memory)) return memory; // TODO: Check why Gravity Duck crash without this 1025th/1024 byte - if (check_memory_bounds(address, KB_START, KB_SIZE, cpu->keyboard->keyboard_registers, &memory)) return memory; - if (check_memory_bounds(address, MALLOC_START, MALLOC_SIZE, cpu->malloc_manager->mallocs, &memory)) return memory; + if (check_memory_bounds(address, KB_ADDRESS, KB_SIZE, cpu->keyboard->keyboard_registers, &memory)) return memory; + if (check_memory_bounds(address, MALLOC_ADDRESS, MALLOC_SIZE, cpu->malloc_manager->mallocs, &memory)) return memory; if (check_memory_bounds(address, FKEYICON_ADDRESS, FKEYICON_SIZE, cpu->disp->fkey_icons, &memory)) return memory; if (check_memory_bounds(address, 0xFFFFFE00, 511, cpu->mem->tmp, &memory)) { printf("[Warning] Access to interrupt registers at 0x%08X\n", address); @@ -62,10 +61,22 @@ void mem_write(cpu_t* cpu, uint32_t address, uint32_t data, uint8_t bytes) { // Read from the memory uint32_t mem_read(cpu_t* cpu, uint32_t address, uint8_t bytes) { - // Keyboard output register + // SH3 keyboard emulation if (address == KB_PORTA) { return emulate_keyboard_register_read(cpu); } + // SH4 keyboard emulation + else if (address >= KEYSC_ADDRESS && address < KEYSC_ADDRESS + KEYSC_SIZE) { + int row = address - KEYSC_ADDRESS; + int val; + + if (bytes == 1) + val = cpu->keyboard->row_state[row + (row % 2 == 0 ? 1 : -1)]; + else + val = cpu->keyboard->row_state[row] | (cpu->keyboard->row_state[row+1] << 8); + + return val; + } // Address that stores the model of the calculator (0 for emulator) else if (address == 0x80000300) { return 0; @@ -123,37 +134,49 @@ uint32_t emulate_keyboard_register_read(cpu_t* cpu) { } else { // printf("Keyboard row %d: 0x%08X\n", row, cpu->keyboard->row_state[row]); - return cpu->keyboard->row_state[row]; + return ~cpu->keyboard->row_state[row]; } } -// Keep track of current row and column set in the LCD selector register -// and write data to the LCD buffer. -// Refresh the HTML canvas when the last row is written. -// Works well for MonochromeLib's DisplayVRAM() function, but might -// not work for more specific register accesses. +// Emulate register selection and data write to the LCD screen +// Register 1: Set counter (Graph 35+) +// Register 4/8: Set row (Graph 35+/35+eII) +// Register 6: Set contrast (Graph 35+) +// Register 7/10: Write data (Graph 35+/35+eII) +// https://bible.planet-casio.com/common/hardware/lcd/T6K11.pdf void emulate_lcd_register_write(cpu_t* cpu, uint32_t address, uint32_t data) { - static int row = -1; + static int selected_register = 0; static int col = 0; - static int write_data = 0; + static int row = 0; - // Setup LCD row + // Register selection if (address == LCD_SELECT_REGISTER) { - if (data == 7) { // New row - write_data = 1; - col = 0; - if (++row >= 64) row = 0; - // printf("Set row: %d\n", row); + selected_register = data; + + if (data != 1 && data != 4 && data != 6 && data != 7 && data != 8 && data != 10) { + printf("[Warning] write to LCD_SELECT_REGISTER %d not implemented\n", data); } - else { - write_data = 0; - } - // printf("LCD_SELECT_REGISTER Write: 0x%08X, set row: %d\n", data, row); } - // Write data to LCD row - else if (address == LCD_DATA_REGISTER) { - if (write_data) { - cpu->disp->lcd[row * 16 + col++] = data; // Update LCD buffer + // Data write + else if (address == LCD_DATA_REGISTER) { + // Graph 35+ (4), Graph 35+ E II (8) + if (selected_register == 4 || selected_register == 8) { + static int counter = 1; + if (counter) { + // Set new row + col = 0; + row = data & 0x3F; + } + counter = !counter; + } + // Graph 35+ (7), Graph 35+ E II (10) + else if (selected_register == 7 || selected_register == 10) { + if (row * 16 + col >= VRAM_SIZE) { + printf("[Warning] LCD_DATA_REGISTER write to invalid position col: %d, row: %d\n", col, row); + return; + } + // Write row to LCD buffer + cpu->disp->lcd[row * 16 + col++] = data; } } } \ No newline at end of file diff --git a/fx9860-emulator/syscalls/bdisp.c b/fx9860-emulator/syscalls/bdisp.c index fc8b241..706996c 100644 --- a/fx9860-emulator/syscalls/bdisp.c +++ b/fx9860-emulator/syscalls/bdisp.c @@ -1,13 +1,5 @@ #include "../headers/syscalls/bdisp.h" -// Refresh the HTML canvas with the current -// content of the LCD buffer. -void refresh_lcd_screen() { -#ifdef USE_EMSCRIPT - EM_ASM({ window.refreshScreen(); }); -#endif -} - // Set the pixel at the specified position in the LCD buffer. void set_lcd_pixel(cpu_t* cpu, int x, int y, int pixel) { if (x < 0 || x >= SCREEN_WIDTH || y < 0 || y >= SCREEN_HEIGHT) return; @@ -22,7 +14,6 @@ void syscall_Bdisp_AllClr_DD(cpu_t* cpu) { for (int i = 0; i < VRAM_SIZE; i++) { cpu->disp->lcd[i] = 0; } - refresh_lcd_screen(); } // Clear the content of the VRAM @@ -45,11 +36,10 @@ void syscall_Bdisp_PutDisp_DD(cpu_t* cpu) { for (int i = 0; i < VRAM_SIZE; i++) { cpu->disp->lcd[i] = cpu->disp->vram[i]; } - refresh_lcd_screen(); } void syscall_Bdisp_GetDisp_VRAM(cpu_t* cpu, uint32_t data_ptr) { - printf("Run syscall: Bdisp_GetDisp_VRAM (0x%08X)\n", data_ptr); + // printf("Run syscall: Bdisp_GetDisp_VRAM (0x%08X)\n", data_ptr); uint8_t* data = get_memory_for_address(cpu, data_ptr); @@ -72,7 +62,7 @@ void syscall_Bdisp_SetPoint_VRAM(cpu_t* cpu, uint32_t x, uint32_t y, uint32_t po uint32_t vramID = x/8 + y * 16; uint8_t bit = 7 - x % 8; - cpu->disp->vram[vramID] = (cpu->disp->vram[vramID] & ~(1 << bit)) | (point << bit); + update_bit(cpu->disp->vram[vramID], bit, point); } /** @@ -130,13 +120,13 @@ void syscall_Bdisp_DrawLine_VRAM(cpu_t* cpu, uint32_t x1, uint32_t y1, uint32_t // Draws a white line to VRAM. void syscall_Bdisp_ClearLine_VRAM(cpu_t* cpu, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) { - printf("Run syscall: Bdisp_ClearLine_VRAM (%d %d %d %d)\n", x1, y1, x2, y2); + // printf("Run syscall: Bdisp_ClearLine_VRAM (%d %d %d %d)\n", x1, y1, x2, y2); draw_line(cpu, x1, y1, x2, y2, 0); } // Reverses the specified area of VRAM. void syscall_Bdisp_AreaReverseVRAM(cpu_t* cpu, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) { - printf("Run syscall: Bdisp_AreaReverseVRAM (%d %d %d %d)\n", x1, y1, x2, y2); + // printf("Run syscall: Bdisp_AreaReverseVRAM (%d %d %d %d)\n", x1, y1, x2, y2); for (int y = y1; y <= y2; y++) { for (int x = x1; x <= x2; x++) { @@ -166,7 +156,7 @@ void syscall_Bdisp_WriteGraph_VRAM(cpu_t* cpu, uint32_t graph_ptr) { uint8_t write_modify = mem_read(cpu, graph_ptr + 20, 1); uint8_t write_kind = mem_read(cpu, graph_ptr + 21, 1); - if ((write_modify != IMB_WRITEMODIFY_NORMAL && write_modify != IMB_WRITEMODIFY_REVERSE) || write_kind != 1) { + if ((write_modify != IMB_WRITEMODIFY_NORMAL && write_modify != IMB_WRITEMODIFY_REVERSE) || (write_kind != IMB_WRITEKIND_OVER && write_kind != IMB_WRITEKIND_OR)) { printf("[Warning] In syscall Bdisp_WriteGraph_VRAM: write_modify or write_kind not implemented (%d, %d)\n", write_modify, write_kind); return; } @@ -174,14 +164,23 @@ void syscall_Bdisp_WriteGraph_VRAM(cpu_t* cpu, uint32_t graph_ptr) { // printf("Run syscall: Bdisp_WriteGraph_VRAM (x: %d, y: %d, w: %d, h: %d, modify: %d, kind: %d)\n", start_x, start_y, width, height, write_modify, write_kind); uint8_t* bitmap_data = get_memory_for_address(cpu, bitmap_ptr); - + for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - int id = y * 8 + x; + int id = y * (width < 8 ? 8 : width) + x; int packed_id = id / 8; int bit = 7 - id % 8; int val = get_bit(bitmap_data[packed_id], bit); if (write_modify == IMB_WRITEMODIFY_REVERSE) val = !val; + + if (write_kind == IMB_WRITEKIND_OR) { + int vram_id = (y + start_y) * 16 + (x + start_x) / 8; + int vram_bit = 7 - (x + start_x) % 8; + if (vram_id < 0 || vram_id >= VRAM_SIZE) continue; + + int vram_pixel = get_bit(cpu->disp->vram[vram_id], vram_bit); + val |= vram_pixel; + } syscall_Bdisp_SetPoint_VRAM(cpu, x + start_x, y + start_y, val); } } @@ -195,7 +194,7 @@ void syscall_Bdisp_WriteGraph_VRAM(cpu_t* cpu, uint32_t graph_ptr) { void syscall_SaveDisp(cpu_t* cpu, unsigned char num) { num = num == SAVEDISP_PAGE1 ? num - 1 : num - 4; - printf("Run syscall: SaveDisp (%d)\n", num); + // printf("Run syscall: SaveDisp (%d)\n", num); memcpy(cpu->disp->saved_disps + VRAM_SIZE*num, cpu->disp->vram, VRAM_SIZE); } @@ -208,7 +207,7 @@ void syscall_SaveDisp(cpu_t* cpu, unsigned char num) { void syscall_RestoreDisp(cpu_t* cpu, unsigned char num) { num = num == SAVEDISP_PAGE1 ? num - 1 : num - 4; - printf("Run syscall: RestoreDisp (%d)\n", num); + // printf("Run syscall: RestoreDisp (%d)\n", num); memcpy(cpu->disp->vram, cpu->disp->saved_disps + VRAM_SIZE*num, VRAM_SIZE); } diff --git a/fx9860-emulator/syscalls/filesystem.c b/fx9860-emulator/syscalls/filesystem.c index 5deec80..1b5de72 100644 --- a/fx9860-emulator/syscalls/filesystem.c +++ b/fx9860-emulator/syscalls/filesystem.c @@ -1,5 +1,6 @@ #include "../headers/syscalls/filesystem.h" #include +#include const char* permanent_path(const char* filename) { char* path = malloc(sizeof(char) * (strlen(filename) + 7)); @@ -8,6 +9,17 @@ const char* permanent_path(const char* filename) { return (const char*)path; } +// Lowercase the file extension +const char* lower_extension(const char* str) { + char* new_str = strdup(str); + int ext = 0; + for (int i = 0; i < strlen(new_str); i++) { + if (new_str[i] == '.') ext = 1; + else if (ext) new_str[i] = tolower(new_str[i]); + } + return (const char*)new_str; +} + const char* convertFileName(const char* filename, int trim) { char name[40]; char ch; @@ -48,6 +60,7 @@ void syscall_Bfile_CreateEntry(cpu_t* cpu, uint32_t filename_ptr, int mode, uint fwrite(data, 1, size, file); fclose(file); + cpu->fs->needs_sync = 1; R0 = 0; // Success printf("Run syscall: Bfile_CreateEntry %s (0x%X, size: %d)\n", name, mode, mem_read(cpu, size_ptr, 4)); @@ -72,6 +85,7 @@ void syscall_Bfile_DeleteEntry(cpu_t* cpu, uint32_t filename_ptr, int mode) { const char* name = convertFileName(filename, 1); if (remove(permanent_path(name)) == 0) { + cpu->fs->needs_sync = 1; R0 = 0; // Success printf("Run syscall: Bfile_DeleteEntry %s\n", name); } @@ -84,7 +98,7 @@ void syscall_Bfile_DeleteEntry(cpu_t* cpu, uint32_t filename_ptr, int mode) { // Used by Bfile_FindFirst and Bfile_FindNext // Search an entry matching a search string starting from the specified index // Writes the entry name to foundfile_ptr and the entry infos to fileinfo_ptr -int search_file(cpu_t* cpu, const char* search_str, uint32_t foundfile_ptr, uint32_t fileinfo_ptr, int index) { +int search_file(cpu_t* cpu, const char* search_string, uint32_t foundfile_ptr, uint32_t fileinfo_ptr, int index) { // Open root directory struct dirent *dir; DIR *d = opendir("/idbfs"); @@ -97,10 +111,13 @@ int search_file(cpu_t* cpu, const char* search_str, uint32_t foundfile_ptr, uint int file_index = 0; int found_type = -1; char* found_name; + + // Lowercase the file extension + const char* search_str = lower_extension(search_string); // Loop through each file - while ((dir = readdir(d)) != NULL) { - if ((strcmp(search_str, "*.*") == 0 || strcmp(dir->d_name, search_str) == 0) && file_index >= index) { + while ((dir = readdir(d)) != NULL) { + if (((strcmp(search_str, "*.*") == 0 || strcmp(lower_extension(dir->d_name), search_str) == 0)) && file_index >= index) { // Matching file found found_type = dir->d_type; found_name = strdup(dir->d_name); @@ -385,6 +402,7 @@ void syscall_Bfile_WriteFile(cpu_t* cpu, int handle, uint32_t buf_ptr, int size) fwrite(&byte, 1, 1, file->file_handle); } + cpu->fs->needs_sync = 1; R0 = file->seek_pos; } @@ -631,6 +649,7 @@ void syscall_MCSPutVar2(cpu_t* cpu, int32_t dir_ptr, int32_t item_ptr, int32_t d fwrite(data, 1, data_len, file); fclose(file); + cpu->fs->needs_sync = 1; R0 = 0; } @@ -668,6 +687,7 @@ void syscall_MCSOvwDat2(cpu_t* cpu, int32_t dir_ptr, int32_t item_ptr, int32_t b fwrite(data + write_offset, 1, bytes_to_write, file); fclose(file); + cpu->fs->needs_sync = 1; R0 = 0; } @@ -687,6 +707,7 @@ void syscall_MCSDelVar2(cpu_t* cpu, int32_t dir_ptr, int32_t item_ptr) { // Delete file if (remove(file_path) == 0) { cpu->fs->mcs_filename = NULL; + cpu->fs->needs_sync = 1; printf("Run syscall: MCSDelVar2, deleted file %s. MCS cleared\n", file_path); R0 = 0; } @@ -736,14 +757,29 @@ void syscall_MCS_SearchDirectoryItem(cpu_t* cpu, int32_t dir_ptr, int32_t item_n // Get file size fseek(file, 0, SEEK_END); int32_t file_size = ftell(file); - fclose(file); - - mem_write(cpu, data_ptr, 0x00000000, 4); // ??? - mem_write(cpu, item_ptr, 0x00000000, 4); // ??? - mem_write(cpu, len_ptr, file_size, 4); + fseek(file, 0, SEEK_SET); printf("Run syscall: MCS_SearchDirectoryItem %s (0x%8X, 0x%8X, 0x%8X, 0x%8X) => %d\n", item_path, flags_ptr, item_ptr, data_ptr, len_ptr, file_size); + // TODO: Improve this (quick hack) + static int mcs_storage = 0; + if (mcs_storage == 0) { + syscall_Malloc(cpu, 32 * 1024, 0); + mcs_storage = R0; + } + + for (int i = 0; i < file_size; i++) { + uint8_t byte; + fread(&byte, 1, 1, file); + mem_write(cpu, mcs_storage + i, byte, 1); + } + + fclose(file); + + mem_write(cpu, data_ptr, mcs_storage, 4); + mem_write(cpu, item_ptr, 0x000000bb, 4); // ??? + mem_write(cpu, len_ptr, file_size, 4); + R0 = 0; } @@ -755,9 +791,41 @@ void syscall_MCS_SearchDirectoryItem(cpu_t* cpu, int32_t dir_ptr, int32_t item_n void syscall_MCS_OverwriteData(cpu_t* cpu, int32_t dir_ptr, int32_t item_ptr, int32_t write_offset, int32_t bytes_to_write, int32_t buffer_ptr) { const char* item_path = getFilePath(cpu, dir_ptr, item_ptr); - printf("[Warning]: Skipped syscall MCS_OverwriteData %s (offset: %d, bytes: %d, buffer: 0x%8X)\n", item_path, write_offset, bytes_to_write, buffer_ptr); + printf("[Warning]: Run syscall MCS_OverwriteData %s (offset: %d, bytes: %d, buffer: 0x%8X)\n", item_path, write_offset, bytes_to_write, buffer_ptr); - R0 = 0x13; + FILE* file = fopen(item_path, "rb"); + + if (file == NULL) { + printf("[Warning] In syscall MCS_OverwriteData: File does not exist: %s\n", item_path); + R0 = 0x40; + return; + } + + // Get file size + fseek(file, 0, SEEK_END); + int32_t file_size = ftell(file); + fclose(file); + + if (write_offset + bytes_to_write > file_size) { + printf("[Warning] In syscall MCS_OverwriteData: Write offset + bytes to write exceeds file size: %s\n", item_path); + R0 = bytes_to_write > file_size ? 0x13 : 0x14; + return; + } + + // Open file for writing + file = fopen(item_path, "wb"); + fseek(file, write_offset, SEEK_SET); + + for (int i = 0; i < bytes_to_write; i++) { + uint8_t byte = mem_read(cpu, buffer_ptr + i, 1); + fwrite(&byte, 1, 1, file); + } + fclose(file); + + // fwrite(get_memory_for_address(cpu, buffer_ptr) + write_offset, 1, bytes_to_write, file); + + cpu->fs->needs_sync = 1; + R0 = 0; } // Deletes dir.item. diff --git a/fx9860-emulator/syscalls/keyboard.c b/fx9860-emulator/syscalls/keyboard.c index 6442ab1..c871c65 100644 --- a/fx9860-emulator/syscalls/keyboard.c +++ b/fx9860-emulator/syscalls/keyboard.c @@ -44,19 +44,29 @@ EM_ASYNC_JS(int*, async_browser_getkey, (), { // Used to resolve the promise when loading a new .g1a file (blocking otherwise) window.getkeyEventResolve = resolve; - const onClick = (event) => { - const res = window.keyboardEvent(event, 'getkey'); - - if (res != null) { - // Valid key pressed, remove the event listener and return the key - document.getElementById("keyboard").removeEventListener("pointerdown", onClick); + const try_resolve_key = (key) => { + if (key != null) { + // Valid key pressed, remove the event listeners and return the key + window.removeEventListener("keydown", onKeyboardEvent); + document.getElementById("keyboard").removeEventListener("pointerdown", onClickEvent); window.getkeyEventResolve = null; - resolve(res); + resolve(key); } }; + const onClickEvent = (event) => { + const res = window.handleKeyboardEvent(event, "getkey"); + try_resolve_key(res); + }; + + const onKeyboardEvent = (event) => { + const res = window.onKeyUpdate(event, "getkey"); + try_resolve_key(res); + }; + // Add a click event listener to the keyboard - document.getElementById("keyboard").addEventListener("pointerdown", onClick); + window.addEventListener("keydown", onKeyboardEvent); + document.getElementById("keyboard").addEventListener("pointerdown", onClickEvent); }); }) #endif @@ -108,6 +118,7 @@ void syscall_GetKey(cpu_t* cpu, unsigned int keycode_address) { void syscall_KeyBoard_GetKeyWait(cpu_t* cpu, uint32_t column_ptr, uint32_t row_ptr, uint32_t type_of_waiting, uint32_t timeout_period, uint32_t menu, uint32_t keycode_ptr) { printf("[Warning]: Skipped syscall KeyBoard_GetKeyWait (wait type: %d, timeout: %d, menu: %d)X\n", type_of_waiting, timeout_period, menu); + syscall_Bdisp_PutDisp_DD(cpu); mem_write(cpu, keycode_ptr, 0, 2); R0 = 0; diff --git a/fx9860-emulator/syscalls/malloc.c b/fx9860-emulator/syscalls/malloc.c index 64b9706..64a38d8 100644 --- a/fx9860-emulator/syscalls/malloc.c +++ b/fx9860-emulator/syscalls/malloc.c @@ -9,7 +9,7 @@ void syscall_Malloc(cpu_t* cpu, uint32_t size, uint8_t clear_data) { // Use the first 4 bytes to store the size of the malloc *(uint32_t*)(&m->mallocs[m->currentSize]) = size; - R0 = MALLOC_START + m->currentSize + 4; + R0 = MALLOC_ADDRESS + m->currentSize + 4; // Clear the data for (int i = 0; i < size; i++) { @@ -19,7 +19,7 @@ void syscall_Malloc(cpu_t* cpu, uint32_t size, uint8_t clear_data) { m->currentSize += size + 4; m->mallocCount++; - printf("Run syscall: %salloc #%d (size: %d, addr: 0x%X) Total allocated: %d\n", clear_data ? clear_data == 1 ? "C" : "Re" : "M", m->mallocCount, size, R0, m->currentSize - m->mallocCount); + printf("Run syscall: %salloc #%d (size: %d, addr: 0x%X) Total allocated: %d\n", clear_data ? clear_data == 1 ? "C" : "Re" : "M", m->mallocCount, size, R0, m->currentSize - m->mallocCount*4); } void syscall_Realloc(cpu_t* cpu, uint32_t ptr, uint32_t size) { @@ -30,11 +30,11 @@ void syscall_Realloc(cpu_t* cpu, uint32_t ptr, uint32_t size) { if (ptr == 0x0) return; // No pointer to reallocate, act as a malloc only // Get the size of the previous malloc - uint32_t prev_size = *(uint32_t*)(&mallocs[ptr - MALLOC_START - 4]); + uint32_t prev_size = *(uint32_t*)(&mallocs[ptr - MALLOC_ADDRESS - 4]); // Transfer data to the new malloc for (int i = 0; i < prev_size; i++) { - mallocs[R0 - MALLOC_START + i] = mallocs[ptr - MALLOC_START + i]; + mallocs[R0 - MALLOC_ADDRESS + i] = mallocs[ptr - MALLOC_ADDRESS + i]; } syscall_Free(cpu, ptr); diff --git a/fx9860-emulator/syscalls/misc.c b/fx9860-emulator/syscalls/misc.c index e5984f7..53b2d87 100644 --- a/fx9860-emulator/syscalls/misc.c +++ b/fx9860-emulator/syscalls/misc.c @@ -75,10 +75,10 @@ void syscall_OpcodeToStr(cpu_t* cpu, uint32_t opcode, uint32_t string_ptr) { printf("[Warning] In syscall OpcodeToStr: Invalid opcode: %d\n", opcode); return; } - if (opcode >= 63745) opcode -= 63745 - 1201; + if (opcode >= 63745) opcode -= 63745 - 1202; else if (opcode >= 63232) opcode -= 63232 - 981; - else if (opcode >= 58624) opcode -= 58624 - 501; - else if (opcode >= 32512) opcode -= 32512 - 256; + else if (opcode >= 58624) opcode -= 58624 - 500; + else if (opcode >= 32512) opcode -= 32512 - 255; for (int i = 0; i <= 15; i++) { char current = cpu->disp->opcodes[opcode * 15 + i]; diff --git a/fx9860-emulator/syscalls/syscalls.c b/fx9860-emulator/syscalls/syscalls.c index 710288c..ae1f05f 100644 --- a/fx9860-emulator/syscalls/syscalls.c +++ b/fx9860-emulator/syscalls/syscalls.c @@ -37,6 +37,7 @@ void run_syscall(cpu_t* cpu) { else if (syscall_code == 0x138) syscall_Cursor_SetPosition(cpu, R4, R5); else if (syscall_code == 0x80E) syscall_Cursor_GetFlashStyle(cpu); else if (syscall_code == 0x811) syscall_Cursor_SetFlashOn(cpu, R4); + else if (syscall_code == 0x812) syscall_Cursor_SetFlashOff(cpu); else if (syscall_code == 0x13A) syscall_Cursor_SetFlashMode(cpu, R4); else if (syscall_code == 0xACD) syscall_Malloc(cpu, R4, 0); @@ -90,6 +91,7 @@ void run_syscall(cpu_t* cpu) { else if (syscall_code == 0x3ED) { // Interrupt_SetOrClrStatusFlags // https://bible.planet-casio.com/simlo/chm/v20/fx_legacy_INTERRUPT.HTM printf("[Error] Blocking syscall not implemented: Interrupt_SetOrClrStatusFlags 0x%X\n", R5); + cpu->execution_finished = 1; } // Can be ignored else if (syscall_code == 0x494) printf("Ignored syscall: SetQuitHandler\n"); diff --git a/fx9860-emulator/syscalls/text.c b/fx9860-emulator/syscalls/text.c index bcde792..6584b5a 100644 --- a/fx9860-emulator/syscalls/text.c +++ b/fx9860-emulator/syscalls/text.c @@ -273,42 +273,32 @@ void syscall_Cursor_SetPosition(cpu_t* cpu, uint32_t column, uint32_t row) { // Returns the current cursor flash style void syscall_Cursor_GetFlashStyle(cpu_t* cpu) { - // printf("Run syscall: Cursor_GetFlashStyle (%d)\n", cpu->disp->cursor.flash_style); - R0 = cpu->disp->cursor.flash_style; - - // printf("Cursor style: %d, mode: %d\n", cpu->disp->cursor.flash_style, cpu->disp->cursor.flash_mode); + // printf("Run syscall: Cursor_GetFlashStyle => %d\n", cpu->disp->cursor.flash_style); + R0 = cpu->disp->cursor.flash_mode == 0 ? -1 : cpu->disp->cursor.flash_style; } // Sets the cursor flash style -// Remarks: // - Sets flashmode on -// - Resets to cursor-textmode +// - Resets to cursor-textmode (?) void syscall_Cursor_SetFlashOn(cpu_t* cpu, uint32_t flash_style) { - uint32_t mapped_style; - // printf("Run syscall: Cursor_SetFlashOn (%d)\n", flash_style); - cpu->disp->cursor.flash_style = flash_style; cpu->disp->cursor.flash_mode = 1; cpu->disp->cursor.blink_state = 0; draw_cursor(cpu); +} - // printf("Cursor style: %d, mode: %d\n", cpu->disp->cursor.flash_style, cpu->disp->cursor.flash_mode); +// Disables cursor flashing +void syscall_Cursor_SetFlashOff(cpu_t* cpu) { + // printf("Run syscall: Cursor_SetFlashOff\n"); + cpu->disp->cursor.flash_mode = 0; } // If flashmode is 0, the Flash mode is set to 0 and cursor flashing is turned off. // If flashmode is nonzero, Flash mode is set to 1, and cursor flashing is turned on. void syscall_Cursor_SetFlashMode(cpu_t* cpu, uint32_t flashmode) { // printf("Run syscall: Cursor_SetFlashMode (%d)\n", flashmode); - - if (flashmode == 0) { - cpu->disp->cursor.flash_mode = 0; - } - else { - cpu->disp->cursor.flash_mode = 1; - } - - // printf("Cursor style: %d, mode: %d\n", cpu->disp->cursor.flash_style, cpu->disp->cursor.flash_mode); + cpu->disp->cursor.flash_mode = flashmode; } // Draw the cursor on the LCD screen