From 0d5343ddf40b5a6a4aa3008230da5cf20448d28b Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 7 Sep 2023 12:03:34 +0200 Subject: [PATCH] intermediate: check image files --- webinstall/install.js | 87 ++++++++++++++++++++++++++++++++++++++--- webinstall/spinner.gif | Bin 0 -> 17490 bytes 2 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 webinstall/spinner.gif diff --git a/webinstall/install.js b/webinstall/install.js index 87d8c33..95929df 100644 --- a/webinstall/install.js +++ b/webinstall/install.js @@ -3,6 +3,75 @@ import ESPInstaller from "./installUtil.js"; import { addEl, getParam, setValue, setVisible } from "./helper.js"; import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm"; (function(){ + + const FULL_START=4096; + const UPDATE_START=65536; + + //taken from index.js + const HDROFFSET = 288; + const VERSIONOFFSET = 16; + const NAMEOFFSET = 48; + const MINSIZE = HDROFFSET + NAMEOFFSET + 32; + const imageCheckBytes = { + 0: 0xe9, //image magic + 288: 0x32, //app header magic + 289: 0x54, + 290: 0xcd, + 291: 0xab + }; + const decodeFromBuffer=(buffer, start, length)=>{ + while (length > 0 && buffer.charCodeAt(start + length - 1) == 0) { + length--; + } + if (length <= 0) return ""; + let decoder = new TextDecoder(); + return buffer.substr(start,length); + } + /** + * + * @param {string} content the content to be checked + */ + const checkImage = (content,isFull) => { + let prfx=isFull?"full":"update"; + let startOffset=isFull?(UPDATE_START-FULL_START):0; + if (content.length < (MINSIZE+startOffset)) { + throw new Error(prfx+"image to small, only " + content.length + " expected " + (MINSIZE+startOffset)); + } + for (let idx in imageCheckBytes) { + let cb=content.charCodeAt(parseInt(idx)+startOffset); + if (cb != imageCheckBytes[idx]) { + throw new Error(prfx+"image: missing magic byte at position " + idx + ", expected " + + imageCheckBytes[idx] + ", got " + cb); + } + } + let version = decodeFromBuffer(content, startOffset+ HDROFFSET + VERSIONOFFSET, 32); + let fwtype = decodeFromBuffer(content, startOffset+ HDROFFSET + NAMEOFFSET, 32); + let rt = { + fwtype: fwtype, + version: version, + }; + return rt; + } + const checkImageFile=(file,isFull)=>{ + let minSize=MINSIZE+(isFull?(UPDATE_START-FULL_START):0); + return new Promise(function (resolve, reject) { + if (!file) reject("no file"); + if (file.size < minSize) reject("file is too small"); + let slice = file.slice(0, minSize); + let reader = new FileReader(); + reader.addEventListener('load', function (e) { + let content = e.target.result; + try{ + let rt=checkImage(content,isFull); + resolve(rt); + } + catch (e){ + reject(e); + } + }); + reader.readAsBinaryString(slice); + }); + } let espLoaderTerminal; let espInstaller; let releaseData={}; @@ -130,9 +199,15 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm"; } } - const buildCustomButtons = (name, updateData, fullData,version,info,element) => { + const buildCustomButtons = (name, updateData, fullData,info,element) => { let bFrame = document.querySelector(element || '.content'); if (!bFrame) return; + let vinfo=checkImage(fullData,true); + let version=vinfo.version; + let uinfo=checkImage(updateData); + if (uinfo.version != version){ + throw new Error("different versions in full("+version+") and update("+uinfo.version+") image"); + } bFrame.textContent = ''; let item=addEl('div','item',bFrame); addEl('div', 'version', item, "Custom "+version); @@ -146,7 +221,7 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm"; await espInstaller.runFlash( true, fullData, - 4096, + FULL_START, version, (ch) => checkChip(ch, name) ) @@ -158,7 +233,7 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm"; await espInstaller.runFlash( false, updateData, - 65536, + UPDATE_START, version, (ch) => checkChip(ch, name) ) @@ -224,11 +299,11 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm"; let buildflags; for (let i=0;iCMN?B$Ju^X5H&r*Sb{Hl%=FCJ3ySEZvb$5db+u}IX*t# z)z#J5*qEK29UmW0AP~I0y&WALjg5`(-o2}!pm5{H4QXj<2?>c45E%IX^J4!Ag~HB0 zz}vo7~ge*FP}$VrY0|^yS$2tBKcdCf`oIo1U4Sdq2OhxU{^o z`eAK-V{_}{_Rj9!r~QM^U%r0(e)!|)=da(#e@;#T2#QU$p41bHxWI2ZRA1N^NyDk0 ztJ+XB5W_6yG(Xf({2~E;E1FHMv1B*}bD~>iI^0+~njvgGlB?EKHkKpxXnTIRsr*#| zHWYP9y}9D`br6`%m79zO!*pnAjQsg=pbDb~9vZB-9so7&Q@k`GL2uMx)seoZ1PGZx zooSI63({3ksZjn?1`Wwiq%s7UQdtK#fP3yheZ(NXOkREgKPbZBj9dr6tC7!>oJKt{cu?x@1C~w?D*k5MQ%rh^0>J6RIpqj{+8hChc(8KAVy-11wiho!~ zLzb`Jy#VrMTEe}YVp0Zjj89(Vp+F@OQ=pGYiDVr8RZ;=|!Myzf=vxOb0D@A{up^;* zMk|iYK9=T=oEyT0QIOuWRi>wDK0x#h1YjM0X`DC!Y+Ta8Cdj0=f4~X-^1Tg!#f_Cf zD(8sB$)Q*l?m*ppEW~(2LPkk?AasJ;*Fr{N?E*#Y&j(p5auf?0>h6B)SuTeY7G}PR zD)w}6(+-O~MWeJ$(ye>1H{(OVtWdL1I+O4;hWPR=8ZC*rt$a0c<^piIwmP&peS^=D zK@)|mESCwJb116##9mS5+c?MXQ9mdnfF|{3uRrnCGuf%^989V%Er#dhR1L5OxD*fb zU)%s-O0q!x_=$n@UdxPK^2p@P&=kl5HSmtG_V z`@6By?JG%go;yQ@^SQVZel58B@0ugUwqkBd2Ie|rrf z@3$@`&R5i%pS-Uny_pxOE%i~>VO(lk6Cp3NBb}N}zuP?R_Is~=v-bC=&L5wC?~^I6 z9Ut^FJv{zA#9Md#<>mGL3pPv+nfd_{aX~DL_R6C{)Q{)&m?ulLTfOBEw}4@HDX` zD6eV{%H)8+Ol5&MQ?CCgm|#6%1(*T%f&V2}!YovS9?VKK`+;@mh+*!Tn4@@ABm9si zo7ZrKmy(0z`ANZh7A>qTx*)tTu9eEHnq`-BX7M^mAhDHhrVC4b?zF7kTQk^KBp}fJ zaR^=$9wzG+8R^W8i%|@Xf0UGzDVlyODoY5Qlf+L^fb=X9N-T9x;er$x34p*52)IGF zwobB>vm&O4ql9v37(So^>8|GoDR@KpVZ$@Sg}R_ul5wpL28e15e0Ga6Pc5QuN@!dO z0ecDk_;G*!)$%pa$x9CKyhaAFe9pd(>Q}oY&4MH!;=ItKR1*7Bs0)}zSdYYZ8PE7y z`pXLx4wtxfLZ2?@NyjajCJ=!0iW0Odix#Jy@v=#<>ym~b=+X7gyp*}@-cM;vNR^WX z%394-m2WEb%g+~&pg!J(O;#H`bGoFkxp;vIM6SA&!*RJ03hzJBp)|Pqp6kI!$#DPc z(TmlAzd44huh&~#0iVc(`}xMNmc`wlXSFu$2L@ti!H(oCu~3K)GBPfgpuIVn=d7lm zV_c1WTYr&WNrcmIr-9Pyg@IOfBxvDj(_uQ5=?}t+EQ)^GjOAw(bkw@;Q9MDxYev1Y zsr}r=VjsqL9goLPZos@g=ay$&DP#)@-PnP&-|Pw=c=)+@Cu1lm@A@n$b%Alpd2~#9 zm>F?zlyS-L=}&u20!=T|tX;VXb{xs7fGEV(Mk%~K&#GLpM9J5;bSOP0oOggUmY^wMgU>FhF1WeneEIcW1y=Gdzrt!BG#tlD9{^RtXooiYbQ%OWQN209kN-tvo%VRMb*tx_Fq_>k}Wjr1wNsAOWr7p%Os6fG_TcgSWND! zqY*Q3gG~il26`$7@g4!Ousj#fSm3J5Nc8&++rLsJ_xVWa6R#-@2`-dSeD`z~6R% z?0c`Wi%!(#$n$NWFl-ZU>P>`kufl8uu-c?c&uiipAyQoKcQM3INgrJT#&e#mLxpD( zl>iXrX-SE~#A=TMlz!V(ObpIWykt!^p{8f~G@mp0jRw5BVlrJ!!~a$#N4aj1UQ)WM z?=lvord7u{B{LjrRHQQuX7M+$H)+wFdm8@x&L^#>>NWYW7$yT*Hv6VS?lcP!1WrkV zklab#T&{{ZuOZ)wHd8DLypkg<+|gvy16VyD7@ci$oXEDN5+2L$*?P-K-z@2RzBLYL z(z@oG_Y~_ksp49IJ$NtXw8BMXDhOoq`?8*rAWN})@z=`oq@j_@OW78et(K;0BkJd} zyKlOiy4I}BcktiVP1@aibmRbV(v53FE^b{RFIH)Fm~EpA50*l*Y_pgs1t?CY^WDMX zTUUGz)6fNBP#vQcN#?Hl6)IYvBa-VM%OiGbc1bA?PrhGmMUSqv&`iegzH|5xC$&-W zAztoqtN70DYvR1WDgDo4joUoK14$g1%96xv>B_YvCG|>6=^~_IY3fblMyaboCDCYZrskC=+KB+YLKdc)6i&Yu8V)b9f zao++h zZiv3@UDHEq`iWCG?6IGAK%l%Q?vW3lZ&*a6sAP0VqF$J^TM{uf1i}x_lp^Ga=Y_;# z;vb}!@|Sy6@|uM;HbIrVpWLgD5eAz-DB*`82VV^K-EV*H$Pa=UP!znP7@9^>JX5<> zKhh0_!JuakznK~M!5G?dou3$R-6;3ktzmv^2*>K17Il+*089e z!mdPa@ETim3NKZ`7aIh(`r9o^i;vpz?ZaLet#XCRbBXFj{JJ%f>HcF##-*7;+a=oO7Gx$#1K1}3p^|jIwLXcK@Ak7XRAdH_{$#FtFi57-(bD70X2H#NkM{&3?Ro7(LGUX-@sWd_`YUNF=IQ_vo+5-P zO6w8pG4eywym>jh?Q4&8cRTEIgrUp438t61Fa*^RA+tD$?_LF{Kq{GMVMj*6r=o`4;tRn%2c==ZdF{{7x$D|FR(ei1fd~ zSBK-b&%o~nS+e=EzYN^(gXHQMIsAzqVzC^OV~!oJNab>nq@1j2ws2K!c}>P)83Xn4 zZYe-I9qOR{T!kI$+;E$zysyyCEngM?08d%|5S$c29uyw#!ml4qVH7I|NeH*+xgH&q zp`INO$)|vOd^S0Gl=7K+*Ybh=p}EFkHI2OSPNZZCWN#nEvwPqg4^c?IQX4m{?@eEl z#W1W}{t@mF)w3DRpZqR!TzFi10crXI!r zy=>-+TAS#NYAvDrEX~U_?P?ky=W0<*TMIc@E1avqSQmqWXWh#4LpZO3d;4dCUUUMp z`|gp0jce*9f%br(*o~2l0L;<+kV_!EhWwt))edIRU zseu@JE`1a;T!+7SEeZP_BtD818_)F($L&l@+m8iEDHs7E4c627-m#|LtCJ}Sd0jzX zZ}%p4}YreS-&_{JpDeA#cV-llY2Y8=>MC(p&o!xNw#CX|IC)pxKkU9_ zqzc2?MB8x|+)@n2PM3Vz0fGy=j#X6RXC`vAK+%joNI1R1)Sds`kY&tQJ`!gn@_?6+ zaMOUfBoRq(yz)qqm1Gl2W5>=(1(q9LP8E>)p31Mp=vTsM;#4^T+#wiQWODfHt;IPM zX4zr1-!a=VyLA~)5iapt*q!&DV;KQrMJmXkC9T)I=^5YoxPlz6MrUWnEAxLWNsV9x z|7A2_Fn|USY&CV>9m;?4E<)Gq_Z{l#!K7E2FfZ+^6YT7~Vw3bMlGNS;Va<^s54 zvf2~Hu!&9bh%H^?grlMH1u7`W0|i8&1~oy2N(k}yucX=9Z_k>6eU<$KZan0>cU7C9 z6c#Q5#PbI6#l)s2XkClqe-ImzB^((Y9h+RppP620t5whq7dY|_cC|`1Y;oloMmq~)ur@M=3SB!4-E6`nUN|m5W$uO3>nqW zp>TF5aH<(bx*Sk&U3P`ZEZxOnw?4O@m6b$t2jr*48moO0FS&@zB@Y(SdWV7Ya-^S3%TS+~l@x^#ZU-fm77Nk62Yh+~Q>o}2LHzUWxH)Zwr+ zh&gDhCy)uUERwMjB6008rySUABPf0-R^SB|*KwdcmLPj70kGz5byKKE$_r&QZ`0=* z3mhHK_1n(IIYV%Q{+_N%HIGgsuMHHI8h(9|FC){GL?>6vRae zgwZ+R0tmDd7NNaqa2)soqc7SQY%^mK{7`L+9Zz($BNL%-Bb>!kUK))Jmz* zoIz+s0u6BeDhUYNSL?Tm5&s3)7&8|geemP(B3i{OM69iap|HsQBvb0y0<&MzHhmfh zB|NvErh&+YyS+)fqU%4J`d>WSobhP>FCI~#USR~D|15>?dpg*))bHo+E#>4D zjK%x=h{7I4ia(5Uj}MDUkVs1Q6a$6JNdhLpS=p(WJYGl{k06Yqj^c)^Mm97^R1hi) zgM^mX*7rcGRlzL^z_28|XN)4wP&&EG6*@j{cK40n+XtZO=>=8LAoN=QgLlhgkc>|p zz{{PU{Qbwk*D?6kq0Z6I`or%5T<0yG;|Qe}uH(QLf3eh0C^ItCxnergwP2J? z=rNmIc9I1)g6{-dC3l@JqX7(a`7H3-%62Hd*rhf%N=%hG4?Xam{@w+Ly25jDPqD08 zmHcG}(R1{u?=MtxII7EQp19KgmpqQ4KHC&l)!Im8tcv250}e7aaiTxZpu6b@A$BgVUM3E zBx3RUpC_gbefEw?%NEp&2j~YEk^u{SCXTImk(UP?eW2@zqX9MqT|EwhDVxC&dG(~0 zIOBe%t;X_QoiLV@n0egK?*?<{h{MXbbAq!Rw<5s`JW+J={7N?ANi_6{HpwVbA|#0$ zO?sE2axBH2NIOU|bQduuPR>vxNTo)=Ni1t%O?DzKN5LFXEGo{0P2;A=u&8^3}|A|-4vXJ89Dww#38=k*iH^i;K z)vpMF5PU6S7g~E8e=spVLmJGm#Oc=g?X|s+&z}7GSVYp_Uqr!63NP;$X5}G4gh8Tj z#Q7)iMLL}2t!|lo!M8}c*YXR*vZZf}f=uAfu=l>*@5;SSX?>sJ8$%@7P<3kJV6fBk-Fb>{Ai^{&w^{Ps?#*{A)_&%b_ies)OF z^Yaeq=#tcreN|Av5lS@19*2%%M8VRW9Vl~eN5WjWsu}uT@uig0pQDB!3iV+~k~_D?1aiNK(tKB;6 zghr7FgmP11F5iJERak~0*A#IfyB&|n5nQ4q>{ZzEW|ks6)o3;9X+;K>xCLDO9&PMbnn8jcET^e|g4v~5zmo*=x}PNCVkrBO*}#}WMP7kl(S`qE5&q)U>|c`1 z4|e%`zN^3z`)U5-6`!NF{EH=_-XfalBBKUaI{m1*Tv>&0;isX3;#V~QPGZ}QeT%Wd zcx(G_nq#%2L)V&)4fjK=-7xn8#Jz%@@w|7jVUCgBQM|r{v;6?^F~K8^KO!;-qt2!2 z4l;+9IDj&ob1*=eg*XTXZ*7B;RDhb4IB;7}E9|LCLRT25cNA8u4CD_Ow2bzec-Dg~ zb2&z)ddKGH$xFR&R<$APy+d0L+dFN0pA6PM!#BUZ`T<}06#)G-_WK7EXnE*wC&lT| zi|!5AsduqH0?#QxD$w!7)P$neD3q>4$7CcQ#n8qpAnV)EK$sZU5mEJGCI$KrmOci3o zGJKRRiKMRAxYOub4aONWJAAwseOLcs8}p+-W#p&#-1F)6sJ}`qH#5)YC?^r#RvsCz zkA4fY3KsDED)5RV;CT5nb6Ff1CR$d~naTvd5T%-d`t^*=WV|5RaodV-iXA4R{q?=h0*cU z>rf6z5P(R!%R_Vkm->v^ePkJeImAdHXW4kR6xgt?EGp3*|m@hU(=kq-Pj9^Qoy#DWDs|m0LjLtH#|IT|^5l|6? z<5?nhw|YjpYBih1&BzwKMphs@v(8opDn2@!ZEh$v2ivD7B`%41qmc+XQqki z-vqdA=hZmDR5iJNB;0Vu zD~D}y+*cJ)o=n#FTy_96k;4(fTKdt`A+}VePdW{hR`#}%uWGzA`g_>DCtAnJxrDJ&m4PKm?W zj3!9!V8}Cj`U4>oA)1MSVikMr{c+EYoV?%6WbD(&ABOLK8?2SCIS*Rs4l$fSi2sDv zL1+x?PO!x@GkX{Lu-&I34o6dZ363+M$lky|L4$^7NSNUAa_G5ihCc5$$4Di@HzATD zn8v3a2i{1sAiGk2iC_;ZaNjD7Jcxa4LXgA zk(4MMYjXRVQG)tg^Q0PsQP~gYh94C8?p0-ZZ+Qv5ygUhisAwu*UH5}m!&Ey^(knPj zR97!tJ~CJ`Mh1e5<0o3(5R`^QrAuVG78pTbWw$*OoQpZYWp#D=I#rF~we{_37S>N6 zfZEB>n&MJ&drP0;V9#*eXnXHi;H&!AZycd-%ao@b+ulB3z|OvUw`$lu>c3gJHnqZK zysNHTq;CA>Yvs|=FUR9Q$HpLLon=A7PbwaTBSlO4h;vuKu~PNuftYDlt3Zx^oC;(| z@&tX*Py%w}IB}*Ic6?*1&cJ1yt#s(A1Y`+PzU>T4;qHtR2?#DR7OT*KC_P|h$QCcf z6<-Ol>O|^qj?-({N{x79c_K`zRmr^NS=T;rx{%>&DL!l zi=C;Q0La%geo->0hd@m;=*#S3)edEoJsdR>wSu@ojC?w!VDS(XJs0+u8riNt?+(2Y znbhJ8N9l}+1j)E>`y$PE++4kVT;E&J?_Q42diC%*MKT-nxM^$S2O9+u`&ukg8muL8 z+9hQ5^V^CV40le;T;Urn#7S4V=hyE~s~A*jK^=gv{gysW;dxU|0rm6Sn0*1N8*KnQ zzITw&cHi3$#*7Zkp9)2rqqYZ0$Pdy|OJOQ{M<$*;2+~UE0%DUL@;nJxCVut4MUJA{ zwFF|mc_6}~P>QOG5#kYcAI?McDgdf-`*@`dAK>H707o_LqAA4gd0x?BSz>%^9h1e663MyBEWSh zR4G0gmq`Q4EiFyEL*lG(EUSgaISA1-xB+#wkfLWa;P%>@P8~>hS$J>6z~E5)Na>50 z5BtZ@q<0+pE@HBKYR*Ev8`82=*;NZ(6E@ynZw96AC2@cGq!Rc2@YipFWJDT!ifz%f>>`&pNcV!AUG=8 zab$~G)+o`Iq$odbv&s##w&)2hR$(&LB&u^oft8cggX%3R6mq7hG#C7v*Aze+1*?4I zmYONTc~0%)3nZ6W*v(N9@g%bAY`yUNx%H<2Zx|?GbdaG!;^1A=mGjJt+YGiV1F9Dj zxB%mAix7W-AM4j2E9(uxzrpw{$XL9GE&t8-W94Dc1q#d{7GSB909rN3(X?3n3iObx z0LvEMx-kD5+!^74{eXLQ@4^f{58G{T1t=ApFc#;11sJtvwvY~mDNW{64RBu*?;-p+ zU}8Qv*LchT-2Sj*DE!CVcVo8@R}c*Cnl7Ln%G-5cI1i;aB7wj4xG!T2pv$Dt!eR6< z0Inz8r;=58y&MiJqQioaB^76neP|*(>Vw(JG7?3nQ)O&nPn-JljN|T1R@mTG7j`9$eF5E!=oSv zR$l_(av8}Z`l>Js%L#p3`2l1*LgYyg`c=sR#w)Ve#`dvPt%l4Ygt}P3j6h#T~tjO{4={ecV zuz$t8|KR_BHq8eth-R98-`XbcYrAB$+eS4qK~=Bev4%`SKlh#*wb7G?iq!2+f6F;; zrjJ71y@>j)OaBoP(K`5DtrtZs=ZBZvD^n5hKjkW~*FdJN)9c?q3L&cc6 zHW*zXM`Ubmt{M>W*G!EYFiHZ23XSxz z+aiVX3w3m-pF6}a{%h?eCBS`ioOUfj1dv|4I^7_9b9qSCix<&f{h+gmCgxLT%!i6f zb2)>{FdD9z5Zu$X*^@mTlc3d)_g)1fILcJnC>w1D^JzZP63uKt%!$k4zlSNXcfuI3 zWFH}6-oi)cqji7?W~b>`A}7kQ2*4Con2MlVF!LsigG6?s9*u>yyLKUoGn$(~hxO$g zpP5Kxh9jaAg!m8C0ZsD?E@26EBd#=kj;#_t3UCc zFoGVp5n&h1XOOn0FAfIXI{dha0H^vD=2LjkpHC3}2i*VCZM*+(iTsaln}G2%RcE^0 z{44qKlYO~@AAdKxiU7%C&oFw4nC|s2(y{7kdPax*3%}Ye(yrT5hrY03n-jeM6>x7g zji*X|_UJv1tNr4Foo%q6DJXz9GVBo$#S4y3#6WOKilH>AY1oXc?1#cB`4$O|MW(UI zd1bN@JaN^A05}o?uByM`?$Q3g8(kuTKA^VIMkp6F_hshW_!*Hp?vi=l>6KcM7El?XB5`1FqdAzNaG@t56Cg3JF)dRL#4}RtBb|G+Y2)6)gum9)Oz=*=DC4vg;kO{w;K8jn%z0U8fgHeQY(xk<%&!e| z8v}>}kFRn^Up>Djaf?*9f)lg3WRb0d$2VioUop@Zz@hkTmbAKr&{hqiwo^|8l$dG zSL#DEkot)f=865Dk3B^zx*>!+U0k4KoRpuD`u?*-XkNTl&spXe^OSrjbfVgJAhTHTA#I{3io*`cDStj-GGR z*blt&UMhE7Meq|&A=hJAk2|HWEcGXqa`_lzj3I+n`P{n(fg|<4RkI&XJVldYC2eDY z5B4@twfO#fYAC_zb8v3UGAo84d+mY&VA!u+JVK zjauhBzNXWqauexpS1CK*E#u1D?nJiq1C_#+2A4b-oL@c>hWf4}4+mzjP!ux;o9?Jx z=>ldm!nS=PK~%t^Jw<(O>;F0b>!upn<@x^m+H% zjs8m_x+hf37wu?^)uttaWLmbLmEZOaevbCC1~-3nvi3=rL{hYVTO8V(-#=eHl96gq*0oQC9=qIv>WQV*YGmCV9>|@{U#K@m8)a zw!oVNeC9q}cx)PcjS9Djr&t~Nt^$BLR4mjH%EZMGC2?OiMtL>NN~j2y7acBwAQeW` zw7f<$!ec8|Lc&|VgG0Y~_!6R7%`;R%$Di%VA(!2LgcHS;jfy0t8U4rs+G`ULcm)nA z8xCdGjztVsGAkb?rEFXhYi#640`Z-VYe}m41YE{)sW3THxi+ORt;lAWC&X0J1D9qS zkKW8hZBLcN`F|OHaKp0_fiWb!p`!uW%lv#$q-$491pJ;z^JacKn=y0IiY@j@c?J^Z z@o=T=kBGzx+P?#{zst3PwZQ*|`|l=gJ2yT&y4G`>RPSE`xTUtlzE=xJnH8_^AET$l$Xd`a!`DaXO)H{^7SH zU85dAppf`zLU0NO1Vd5f<|1Ly+_BfdlzHX3DDOfb&F?C-qA|C+ur^Ex()6sQ)kY}i zStGJWx}moTGH@L^)I>2VIo?G1S{OXpl<|&dX13z}JQrwbc~xjV549f z`4`OC(!>v$p7EVyBdBn=rb4@w3!I9~{Q)Nop)gk9z$O(v_khu5njG*I^+%&QVV#Vk zYXeCtC+g`+d^)zO2_RTI6q)h>m&M?`_$(>~&|nm`&!WZIgEi=woYI>+Xxg`cdTq^Xc*b^9om zb#JtmWx3IaIAF6g-Wd)!sg1&S)BpIdxzEN*T*bo-#5Z*7KG!eOeScM}#bh zw#-d@Se#qk5&FNGzvCBl8VF3H z1(X1m4f&&_s?3N2i}tL&|Ixn?<9l6!mAE2aJ;9>3Zk*OIwS!3Dt;q_fdHdP~v18Z# zT$*X~ezVOY7%Th%t0&iG^Y*#)Vfd0U2%`+WuLlEn-unf+ppxB@*~`rL*X<(V7`f(6f2=1aMBVYvebPg^rGF}OcY(PFDcC2 zg*b&gmpydF--^Z_FbA~{U5S1`@2N;Qm>n`FKB5{(44I*|;7oW)wZ@UiJaxn#e;uX+ zglW>1(SnS+%9Z|=cGaC}qY3ceotfyfstlYq_pdDB#s&4_{jtw;WD*T=ndKstqA=Oz zTtRQYhY28AH?g}bym!vYu*S!f)$19hd#0LOOvz@YXRF<_7x=S3r26!ev7Nt)V~{1z z8WyS>;^jj$j*d}|OHAj2rYL9Ra)YyF!}E$MN!0qqm^8QK?CL~kBB(TlqB59R5QAz9 zt~1HQ73o=DBH46S}psP6HCFh_5KN5+6KX`v@K|+dhdxsJUURP|F zfbV|at~iivkboVYJo+h!JW!c1A@d>;znZ*hCa~crRFciF&llamU3j6?WOV+4vd~58 z+OJ2ns2fR~CmKA89p{SG9|=yW1tK}QR3PFlP#v_Vk+ucL@Sm58KoPj`maT&TEqUXq zlUf>z8S71!W#&UUc+=`TPSru|e8NG8J5vpJz^Q-u8TW{l&XxJzlw5989bV4lxR~qL zE5>6WY_OA60 z!4reEKb@d^q!tZ3+a7IITM@Z{*{4@TVX^GFr> zL)xyb%YyvEJ~-?5T&kpC5vt|oa6ui1aKak9A`pRga_9q4tnE-DdRsD@K&-)WM69qY zS_RA}y?z_RTs1=hx}T-0XyX~>+Z!om<)CF?!i`+fP-6VJEC#~xttWF!d@MOHrYT*T z=&I+#Z7qf=D@_*Zee@xXt4Fdd)il|XJN{M8@P@|KRUNT!_}@Us_mJTBf1S;|P$(3Z z%BMLi&~*23sJv~!4RO^A3o$Pt^j^)DD#R&OP|IUwrwH|-( zy1a3eO!}m^b_xUpDh12tTbTtJp^rQBshkM}*Mu5KvyeOG;>ZZhomV$jv^{ zuk?uv2>H+h_oUWE(-2kM+*05Pf>NN)CZ)Sc&$}{Whx6pgu-6c$QPJTWAb8*VKG@r- ztIuyhknXY_pZ;bvEuBX~C*-+={I-7*(pzOmi zqh?o#Ws3|BXV$7S#xDV)!Y_3rjt8h81ND} ziV~)P*}_Ebms=%)^t6DrkAb;bsf>vU#yrE2P*Q}4lBY^CbT<0Z9+@j!7b`n6&RU(7 z0ev?u_~iU-Yv}j14kf0D@pvkJyw1pfskY{#Zb#m?IF5 zbhoISih&pAW4?Nk-c%dXdQCZf;GAg!4a&HLeW-sbNQ?||S35zgbxr%E)f14k;!8Pu7aqQUuc*_7Oub)#BdiPW}K#Dcgs=#3+mQ2Y>L#1uElc9kX~V1agoddhkMv(*0?DhMik2jeRbJ_>q>{JD!G|_c&eJ287_D zz>w}FCtC#aANx7v5-u~{D)=MkUv73W$tSeb<5SvV@|MS3Z;?)iLIvU~W&=~6lpK0L z9zrxJ&Z-$#-zkh5H@J{{ZRTlOzWBf6hJVQar^4`Gal_wD-D+s>eC)lyT8lL;j*BRrT&b73yUc}2m$PG`%Pk|oN}?(n1)eE%jw1v?2duOopyvfz(+*B9ZR03W z5I%i>Eu$c@5IjDNS2I#8S`-==hw|lcPQINg4D!stKj4O+T{&J&FUF&IBPyh;uEsvW zIdX#>~I(OkjygRB1ikX-g?<7)EP|*Oi zXSa>NWC*E7#hL^N^h7ECSOmZiXCM%@nu^P%W1@j}{FQ!R%QW*&Ob?r9&u^X2zvXH! z{;L=JCZ{xW(AU4I;%%kg1C!^!6dm5Bm}UeDU661=fvhhai#roE4InO-q_HZ|tGJF^ z)^l;RWTDqj15^+gcam^6nbW8qJAbRedhmozAmUiD(Po5S@aABw;#Zq-Ub$=V=ZfMV zC$jGpckO}?rp6Qy7tPg8aQNPK>Syr+=(hb{XOB)m@nt6`j_R4J^yh8p8`1;8(={j#n;GdmWV({OCn`XE_zZXtjm0k&v|D3W_ zDuZzV3PT8iu2$Gv5MZdG<&c7U8j|$+xfc(dq0o{Q&mi;=i6hhz!x7Xa=euIeS>|gM zh~!VUir#&_x)Sy_H*RjfLC&w>L4AD_hl6D#QegV5Pot_TSS zhAYx-O^K*0LeI`}7l(}Agac{9v}_i$_XZL&h3(&+tw8NxM#x$p^{a_(A;7n+lf|=( zu=g^tSyfv<#clN}F}Vh}{TQmFK=T~-(VSB>R#W44q<2ARr0v~Gx zn*7*hvHMsU1)o=}Un%lAf04&U_p`-d7@Kt%o3W_vg=AV@pTOA{E8{9)Dva(jm4g$z zZj783ti)*w&hjkyiNC8kvDwktPDb{z(|eDg&9Ypbqn**g1l>@Phy>7M+)Zl968^e33xCiC-y z^M^k#3^JE+hhp-t{YiU#7l%x4MGH}%BlbGbDm$#4i@B6(89^TS{yLI%o0|;$NE7FX zDq-+m1%wCN3mF9(Rqs+>8?3Na5uGtowv>TKS*ggUM-(S2_~?CzyozR7i`BI_vb?Lk zjaa7-xUC9U>5mxc`afIW;tDqF%E)j~%|;d^I?*vMrSbiHweZ*V<$twt{#h;XgTZ&1 z2WTqJj>V4V!#v3N*oWkrb62)shXoAQ#Qo;P&Xr3LkW0ZDW1w7JLWU-FwLSYhr3E>phdhrE3?#RA`!{ zWisHv!H5uVBdd!++m3;mOh=9zt{;hMidgqixrFHxLg_`U`YU{_9#>K4yU|~Xn860Cyd!roc?68G@=ij1)#xmyLvQzN;M z*T7j^zP3V@ioh3IAR^^4qH+;1wDn6%(!$c_)$0yGW0es0t1SQQo#`7r4y5Qh^h85T<|QJ z!d&ixC_`S&5=1#)N~KpiZwbnY4-AV#Az)?L#wPm~9%xn_28^T{e>INik?((0jVTVD zgib8H%8`dp*)12jBG(~{J5=l9kPjmo&ui+Y*)j-A}6yF(2*&qW|72xhMgn8L6di)EC@&o9QBSw>WE z1AXZ1>Ahv&571%XmM4%wyScFsl0bWQZfx`3`e6Zgy=*VAP<>-Rorcs%2ErUq%^7mJ8?4F@EYt4C%+WS{ z8doU(UjQKi-u}MqqiSWboMi;?t9Wy08v-6jK21XaX~3&l*AU3G0BG4iV&mS;ySMM( zz=I1PPQ1AB_^Z3TR(Z~^_|BbaPWyYUvL4+poiZwDY>BC76TRs;e-pGFd>B% zj&R|H8H%tWh8cbcVTW3X7~u@d5p`k`CQ6hN0WcD9pb{d&@m!2J@^#9FHsa_b0XiZ_ z*pE1_xEql^9yp?GNaE-obIcj3QIjw>2PKqJit%ETw_SPVX+t(iLyBIic-odf>PBXn qGe&}DjLofCrfe+4INfAyGS^|AFLWp;lq3ScW}qHoNvKvK0028J#nnsz literal 0 HcmV?d00001