From 58c59bbad00b0f33bb2599469b2596ce29194a91 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Wed, 27 Jan 2016 15:33:28 +0200 Subject: [PATCH] Add a loader for the FTEX format from Independence War 2: Edge of Chaos --- PIL/FtexImagePlugin.py | 97 +++++++++++++++++++++++++++++ PIL/__init__.py | 4 +- Tests/images/ftex_dxt1.ftc | Bin 0 -> 11000 bytes Tests/images/ftex_dxt1.png | Bin 0 -> 3886 bytes Tests/images/ftex_uncompressed.ftu | Bin 0 -> 65599 bytes Tests/images/ftex_uncompressed.png | Bin 0 -> 3886 bytes 6 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 PIL/FtexImagePlugin.py create mode 100644 Tests/images/ftex_dxt1.ftc create mode 100644 Tests/images/ftex_dxt1.png create mode 100644 Tests/images/ftex_uncompressed.ftu create mode 100644 Tests/images/ftex_uncompressed.png diff --git a/PIL/FtexImagePlugin.py b/PIL/FtexImagePlugin.py new file mode 100644 index 000000000..24e4c0e7d --- /dev/null +++ b/PIL/FtexImagePlugin.py @@ -0,0 +1,97 @@ +""" +A Pillow loader for .ftc and .ftu files (FTEX) +Jerome Leclanche + +The contents of this file are hereby released in the public domain (CC0) +Full text of the CC0 license: + https://creativecommons.org/publicdomain/zero/1.0/ + +Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001 + +The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a +packed custom format called FTEX. This file format uses file extensions FTC and FTU. +* FTC files are compressed textures (using standard texture compression). +* FTU files are not compressed. +Texture File Format +The FTC and FTU texture files both use the same format, called. This +has the following structure: +{header} +{format_directory} +{data} +Where: +{header} = { u32:magic, u32:version, u32:width, u32:height, u32:mipmap_count, u32:format_count } + +* The "magic" number is "FTEX". +* "width" and "height" are the dimensions of the texture. +* "mipmap_count" is the number of mipmaps in the texture. +* "format_count" is the number of texture formats (different versions of the same texture) in this file. + +{format_directory} = format_count * { u32:format, u32:where } + +The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB uncompressed textures. +The texture data for a format starts at the position "where" in the file. + +Each set of texture data in the file has the following structure: +{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } } +* "mipmap_size" is the number of bytes in that mip level. For compressed textures this is the +size of the texture data compressed with DXT1. For 24 bit uncompressed textures, this is 3 * width * height. +Following this are the image bytes for that mipmap level. + +Note: All data is stored in little-Endian (Intel) byte order. +""" + +import struct +from io import BytesIO +from PIL import Image, ImageFile +from PIL.DdsImagePlugin import _dxt1 + + +MAGIC = b"FTEX" +FORMAT_DXT1 = 0 +FORMAT_UNCOMPRESSED = 1 + + +class FtexImageFile(ImageFile.ImageFile): + format = "FTEX" + format_description = "Texture File Format (IW2:EOC)" + + def _open(self): + magic = struct.unpack("cg=Rwz9#(K6W*t@`3$>&Wg%lKeh&X8!Jfz~Gl^jyZA#Arb zB7v4op~VffSPvF+SlI}Hxd@|y(B=@(Ll`{-+(Qow^)QF9Y$1fN|Nr+nEm>mLjjmZY z1MAz_o$<}@d+&SS``+762QOY}&@^pw=hXzsa~|RU{_xGwKf*vTO*1eRl+yp`H04XJ zt(9_ByVy>-sNL?O%qUsN969iL$>->b$M!Y#f~T!+;M;})D1_B6(%-E4nfKh9q+`aT<<4^n>c%~-p+dRBWd z1D@54W@fjC$y2nijZ+S-xe3bUjY}!Y7h4ZzC|mN+XDQF;PcBf_%CTct<1ziy$@;g4 zdzu_O>T?s3RtKEVXZ!kjzG-)iJF8J}1*grI8EwRUDtU6^uEp^X;-f#!B_HJS2HYL8 zC2yXk9C1w_aVo3d!TbfM|HI$T^itm3 zxIY12|4Q-X0?s9`C3)wH0X$r}=il%5)#9I*>rXoJCE}K2#dO%YwUCYaH|_0Va*w#z zQYS5TC8A4*x!%4^&|uSIX^Ozh8c7Z^w#o zy(0JX*`|-l{Q_}J`S&LLo7{87<{IMOFaPg5^V{=t&)(#=Fb+Z#Z3u9$6q$2-`f5#9 zYX6;eJH)tFunalBVQ-y&uDCaC8xy&dc#De^19V1Da-K4gDnY z7IJg_R(7wAbmse(qRtL;AZB(|cQhXT1e>kGP4pb_2ZJe`6e?+=s&MjivV= z_487G)|tR-px)A(1wZQ!90%^t>S4Z@+RNi-ymQi_cn9|9pBP4S(e4JuVrsMHQ5b+N74zRu!!5bl-Z3ODj@4a#G_*qA`0L*$Su-jQzgam)mZD?q$ zn|SYWdoqjxFr83{^>gyFj!emVDn!7$u3qI7=6yOH3bAiMZr0B!Sx3gaZ?0EvkBjvS z%=dVjl%-0YVq{+2xO8@zFt$nFZ=+$p6cr-H~WH$A9cI;fL}u&E{vCcd2sM5)&e$N zxh5Qhe1zDVK~Me+atqTe_&Y`^m$dZ+Bzo_`Y4mc@i>KZ}9MJk!M~~_i$61qv9{uYX z{{JOk_u4V^z%yFX%?z*?F2s7mTxG8Xz2GI*s?Y=eThng6rGCZbV6TAO88!Y9H{b2Z z9#&_n5Mym%Xw8x}@0DG8SxNT-j=kzvwJ0-me*%1tg+2MPR#jsUPFqsnv#rW{W_!~w zYayl?x}K7?Pwa)!yOTW}evUIrKn8hL zjq{-NdV8tY>z957#k8-gG2XQTRjD<-s$b0`Cq#d?P{bD^I9=UZ&#U@nuN28x67z_) z7wo;Zs#|MO`n~w`6Z{M%FXX;mDI;(gwGO49Iq2n$Q>U&VUUBh}{(UHWE~TITmX_oG z!vNG{&x5sZU-R1Y?{ZSJM?xq4cjOrh?1?xh@2d^dDZXtMqdnWDdsl z2st;#Kj~s2G^;OLat|Lq{7%CfbpZ-vpI;ti4B*;dD%J1i%($85*OoqqC&cg0w4A^i zF8taB-1oXQeAAZK!>6Z$YE3-1U_gZsIidQZD@Cq>rT3rD<32RngyVC!Ng!r75&3$$M4U7vyHuRobz|;j_Ip6obSNDHg?BPBHyxzEY-
    ze%isyTWLYg;l0VEJ2^S|)g&$sqk{!DhL5PvIY!tsU;+N0F(+t3 zejbk;hyRv7v-kac5L-&@sdD^{7M{_QD@8s7_47`sJ}yL9Q@K-EYnHJvC+MqmPhMl; Iv)$hR3n1ZpUjP6A literal 0 HcmV?d00001 diff --git a/Tests/images/ftex_dxt1.png b/Tests/images/ftex_dxt1.png new file mode 100644 index 0000000000000000000000000000000000000000..a5521dc987466fb9c85650061fb5ab5bc224e92c GIT binary patch literal 3886 zcmV+}57F?6P)k-fCmLv zG1?CAKM8mlr2wUCGZGRpF)*Wd}5@AASSsyOv&%bkZ|DOgyx#$l-*|sAPz*f#6ylvaI0V1cHGc&U= zvxu+|Qyz#g3kwV7r_9d=#bJ27xIQ9cMr1f5@s=udWI<3M)#qCifB?(YMVMKIMKp~s zBBCnmy&fd|G!%$w4VDW5myo=Sj|CSCmq45YV8!9@nTD29(rO|iD$J~EDxxByYHFe^ zqN*yY!q+jt!koh-!vY}^GZ7L}hG<%e5D~&5KtEA%Ob9Xv{P=Do01=2q4j)%lQx#Ej zRZ%rFGZ9HTR7F`xSXh~fIVFgQScE7|&+C8=8V>m!; z4+2Wrh*D2PMBGi(T+Q6wP0hkwO;y#*R8w|Dj}fvoN?22>M1_ftSWLLgPfRQSPx&Xp zCHc5Ape+|Bau{seR@R>{Oh74xnbYv5wybVxdN+4B%eTw;7wNH#1uFSRB)wlRQY0cw zN8U*(_Xw1;;er5VEl89Cxh}MATPC0{e9w2b%R8WyA}peketosNdqlXIN4S}~yQU0X zU}`xY7H#>D9Ep#J@S*ug$cNx(xWxj1<*%n!UyOhv!YYz}?O|>a5gs0k0Cx|&B28`? z32gCUs;pv?NvMdhWa6LEc4aw;M?`(wH@?YvKLVIJEof#Q?jAM5Bf@==P$L!J%-mc( zjc)F4zC8aj092GjB&WlYt3a5>zwOul3Iq_7imGY2n@5C4)EYlwX^^GyuW#~QjsjiD zr|>1+AtIubmOJ5d699;+8Do8UdPJ?Y)~K~D!`BG+mG_vZAT1V-5OFxYh%n_U4-o*o z!zp}L0!rCL)KtygBWgsgQERQyYFjKsgu5Nezal|IxJAm&l6_Sc6(J@eekZ$MPvkxS zvs;0fbMAC;`IhEyEsskfs+ljLueJByTI;RV-fOL`rlUvHR%>nf8ZDK_BQ0+t zBFum{P9@)hPe1?>nQ25_g426j1oYn1^IL@1nxmoC)}q!HHENnZ6`z^qodfJ^@aP1n zs=2q6lQenjWAxTj^)vdmT5Id6Nv%eB#$oqd_!Q;`G=1W62>^f@(cd%~a$Oz5Y2KlmI5OBcArwNAGR)(Z`|uSM=U$YrVGGdgevB%%CFp zNLv3pJR$)ZZ(EJpTW@2GKE@b*5%Jvgt&f)0?=>Y~%~XY%Kl&tIi^n3MlwztL(OPZ2 zkDkWweasY|p4NVb*4D(=&DB*!Kl&v84i7{C0+@T$Xwll}bBr;@m~)IiT1MTTkzhrG z(QDLJTdp;WDn7Q{zlcX6fQZ~ZgKF=6j5%hXV~#O1^d8T%k3M@(;i(Z`!&I5&qtDSLVw2ptl2NR%X5#DR-hvv+EpJSx+x0YVt$G#s~XpLwQZf2j_ z@h|vD0?e#NwBE-UV~#oJKKD7tn6tMw##}LYO@$tOr}X#HXP-04 zNbC2W%WdZV857Mv;{ymVvs!EKW2WpJ7-OZMbIg6;$3DiG<5=EN)h~HN)q4|Q?zPs@ zN1tQg=e|Gf`<(M>%rVFE{Of4wy|(6VpKgBpdb}$E=H6<}h`R6lvi`pB8EWU;_v2~K zoN9#!@k3m?x)a{-+%o6sy)ElMJw5Gn-=EU->Gp^3&pvvs)y*E{!r}e!PN%185w)+? zoqeB6c{0rI`@Ziv681SqYope1_fN5&{6@S70U{dVy|>YN#@zX|?_-~{r}gK)KRrFo zImaBM^{CY&;46mQ|Hs`45RqCvm)MrdWX8WQ;dzz?WyV?E?Mo&+FXFxg9BZ6yWWGP= z^9(<8(5I(;?oVm`R;#;ztD9%u=k3uZd+T#oTMFEiY>AYXtPQ#IPn{$HHfi+Uz?i&jt4y0cM)pcygU>pZlEGYg}{A zr+wyHPwIYrKi^N>Wdw*yM6{aMw#I&}?;ivlAz;kjTCMH7>Hc%vo&aVFk6K&r%l&gf zx%ECq&zWu}LcO$#&wl{#*I(j zOzTzp$Mw?Ik@%$kwBBlK{#CAOy#=>Ce^HHywOR1+_C7|-omFETyL@`9EzH0CI?rox zGXjVx+#_mgPa3+fr;*dHkvUJR5np_b^Yyq50Z@vWMMQ6P?VugK_g-7?tv}0vTB|KQ zL_Ym)hFjq_1c<78)Y|HD`rca-vF2ep1#7KGxSHMMKm8iG0RegEP_5BgojqlK^gdc| zxtS{Y$h#NK{rjezFX62O02DRLd$x{EY`KBF9x-5yF=}tQ#DAtezu>Lr5V3G?nfSN1 zwlJ?-CVFqjhVrQHXWH`u-b4Tqx!bx^x3!iF`gs#Y?`vmmZ@Gc_EA6JbA^x3!QcBi= zskN?P+t*!N&!ni=+PubT{omo=2@sLo;j{Fn_15}v$MlK^>)u_p?{IbZ-|&wF0HUfz zL|wOzt{c^_TGV=LHEMNpmM^uL@V59z0#wA^JUq)Y^t$%1t>D{gZ7mB0$SFR5#J>cO|6(0cHS>t5%k*v452*DbMeP)y*Ws@TU=~-4 zyylYS0rGD3h<+)b5!KvP&jp|V#@`W8wxT9k`lV%^o>ucF(Ygu+w5T^CJS`r}>MQ;pgm}1#e)P>}p9X(H z01Ib{wj<)!RTy%Wu8ORUWM=2Zme=F81e8)#)J>mNCake)vSr<{77=D&`DTiH;I#x~ z9cDX9Ltct;E+$fX+|~44b@(g1ih#0}toWYwzYc3(>vSF^dQ8u2jem((5kNvBnzP@< zK!n*rfk%xncd>Kc``37ty_WUeE&RwlJ?bi<=i%mVYVvui^gIs!n*btGX3Z+sSqnAX zEtVD?HQ`ksw=UZwj&2m01XXu4&lcq7PMz_H*G+1Q3x5XUhr8rZ;N7TCliznEB@_QTTxPBLPezNBVO#JkU!JFjG|# zJ$L)O1-~T#0YzlB*gCpwnXcAPtNDTH>HcrT?+G9j7D+|#fS8%G zou8)ktp1{@KdPe2!uUabSH2QnCV+`@1o+WsD4UQTZT~ErTfXym{gQZrfLwlHRTDiL zjB2)l%``L{jMCStUieY?4FST!ET)SB5ml3GpG$q@o)n)8FBtLYvMv|%v#M|gpn~ms4swD2_QfsW@S;$*06Hvzpf&hjlLhfG0P{y zuXfBn(afwOCYte9M3gnVd(-LuH^u*rfD8cHJvw`Nvxusgt{&?|=f;y8;wJ(CVCA5E zVIg@)I+&G-8R;b8X84H!L?l{m>qS&qi0MKCXWLPvbKB<)@hbt$B)sA-KW}F*k^$g! z|J&kc0#*bjUfwPj>!&6$5}pzGrnnMNpb!EPaXyj*=4@;)tH(Niy$b#xi>oye5RsUP zSHMjI@*%`Rbh`iTaU}o%(!>`L!pulnk_I9|Jl+4scs>FE5s8S2Q-+v`kWzh!i13^N z+#b(HKqjGhL;zx1$BPRfJTE}r9Lu1klyraMWp6}WOdug65fMN+Bk--U3<>~%NB}7{ zD-&7+f(e<2oaGm2|!#Arc8~1;0yrwz#niDz(K|t0Pcb` zE0kx31^~=?S?Up#Qc5}J!1n=c+XhgeEdS4|l7I+M3IY}cBmgXMO3+;Z+qRWb;F<9g zix4bOG66bAz+JF{a47{B3z=}1b(8@a%9#S)39xNjNe*BcI-MT@fYw;p3Y-dbCma%# zarSs@$8Sn086mclf%{+)kl2>J*CT(*mJFN(+y&1FfD5hzq7*!>e^(r9LEBdHFXI7=x5HHIZKu-S+i!%mMvS3?AdeV$dNN=&RiUZ;*mSIA%6^i zsVh(Oi_z25H!v^&X8~jo&Xo&H^W@EwKYxBBBjW-E3KT3Amp zC{a>!loAgUlQf@l#{NP*Jv{^)sYal48Rp89M}Qif3l}O}v}jS_Emg|I%*?DznKI_) z<`xzf6)IGyRH;(s%9SlGEv>Drs#;rDt6H^MwQAL?Rj*N_I!DHaBTZ(nudkmaOXlp^ zvm53z%$p~Vk&#iMLWKmhOO`Y>GX-i3i*gk!RHO`A5ex3_QJqIruJEm}&BR+1x4t{~%m35q34<}BH>Wk<;K=FMwdz_>_} zB8WGDmo+ypU%o<>%2ldXwXRvCX5G4VZES4p8a8a&q)GGU&4Jdz!Le=Iw(Z-u@7S@U zv$IQ=&Rx28>Ds+}w;nxu^z8XR4p;uT{=w1fk6sxM8cohv-#|ZG)~qa)C>CR5<06F% zgE{)7T)A?n5o>Gf8Z~Ovt5>&yZ39Fav>hBAfVxA6_Rh}EUAuPe*|WzVf4KJU-Me4E zegg&!96WgN(4j-!+}zxU5BKo!@bvT?F;X~2jT$w2G=Ki&KTTduh8dehG+Wke*e3b% zCzQx?$xUoP!Ae3 z2%O#B-91Nmg6)_wW4ye)#(R(V@$s27Y0{L*lc!CcI&J#2=`&``(xW6*f(p|y0wFolaq^!OZV>GQ5pb_ zU?bI_J$m%$apT5Km@omHr%s(ZW5$fxvuDqnH*dj$1&bCgT(Wq{Qa``t%a^ZQxzc~t zs(^riHEUL{S+iy>$2yMn>({N*z-4Lskk~L-jF^ik_JV~97As!NtaRz}<;z=HTGp&t z)7HkeNt4E?apc@tf;mFof4~4F+{4pD0Nl&V$7jNnDN|<5n2r<+P%mEO=jXR_#mayH z|Fvt^tY5!=)26^JTeoc6zAY#?Xy?wIyLa#2yLa!teftjV-+$o1frG*ka)|#z4rx1B zYMmc$QMT;abLY;DZpVN@$&|OSu&iQPs}``^G;P`xea^bcddULWuYbRxLxv3Z7>=Sw zpG=%M5fy@X&!4|w$&$rLb%4MB+I4F;1_o~3x-~dBc-QV-APlmH4;?ysHO_3DiqH*VgHxPANf-Me?9qN4u3 zckjW22hk56#7K^Z4<9~y`0(+gNBq%V$kZ`2J-tk*8hn0?b__@}GgFK5<+0MS&l@#r zgdyJEseP9&U0hvV2Mid1N@w--^7fiMY0}IYGv?2mj{-+^uUWfh)22;9+qduDz31S8 z1EEJkkB5h!IddA3Mv|{zyLRi=%{zDQ+>?O(Fec{llP7U;u}_~qeg6E#%a<=-zkVJ6 zI{wX@H*ep*<9Pp`Kkwgvc>h6isN*WNaSW6^UaF{SiWe(hrc7xJNIXt#^=8eQIksto z#rVITJ^T0XKWvx)ySJCux(jHt=zD2!?tbPcJ11Q#tuDl9eOVUI;k9ee)J@KYaN3`O~K_U%q_(`t95I?>{8R zKmR6uXd`y2%?ac&CPALC0M@&?d0Ffkd{SIdR@L@SPTjh7>(jgU;2}dsczU8_FkP?; zSw{W*mIbU{9T*rG6cn^)&z{4F4x_{W`b&bjAmouqcuaKklP6D}J$w4<{*Ur#%ijiVLon6zLC+J;O^|tzx3VHKK;#9J236zwKi7eS26=jTjvYG| zfuB8V_QFMr(CzEjt>3d13)~&z){u`U**|TSeI=bca=g$&3R}tgnKCV45 zreY?<4X`SS025s&4vC(xUAuPU#*G~v9XfaJjNde5@Zgc7MoyS8e){z3X!vEzmg9Dc z0*~Ez`qU|G@tZeqqSzlj6kv~!f6elVdST5>4mI`tE)`d;4IjaoGr_?@2M-TyG%oJd zE2RD1m(OVjyYvQX1DMLrfy5r6cvL+}B;p3dYU~;`z_#hwsS|!H+Gf;9WPXAK`GqT2 ztqR;2h^oQaKXLr{g^L#=B5nu*|LD<+7ti0mef#Otr*GfCNdZaY7s_6H`kqfJgv1l! zGO~Fjn5?XG$gu*s%+z9fA4j(>s>LlTo$jC?n0H~Vy_;`#N%ysp`nCj7#LLj|P#-$?tmoHy_ zQrM(Is#dMqpg{xDcP`G(y?XZ^I&9dOF@H{;JbB){xyzO>U%z3)j^N-!heA%BJVlr* zGV*qGObpQg+$B`akMHWYAyW-LA51Su75`pBXNHEkh?^ny1kJE52~-l0C!0s&gFu;& z&qOjBOO`ApuuhbQ2;}h-CoWyQgv>|BL`#r=`}y;yG&5Ft+6fp^zGw5+?}v6KJ3DU3;q55iMQju5;3@N{(QvEo5ULheArVbPhPolm6+t?$B(gXK79O$-zs~VTKhWfu}$$C1)Y-@ol~rw zMY-zLYY?c1W6+~}_kjZk;Zh;?Bv@ChSV4|&&)&Vq!on_`zi=z^7MYC~&tDK<|N8Z- z)}k#xu@uor%M-A3ph}V}DOIXu#Yz?H)Tv|N%)VoX4lrfhhPe?do-uvKqD6}&Vjpzq z&>>QL*RNm4nTm^xdlUcW^Ow)^7pUd$q$Rw`e-~^7!f2THW~OFUEGI`2^8X)>-^1g2P|Ge#wBqCrmlb5KiWc zvSiHy6{Kjfq9}YAeFQL_oZ1t~88&Q~sB_4}61UvBbJww>$H;Lb_9XsZy?puU(?{*M zaFUmeWMD~~Uy-H7u_aMhp<;zvwQHmBVV6PSBc3pR!g#m`%a$+Ow0SeOJzf>8jVM9e z$CAwcl99z;dQ;K{$i+#+bbd-n#Ngvd?A>bBL~?N@>ck^q)ytj zY16YuPY9O6YCCg=-%>w}OcXvrd$Lr}Y@R)PPPjgUTA0b-1$lfl{SJ~3_-1CMq0__3 z!MCL(0Zxdwx7V!Mvxw(x-n9APfrF>doJQf})neWg$x#d0BY7y&<+qwfJJI-}zG_RNN^wY)I#Z*7%MLDB zJI2OlW~LJG?bf!KWg*hs z>ulMGsu?FNxOR5!+O~83!`0n=I0SFNhwKV7|?#9JEh31!00?10CEq}7+ zFqe)@2{;rf?0hO68i)(7tE;;kYG=F%eo%1mv17;JCq&(gdiL}wrp^y_P%GsxRtw*e zqNJf0gctJX&qvKSsax!P=nA5>BdVPMe%_)v>eLE%o_g=@*DG>1R& zd|FHtD_WEq{CaikQR9P$J8bAsvWG2A^dT2 zNv06bFK1yvRI^3P7Q}W4FW}+MnkCrz+qQ2futN@o@+gVg`5nRMzGw*l967R61VJ*T zM$MYdn%Tn`fgDI-GLhHn;yeGCQY13 zJfDyFg%qC!HR4aFGXl%h0Z|Y^vmxr52M-#A!83c#>{Xch!NHJ5i02bhrn>5v z53Hq#ym*4T@W<7XsC^SFD{BZ09XfVo&HeMw(GUeOcqnAX+Prk>GRc&euU=AMrQq~_ z`P0;bzrLOxP5p`$D~8&KH$_T?)=sn=64uaJ5>sY$>SyVZ(eT_$=^UK#LpJT&l<;Z{LO`_~HHgbT0T&9GWyaQ+50) z3qtcy9D%jptZ6e?%7X@?_Jtq=fo1w996x?M;zk7Q1rq2wgD25lRS|!EeLdQU(%O<9 zi_`>R?L!fVrHrd3sQvBRjvhTmpPL8K(ZZUmHC0!MKuE_QRm7jhu2cq5lLc3~b*oks zAr5zUhawJ3nX1o&2Sv3X1vC5?Rcg?{wtai2zJ2>p7KFjMa^(tQ%XEc;oDWeJSL-)P zCME@N3h$*Ff2vgQwkU$8G7DE%&^*J3qj@L`+7uW_aRkX+DB`bPz5M#+S0k;ZfT=RS zP96Ri17kDp>C;aiKPoew-|pPh;SYz6 z#upalEhzsd$kw}8Z|ZQTO`C?zvpr~gSlF>^*RIoIP8VTIQJzwPzqstumA87eYR&DN zck9+w@V0z>h;UL0imMBUhkPz^2A$^71painr=2}1^#*ngXnY~WdB%*PP=b8=?p?d+ zw?KHA#0oW8DSo@|1ZCwvOO~w0M#cm=Yu2jOs#QyP%XBEEbKue?OSW#=LXhq1l`ApP zF;E9J(XH#(yI)G-ua^mL*RVwK5>!kyYS@UxB3WJf@zVH$#-zk~NNiCAO<$c~n>4FN zF;EJB$W#e7XPw%u9UP!86Xc}j%kpK*B+G6%<^PbD>4EvLF3P6ZO;Q|x94>N;kg3>8 zg9txO1@U%aQqgZ`!-fqo_RpWcfWgnQtIMq@hP`l>;`qaAU_TigF8VssT96)Vw6>!l zeZ|U^z-YzsYTE0*p zrwcVGfxg1>FV*~_@YC9nATyyJXoR%+BC8&GD-tHvM_q19DfAV?AG0SnW)H$|Y1yQ4 z6EZA=hYUveak${~P_csW)5wty7`pU|lJF~}f9MZD=?Y{*SS4_nMa@5bI&n@x_-V6t zJ2LX=vuAXgQuf<*=PR=QvSl8WD)?-T8aJZlonZD%81K8#_x}n18(nTOHPVzK{zQ9> z^5>@y6)oH;QOD*VIdX(V_*W)q{>Vs*R%mdSny>%uKa(Q<0;R1;5u!biIceceoS8yh z(dH-AmZ16H#OpMF>O?7d{MlTR0v0liP}7VOHz&Jq3l_Zv5M9O+$HJ?zSH@xq1s_aCskiMBO0O)VJd@VR8< zU&!$0&Q0kYjRM#+g*d;PyBirs`sY(99UdMQ5pe?!xh~GE%56&qe~49-QIs_=3%7&~ zYoPH{L&44wBDdk}xpO30Nbl-cJsQHFtqs_ujJi=eKsr0Shye$-?Vx~#I6q8gLY>s= zs`Bl+&;CsQ4fGAzt;|eVd26(6(Gq$Cp=LIKqjYZPu3hXLap%tM=P#b?n9M4Q(9iHE z;6ni&8FricHcstXdHciU7gpZ&>q8EP(A_#ZI!54g{!X>hDngy)$A5-D)uVL1Ce#nH zjYbNCILaNl8N#alMYnleYaHv(YCUw*F%3#mnSusZf=Db&7@jp&6>G-bpQ+pcCjVNEBL$_v){6|-2!?11S;~kPe~Cx2LjIa_RT2c_3-e3)lbZ4*Y4ePAdk8mMc4gr z-!jJ5HfqC2lLN`Fzqq|*yKsAJ)~v~f(SP*n_2=k6XV0Dkw^qouT)fD3X2g7S)J`?t z5Lx^s0sjK#<;qdjLM88j{sSl}!t$h^OAI*EG=?s1YW#NHP_p=={3WS&A>i-g(s}5x zp%W)gWP5aCZYNGi+}`K9fU}xKMizf~KL06ydx`S*6qNs}RrKMffdkpD*RNjxl1<>% zjHxoiN%2px{7p@3)~G=xJ7EVQ+cvobO= zH!oYSUR~P4WBGe|ja#r_e&EK9p`m0OuM5gwSJIU-qRO2i#h7(s0dC8c}!?LB|)JblCB-@N%p7jsquT7^y( z@rP=aCr=*w^9!DrLu<+_SbS-qw07OPkb?&=UA{ycjrZ?#2`_~pRq7-Wev-kQo83!Jl`l1zBgS{+D6zuB;2X0rm*xa#?NoFCyP=5s(7-9KZWfkrApNha*i$87Hae;A@Z;B0sb=5sc4Gn~okia{a~)`qt{o>r}+~XCIf~ zpV^3w?_}0-x1UQbAB&eI3N8W`t3Vi z+E+8^OZc}!<^|4M)v5#^*e*&S+|GpUzxu%b{TDA@64LIv$iJrG7x=f}Ph&B5uV~&P z0e?!pD6$r450@@IjEQ;wP8WR8)C~ZC!(4_m8K_pRTFaI#A)JpIHFB0T_#kPD&gpvF2aS(UQsQ>FG1)>^ZVigjTpvp^~LaF;6_jyx#yoIGVR z**NMDmMvT6@4pI85-oSPY}vx5lGJ3<5Cj7vjpq;G+;sS|v}UuktYT|xL%j;I8;Cp1 znWoq?XU-ym4}Fc=i5)w3V9K-AJyZq)3fT1Y?qL^LN=Z4`tb#{7&E}7y6FT}8F2bH+ z?A(Kd(90OvKq`p(`7T(%oWYi2OLj{=ckV1(C=r3B@PQEWgAylSN0s5=;K4Oie!0EP%!yI_zKr=TqkPAgqiJ! z_aB%fJ*r5biy8EUfB#!lI7id?>l^5UxiIre6&u*uk>lyxw=c{5%oz!r!P7q)!GLD; z=+Q%h-O#MRe*G$$Z_2e!nw~$@9JGqq3xiFz$PS`0pav2kfE_|z3KBw$0Cp^02x)#T z!uub(DgR$X^GCNBNVcS9t*KMHHkC;!pE6<%rIobYw`;ik`IEh7K&GHTVTjd8+SKV`~Pv?5E-<;#~T+eT??qGyL%MO{&8+>0NVx) zSVjp!(&}sTmMtfa9~YSSuU?4?Tzb>h_(E0uF&PaEu+OR7z&J!^yuH2s{QUOp*+aqV zy-&D&#$TN9L&D*tW%LZn1=FHx*c?${G=$IHy%Z%(zOcnlm z;^eDVwMNyz7UgNBNR=(dDH6ln(x|}8PM+7Pm_L@Jv2g*!2DX!%o7?>P^VvX!r!_7v zR($GuiP4 zJBK~RDMMnl(#~B*l9V4JAp-bLPQ)tLtXWInK*D8O_1H>pf&BbYJB5oBM)Ldh>pN%e zoPGQE13nQm=|{Bcg}nR|x+J;Krfpk7+(0Rn$roWvgg*#K5sO>)Y5MJmg{V5Fb8zr2V_eO64ET1NQ(9tLF$$*z>4; zQkmMh=E};j6o1M9DpaTd_h7>K30t;oWh-&qbJ;Vs^R+mCmaRN_^0HSA#0i!yvV>&7 z(;#}UIwsQjLjq}FYYXcG;so4-xY$_j7alHOlz$9nVrwkB9-bZn{{GbM@P0;_KM@9E zgD{;S3&FS96u60HR|{ROB!x|s9}Ip%;xL`i{P1nC+0s2QnB;8b-(>#8^C8H=F~jDB z=|sjBzKxcf4blq{@h68^uU_4LtX#gnFr8v!pTJ9yo}ovz!|ux{$`g@b{`f2?e{vIEUfzVqp(cLHIJ+r}^M^$Y9T!F`q)MLh~@i2Dmn04#c!)PTL&2?pngXT(^4F7)(00FF2 zA9xWQL@_X`c&;ccrnU*~hJmOGM;7}Y{|YOiM9C7wM@Zs2Iy!dg+=Wh!{n;UCz<^Y9 z=!sb{EN(wzy12N&hrooQ={V_OV(y0yg@lKN(Swjgj*kvPF)vICha`23`q71 zjm#q>p^3!D$Kzii=omWeVhZ4jkI$S5(qS;srJG(6fq{X9*9o<-Y$VA*B?_SU_}o5X z$Ii~qjEpw9di5%P?Du5Z2?~zSwDIs8H*O3^e4S*S;|WnDHmAt=hzBi{m^R6hBS%7E zKPw(PNO64bex=Hl`}FBUTLJ)4c6m#FdIUcQk{lmt!K#3@3@prC6=za@e7#JW5JjYg8w%#B zp1FvX{P;XX$T^~jw5aaMBpRQFVLYLpo*w9>59+ARO*Fodkuk{dgmO>ig&`I1$c~T9 zpt#U27cX8?{0U9|OJ&DL<)Rf)T(4ipC;utMe=8jynM9vYnlzEys-n!ue!FyhRw4p$ z=yO!A>l9WY0mMh_wec8O@qFIZ2yU$RQgUA&&5FC{N^xg1Q3l ziDBi>r+BeNixvnjdunrS(q{u46P+*WFMM256Y?l0Iv-8VGDE2ocD7W`WM9ah&&ZsN zW&u{&b7Wu3o(~z2k&`)@4@%GH0+}-q%Z?`hT0EbbfCd*Re?p>n#Ph)!=y;vzxAH%V z=W|nTMnuT}R{kB%$7i7Kikl+;Tlsf5AJzu9TY3lO&yjtH^ARLuL-x&NUUNQDhae?$ zj_j{E9}z|Bc%jI_-3&uIk!eInJOsns+M0|EJZ;_)WtT8J#)U}!!~_uEx3{;){gQsY z_&#UKzR7QSpE3Ek^i1&u@AF0S%Npk-fCmLv zG1?CAKM8mlr2wUCGZGRpF)*Wd}5@AASSsyOv&%bkZ|DOgyx#$l-*|sAPz*f#6ylvaI0V1cHGc&U= zvxu+|Qyz#g3kwV7r_9d=#bJ27xIQ9cMr1f5@s=udWI<3M)#qCifB?(YMVMKIMKp~s zBBCnmy&fd|G!%$w4VDW5myo=Sj|CSCmq45YV8!9@nTD29(rO|iD$J~EDxxByYHFe^ zqN*yY!q+jt!koh-!vY}^GZ7L}hG<%e5D~&5KtEA%Ob9Xv{P=Do01=2q4j)%lQx#Ej zRZ%rFGZ9HTR7F`xSXh~fIVFgQScE7|&+C8=8V>m!; z4+2Wrh*D2PMBGi(T+Q6wP0hkwO;y#*R8w|Dj}fvoN?22>M1_ftSWLLgPfRQSPx&Xp zCHc5Ape+|Bau{seR@R>{Oh74xnbYv5wybVxdN+4B%eTw;7wNH#1uFSRB)wlRQY0cw zN8U*(_Xw1;;er5VEl89Cxh}MATPC0{e9w2b%R8WyA}peketosNdqlXIN4S}~yQU0X zU}`xY7H#>D9Ep#J@S*ug$cNx(xWxj1<*%n!UyOhv!YYz}?O|>a5gs0k0Cx|&B28`? z32gCUs;pv?NvMdhWa6LEc4aw;M?`(wH@?YvKLVIJEof#Q?jAM5Bf@==P$L!J%-mc( zjc)F4zC8aj092GjB&WlYt3a5>zwOul3Iq_7imGY2n@5C4)EYlwX^^GyuW#~QjsjiD zr|>1+AtIubmOJ5d699;+8Do8UdPJ?Y)~K~D!`BG+mG_vZAT1V-5OFxYh%n_U4-o*o z!zp}L0!rCL)KtygBWgsgQERQyYFjKsgu5Nezal|IxJAm&l6_Sc6(J@eekZ$MPvkxS zvs;0fbMAC;`IhEyEsskfs+ljLueJByTI;RV-fOL`rlUvHR%>nf8ZDK_BQ0+t zBFum{P9@)hPe1?>nQ25_g426j1oYn1^IL@1nxmoC)}q!HHENnZ6`z^qodfJ^@aP1n zs=2q6lQenjWAxTj^)vdmT5Id6Nv%eB#$oqd_!Q;`G=1W62>^f@(cd%~a$Oz5Y2KlmI5OBcArwNAGR)(Z`|uSM=U$YrVGGdgevB%%CFp zNLv3pJR$)ZZ(EJpTW@2GKE@b*5%Jvgt&f)0?=>Y~%~XY%Kl&tIi^n3MlwztL(OPZ2 zkDkWweasY|p4NVb*4D(=&DB*!Kl&v84i7{C0+@T$Xwll}bBr;@m~)IiT1MTTkzhrG z(QDLJTdp;WDn7Q{zlcX6fQZ~ZgKF=6j5%hXV~#O1^d8T%k3M@(;i(Z`!&I5&qtDSLVw2ptl2NR%X5#DR-hvv+EpJSx+x0YVt$G#s~XpLwQZf2j_ z@h|vD0?e#NwBE-UV~#oJKKD7tn6tMw##}LYO@$tOr}X#HXP-04 zNbC2W%WdZV857Mv;{ymVvs!EKW2WpJ7-OZMbIg6;$3DiG<5=EN)h~HN)q4|Q?zPs@ zN1tQg=e|Gf`<(M>%rVFE{Of4wy|(6VpKgBpdb}$E=H6<}h`R6lvi`pB8EWU;_v2~K zoN9#!@k3m?x)a{-+%o6sy)ElMJw5Gn-=EU->Gp^3&pvvs)y*E{!r}e!PN%185w)+? zoqeB6c{0rI`@Ziv681SqYope1_fN5&{6@S70U{dVy|>YN#@zX|?_-~{r}gK)KRrFo zImaBM^{CY&;46mQ|Hs`45RqCvm)MrdWX8WQ;dzz?WyV?E?Mo&+FXFxg9BZ6yWWGP= z^9(<8(5I(;?oVm`R;#;ztD9%u=k3uZd+T#oTMFEiY>AYXtPQ#IPn{$HHfi+Uz?i&jt4y0cM)pcygU>pZlEGYg}{A zr+wyHPwIYrKi^N>Wdw*yM6{aMw#I&}?;ivlAz;kjTCMH7>Hc%vo&aVFk6K&r%l&gf zx%ECq&zWu}LcO$#&wl{#*I(j zOzTzp$Mw?Ik@%$kwBBlK{#CAOy#=>Ce^HHywOR1+_C7|-omFETyL@`9EzH0CI?rox zGXjVx+#_mgPa3+fr;*dHkvUJR5np_b^Yyq50Z@vWMMQ6P?VugK_g-7?tv}0vTB|KQ zL_Ym)hFjq_1c<78)Y|HD`rca-vF2ep1#7KGxSHMMKm8iG0RegEP_5BgojqlK^gdc| zxtS{Y$h#NK{rjezFX62O02DRLd$x{EY`KBF9x-5yF=}tQ#DAtezu>Lr5V3G?nfSN1 zwlJ?-CVFqjhVrQHXWH`u-b4Tqx!bx^x3!iF`gs#Y?`vmmZ@Gc_EA6JbA^x3!QcBi= zskN?P+t*!N&!ni=+PubT{omo=2@sLo;j{Fn_15}v$MlK^>)u_p?{IbZ-|&wF0HUfz zL|wOzt{c^_TGV=LHEMNpmM^uL@V59z0#wA^JUq)Y^t$%1t>D{gZ7mB0$SFR5#J>cO|6(0cHS>t5%k*v452*DbMeP)y*Ws@TU=~-4 zyylYS0rGD3h<+)b5!KvP&jp|V#@`W8wxT9k`lV%^o>ucF(Ygu+w5T^CJS`r}>MQ;pgm}1#e)P>}p9X(H z01Ib{wj<)!RTy%Wu8ORUWM=2Zme=F81e8)#)J>mNCake)vSr<{77=D&`DTiH;I#x~ z9cDX9Ltct;E+$fX+|~44b@(g1ih#0}toWYwzYc3(>vSF^dQ8u2jem((5kNvBnzP@< zK!n*rfk%xncd>Kc``37ty_WUeE&RwlJ?bi<=i%mVYVvui^gIs!n*btGX3Z+sSqnAX zEtVD?HQ`ksw=UZwj&2m01XXu4&lcq7PMz_H*G+1Q3x5XUhr8rZ;N7TCliznEB@_QTTxPBLPezNBVO#JkU!JFjG|# zJ$L)O1-~T#0YzlB*gCpwnXcAPtNDTH>HcrT?+G9j7D+|#fS8%G zou8)ktp1{@KdPe2!uUabSH2QnCV+`@1o+WsD4UQTZT~ErTfXym{gQZrfLwlHRTDiL zjB2)l%``L{jMCStUieY?4FST!ET)SB5ml3GpG$q@o)n)8FBtLYvMv|%v#M|gpn~ms4swD2_QfsW@S;$*06Hvzpf&hjlLhfG0P{y zuXfBn(afwOCYte9M3gnVd(-LuH^u*rfD8cHJvw`Nvxusgt{&?|=f;y8;wJ(CVCA5E zVIg@)I+&G-8R;b8X84H!L?l{m>qS&qi0MKCXWLPvbKB<)@hbt$B)sA-KW}F*k^$g! z|J&kc0#*bjUfwPj>!&6$5}pzGrnnMNpb!EPaXyj*=4@;)tH(Niy$b#xi>oye5RsUP zSHMjI@*%`Rbh`iTaU}o%(!>`L!pulnk_I9|Jl+4scs>FE5s8S2Q-+v`kWzh!i13^N z+#b(HKqjGhL;zx1$BPRfJTE}r9Lu1klyraMWp6}WOdug65fMN+Bk--U3<>~%NB}7{ zD-&7+f(e<2oaGm2|!#Arc8~1;0yrwz#niDz(K|t0Pcb` zE0kx31^~=?S?Up#Qc5}J!1n=c+XhgeEdS4|l7I+M3IY}cBmgXMO3+;Z+qRWb;F<9g zix4bOG66bAz+JF{a47{B3z=}1b(8@a%9#S)39xNjNe*BcI-MT@fYw;p3Y-dbCma%# zarSs@$8Sn086mclf%{+)kl2>J*CT(*mJFN(+y&1FfD5hzq7*!>e^(r9LEBdH