From 310b955652b6b24eed9a137ce3f5cc102d2258e6 Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Thu, 10 Sep 2026 13:31:10 -0400 Subject: [PATCH] Migrate ES3C35P to latest main firmware --- .../tests/test_integration.py | 28 + Buildscripts/generate-launcher-background.py | 87 + .../app/Launcher/assets/color-field.png | Bin 0 -> 304953 bytes Devices/es3c35p/CMakeLists.txt | 4 + Devices/es3c35p/device.properties | 27 + Devices/es3c35p/devicetree.yaml | 6 + Devices/es3c35p/es3c35p.dts | 141 ++ Devices/es3c35p/source/module.cpp | 9 + Drivers/st77922-module/CMakeLists.txt | 11 + .../bindings/sitronix,st77922-touch.yaml | 37 + .../bindings/sitronix,st77922.yaml | 47 + Drivers/st77922-module/devicetree.yaml | 3 + .../st77922-module/include/bindings/st77922.h | 7 + .../include/bindings/st77922_touch.h | 7 + .../st77922-module/include/drivers/st77922.h | 20 + .../include/drivers/st77922_touch.h | 18 + .../st77922-module/include/st77922_module.h | 6 + Drivers/st77922-module/source/module.cpp | 21 + Drivers/st77922-module/source/st77922.cpp | 270 +++ Drivers/st77922-module/source/st77922_init.c | 74 + Drivers/st77922-module/source/st77922_init.h | 15 + .../st77922-module/source/st77922_touch.cpp | 207 ++ Modules/app-module/source/scheduler.cpp | 6 +- Modules/lvgl-module/include/lvgl/fonts.h | 9 + Modules/lvgl-module/source/fonts.c | 26 +- Modules/lvgl-module/source/symbols.c | 8 + Tactility/Include/Tactility/lvgl/Lvgl.h | 7 + .../Tactility/settings/DisplaySettings.h | 9 + .../Include/Tactility/settings/McpSettings.h | 14 + Tactility/Private/Tactility/mcp/McpSystem.h | 77 + Tactility/Private/Tactility/mcp/minimp3.h | 1869 +++++++++++++++ .../service/displayidle/DisplayIdleService.h | 1 + .../service/webserver/WebServerService.h | 4 + Tactility/Source/Tactility.cpp | 4 + Tactility/Source/app/apphub/AppHubApp.cpp | 7 +- Tactility/Source/app/applist/AppList.cpp | 2 +- Tactility/Source/app/display/Display.cpp | 35 +- Tactility/Source/app/launcher/Launcher.cpp | 424 ++-- .../Source/app/mcpsettings/McpSettingsApp.cpp | 98 + Tactility/Source/app/settings/Settings.cpp | 2 +- Tactility/Source/lvgl/Lvgl.cpp | 52 + Tactility/Source/mcp/McpSystem.cpp | 2000 +++++++++++++++++ .../service/displayidle/DisplayIdle.cpp | 48 + .../service/displayidle/McpScreensaver.cpp | 100 + .../service/displayidle/McpScreensaver.h | 30 + .../Source/service/webserver/McpHandler.cpp | 1019 +++++++++ .../service/webserver/WebServerService.cpp | 32 +- Tactility/Source/settings/DisplaySettings.cpp | 43 + Tactility/Source/settings/McpSettings.cpp | 76 + Tactility/idf_component.yml | 5 +- partitions-16mb-with-sd.csv | 2 +- 51 files changed, 6873 insertions(+), 181 deletions(-) create mode 100755 Buildscripts/generate-launcher-background.py create mode 100644 Data/system/app/Launcher/assets/color-field.png create mode 100644 Devices/es3c35p/CMakeLists.txt create mode 100644 Devices/es3c35p/device.properties create mode 100644 Devices/es3c35p/devicetree.yaml create mode 100644 Devices/es3c35p/es3c35p.dts create mode 100644 Devices/es3c35p/source/module.cpp create mode 100644 Drivers/st77922-module/CMakeLists.txt create mode 100644 Drivers/st77922-module/bindings/sitronix,st77922-touch.yaml create mode 100644 Drivers/st77922-module/bindings/sitronix,st77922.yaml create mode 100644 Drivers/st77922-module/devicetree.yaml create mode 100644 Drivers/st77922-module/include/bindings/st77922.h create mode 100644 Drivers/st77922-module/include/bindings/st77922_touch.h create mode 100644 Drivers/st77922-module/include/drivers/st77922.h create mode 100644 Drivers/st77922-module/include/drivers/st77922_touch.h create mode 100644 Drivers/st77922-module/include/st77922_module.h create mode 100644 Drivers/st77922-module/source/module.cpp create mode 100644 Drivers/st77922-module/source/st77922.cpp create mode 100644 Drivers/st77922-module/source/st77922_init.c create mode 100644 Drivers/st77922-module/source/st77922_init.h create mode 100644 Drivers/st77922-module/source/st77922_touch.cpp create mode 100644 Tactility/Include/Tactility/settings/McpSettings.h create mode 100644 Tactility/Private/Tactility/mcp/McpSystem.h create mode 100644 Tactility/Private/Tactility/mcp/minimp3.h create mode 100644 Tactility/Source/app/mcpsettings/McpSettingsApp.cpp create mode 100644 Tactility/Source/lvgl/Lvgl.cpp create mode 100644 Tactility/Source/mcp/McpSystem.cpp create mode 100644 Tactility/Source/service/displayidle/McpScreensaver.cpp create mode 100644 Tactility/Source/service/displayidle/McpScreensaver.h create mode 100644 Tactility/Source/service/webserver/McpHandler.cpp create mode 100644 Tactility/Source/settings/McpSettings.cpp diff --git a/Buildscripts/DevicetreeCompiler/tests/test_integration.py b/Buildscripts/DevicetreeCompiler/tests/test_integration.py index a5186e70..6219d8ef 100644 --- a/Buildscripts/DevicetreeCompiler/tests/test_integration.py +++ b/Buildscripts/DevicetreeCompiler/tests/test_integration.py @@ -311,11 +311,39 @@ def test_compile_missing_config(): print("PASSED") return True + +def test_es3c35p_uses_current_runtime_contract(): + print("Running test_es3c35p_uses_current_runtime_contract...") + repository_root = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "..", "..")) + device_dir = os.path.join(repository_root, "Devices", "es3c35p") + with open(os.path.join(device_dir, "device.properties")) as f: + properties = f.read() + with open(os.path.join(device_dir, "es3c35p.dts")) as f: + devicetree = f.read() + + requirements = [ + ("apps.launcherAppId=tactility.launcher" in properties, "current launcher app id"), + ("hardware.tinyUsb=" not in properties, "no obsolete hardware.tinyUsb property"), + ('wifi0 {\n\t\tcompatible = "espressif,esp32-wifi-pinned";\n\t};' in devicetree, + "Wi-Fi enabled for web server and MCP"), + ('ble0 {\n\t\tcompatible = "espressif,esp32-ble";\n\t};' in devicetree, + "BLE node matches hardware.bluetooth=true"), + ] + missing = [description for condition, description in requirements if not condition] + if missing: + print("FAILED: " + ", ".join(missing)) + return False + + print("PASSED") + return True + + if __name__ == "__main__": tests = [ test_compile_success, test_compile_invalid_dts, test_compile_missing_config, + test_es3c35p_uses_current_runtime_contract, test_minmax_within_range_succeeds, test_minmax_below_minimum_fails, test_minmax_above_maximum_fails, diff --git a/Buildscripts/generate-launcher-background.py b/Buildscripts/generate-launcher-background.py new file mode 100755 index 00000000..d0c0d846 --- /dev/null +++ b/Buildscripts/generate-launcher-background.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +"""Convert an image to an uncompressed LVGL RGB565 launcher background.""" + +import argparse +import shutil +import struct +import subprocess +from pathlib import Path + + +LV_IMAGE_HEADER_MAGIC = 0x19 +LV_COLOR_FORMAT_RGB565 = 0x12 + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Create an uncompressed LVGL .bin image for " + "/sdcard/tactility/launcher/background.bin. Use a square image sized " + "to the display's longest edge to support both orientations without scaling " + "(for example, 320x320 for a 320x240 display)." + ) + ) + parser.add_argument("input", type=Path, help="Source image") + parser.add_argument("output", type=Path, help="Destination .bin file") + parser.add_argument("--width", type=int, required=True, help="Output width") + parser.add_argument("--height", type=int, required=True, help="Output height") + return parser.parse_args() + + +def main() -> None: + args = parse_args() + if args.width <= 0 or args.width > 65535 or args.height <= 0 or args.height > 65535: + raise SystemExit("width and height must be between 1 and 65535") + + magick = shutil.which("magick") + if magick is None: + raise SystemExit("ImageMagick is required (the 'magick' command was not found)") + + command = [ + magick, + str(args.input), + "-resize", + f"{args.width}x{args.height}^", + "-gravity", + "center", + "-extent", + f"{args.width}x{args.height}", + "-depth", + "8", + "rgb:-", + ] + rgb888 = subprocess.run(command, check=True, stdout=subprocess.PIPE).stdout + expected_size = args.width * args.height * 3 + if len(rgb888) != expected_size: + raise SystemExit(f"unexpected ImageMagick output: {len(rgb888)} bytes, expected {expected_size}") + + rgb565 = bytearray(args.width * args.height * 2) + for source_offset in range(0, len(rgb888), 3): + r, g, b = rgb888[source_offset : source_offset + 3] + pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3) + destination_offset = (source_offset // 3) * 2 + struct.pack_into("gS z@?P??*_~uIlgVZ$Gqb;$jZ#;Y$HpSVLO?*kR#cGDL_k2?{-23~^dINCg}MJfLSw0* zse*vu%ZPvw{1XA;=|5KR5dwk-Cj!EW83KZECIZ4o=bR3Wum5eJS$vb1L3sP0QP5qU z@*fA?T~S3A{REv7k&j{>!ngb%>yx{zp1YN~J5bot75E=P;O6A!XXg}P=N8iDVCR3?3FQ8FBm_knNp0^{qZi{^2g78Fa9OK(F1Xl(PD;`Dz8Rj*4oFt2SzoY>60u?o zFS8cXd-138$9&Sj>qc`=uf>;DziKczFcMb(AwZGWG1qTO%zjcxE>K2J=KAd_=&g5S z`XdI<=acyDhK`$^`@Q}2F&6Y{1mYi1r2%eaTzyc>(yMPEuxviKD2Z1aFL;G}0?tE^ zZxSB+38^-!>wVBxvR_=O7Z=R*23#M_xr3AXJ+VxM_ei8U;(lG+R}} z_@CgFo71<)x2K@zRwtRIf5^eSAri93DGe-*W*|8^8AAVRCK(-C4d)w+plGSL0y;?& zy|BkQ02GPm-S1pQLCj=HLTr8gVEwc_>^SH+T{JP6ml2BPtIzOm2+0;z4i#3>8aW=A z!6rc+B}3d8Eq7#%i-rI&BYH|WNePi-MR;=I4X8$ngPmDR6$DEl$|jZsnjq@WT!kiq zz>1pr0|*F7n2Bsz1hDs%xsbUvwv%bye#kf`ePIfsCN+^P*%*woQS6%pp0Kx0=>SF@ zqzK0qn3hdyRv|_D$oaYmp>BC=`x^?!$)=7EGa#NgtB3Pzqu5^1c(6=h6zisuK3^2% zz+i!|fCCe^=iBziTC6cT+a7Lup2=7c%I+Pv7Xqw8$&1H8hHzN`MaJ~Q20c$XM{Ro* zK-Pj?9sr_CVX#Syvd{rCVtP?J=#&;p;L^D77a-4+V^=_8c|$;>b3C6g1&oQ@4E;_M zKqcUhSg3@f+{m!&9!y*)L@J4)o5iF2`8}yS<2zuB!bBV5@+J+dBN8uq759-U_hmL` zX8oHLh(WO`zZG(8iCC!l3&t2v&%kA!dUPea&69Dur~cRggrVty&0!gmxi<)p~UMH+ZO3 zzB7Vi-+^w9J3W3{l=2ZJQ2j?cggegn)wyvT8W}xf=`yG2g`SwCPNBIC;SOek<4vA$ zA{DUEV}^B4;a!9)rM`AV!$xV2%1#ZQdNeSlMmBAfwRL0y|NV6^K0SSm9)+{A#DqB< zpd_K>xU9KYep*;8o;5E=CkF?_M}Zi7ZMhTL<%UhDL}6Z{so1iNNjv8 z5VlM$LTyl&)dn7!5FD$LVe0)7S<(iA7!i?U_kU-7)c~NK{1mZe+Gn;BmnbI9!~^V4 zY-vQPJp!?Njo0h>NZ%}FIhxGt>pNEMOeKc`fHh>mU~Jo-Ri zR*3)8W=e2P`<%Gi5tPo12CFQ=1eN8tXOrd$ssxYM<&hC;2_4V7(dcK!V|yU+=Y$iY z4*e}(CaQcX^aHujdUs@&WF>9{%wll6m?=>z8)RC15@v>`jiy8`x58KX4Jq@4<4Ars zwMU&PlRWlC1_p^_vueLPJSRYC%YNo|OMcqX_N|2~Xah=K6G>m*uFHG54k_E$so}48 zr~8f>V2;n;1YS#M#6rL`D`C-(bljLs6bLXSQ5;c+R#nbu;2A`>wV3ddaTBc%307Uy zFb+$V_m2*l@@7ey^k*l+^tJLmg)}SXJs2QNDiq)OI6uTfh2hx&{V=)|@We-z<8``Z zt;THTwKKPK!3Wo*C8$*^m=FCKHaFX6F;^tj7$T_CXw6%)-KMKHfei;jn~c$Islvc2T7 zARNHr_vVu-Qg}cp!QCGlCQ!0~pCmhEf4_7nW|5jR+z~IC7-vJqxpdi;bO79l4Gr!U z@0yqBoHc_BwN-S9CJ&>JW$&*pAV{(^aBfRe!&adc80pKjF&79){1QF;}y zQkh6jRy!U1&^R8*@!I>^D6X^QQC%WxdeugZAfSV%BkM0puP9(5jG{0kWlYP{{PJj= z1K7era3`Bk2Pn?`fGy~sp6Viu!^hvxEnSbtDo~Ej53&e+D3~2eY9|G!b^P#6EmO_W zDP{DU;}8*^@s_W1#`nLQmibXAHncEV-(ZhVKWR)N1zP0}8O6O&Ph(N;q|H<&k>-lNQuE%1fVYQ+}bDLZS)|2Z8%SY*<;w4R>jP{>(SNqhPuqplWX){C=aMt**^jZ=pj^udQbQvqzKB*ep z(;90z>gDqm1Et-yLh)VD6;88dwazM#^fOt^>`;jWXmAz#*KbetN0p^U(@CAD!ab@0 zFF9k5)~tvfs(7rP!(GMZxkRIP zW2|hZi2TC}>mQT5t@4DzAMMZglsWUgQjNo}-uQJaJ=~4su^67l3KbyNJp;VL%M|dz z=6NM~TpBpeuM%=v)U`P&os34m(J=mYDE=Y|reP^&fn11CCA`G`^W6+dnLIzN6?wN- z|DH)^|GIfSQI?Jyrc&@O&I;@!jk8FpnTFN$>MJF5SMgLthQm_Q??mTnitJIaq24$P zvD3r-iK|w{<|SYW3CuCj#1SqpWL;cwM4v8&;M{1+9S4z-T|PS+w*i46&S7#O##ow- z4I3q6ZF(8BKM6g?Z-4S%#mqKb$Q8eo(@5nab_-$MG|3e;CU2gncSw^pYMP%;EfC3i z(i#9Rmfl>21+(ftlJFq*!B37@Y!HT|_+wYnY(Vh-31t`0Xkz!B^&v}a&d7_TR6Pt` zMH8Q*&+7d}_P!gA#&`THBm)S#XHpi?=Tf@Se03zBd-S|Y2OHdxzEo}!VUJTF~Qh;kJaWz64jR+pW=R`CL7ZI63>hrfXB=zldosj_@v z6`GMCDkfb2oyCv|{K=jjdPwQhx?H}3O0jWMUw9$uA6yF>8 z9D1B1m{GZyiapk7bL>+prq1%BkTiXOa$A0awXIjNa91S{OVFe7PQ)`KE2@dXYpnnv z9|3=7vfHDnwOs_e#i)+5y0M;X0&6}@UVE7?{2MuPZIllEY;bNMNO``zuVf%24UnTC znHvQWqtM@=%k(4e5xqa0?)BevVdrsem;ae^0fSbouElAu=Z+*-aG!=W@5p-bhbKC+ zoN`4KpqIo^PFg#k?k>kg(=c2pykG(eb&v(3+2m9{rj5p7B4}vvI0=31`R4zF#flN+ zJt4)f?SQNz=?2gGtO6N3pE6D(yR#r$&+TRKoAI~^mh-%X&M5vmd&w3anI{XZk(0GU z;F55T4s49x1v|Z)Tl&Rhzfj)m9D0pl6D6r|vLF|oI^!?p5UsI{f$zM%*5KeRNIj)u zq_cJ;hj;-J+-G52v1TY+Vn|$(5JwvAR!vfLWvjCzrC+7;@$Ui(s-Fg)(sD? z-TT4Y&dvC}pF)wgX=(rwTV~He%tfUULfs@}mqgZ**wL?m&QMxowy%2O3{40$a z6h9T`AxNFmqtN%5C+VWTeImCr2qcDh3+FW`GU>mxY1wog(#+9t+hn#>~zIu z!0FO=3_RKC?K)kMelo(dDC)?=`mJPk3^(Sf6CGBsRTWnFP=5YtFkkn;1P#R=tDK|8 zlA2#w%pPGa8@Qk=nl7Ea%j2>5s;GLEMMFk?1TyH3Sp!Ru5kaedX+A*nP_!3US+gI_ zazBt?vrl#N>`;}Q#us;}$MLojN_-FR4~R%sh>+UpmvcLyROuh%HI442qs0tc1PYzu zCoAb5F}4ic;x=d>cpYzt3%wNx^e;x{_>J_n7bB}*Df`+Uyj|tLDb}9%SXO`8MM~3< zYg=pn7q2}@os^I;GCln~WnjCsew-wQ4 z+eUY##EO81Y>}88XpRz7V;^hZvBK3fD$nKksSUhajZL#w>C7!YD^W^S6<^dj?RR$} zGhN}iS`2gMxhcJ$S`T2C6g|wcb0zdbsB2cAeAa9`Uit-FaPRP7a=ly|2m3!N|4lQJ z{U!L4!^cvjY?s3j=_OKjogZ4!=4%7TfhA8ztP3A{Y<{{@mjsLR9b82_w3`3ob zBx*Ps3HW)nH&Be8^#=!+ZHBlh2N~R?_BT9N{Oi4I0d8erjvT+ex6qGRBfqHQ0=u$l zJE`fa5g@<5RrG1Iu4jyfDCWF?b4;Q0$tN5IhNi+_jvq3XY{wD%hqTk!yxu?IrELc5 zx6dy({tCg?_$Y-#5E_pKuwmh2`kv&J_^a%gzcG@t5&Co$ahj1I^z_)uQ8>Q~(D~sc z)1!9_MEx~l{O)|;M>hcoSz6DLe$jUfxeFucjsf+L?(+{Z`))6s+w8Rm05-2$TF8II z>N)l$ej7@Qpi`B{BLq}aJh|mh(S`eBo3&Fu$=ER|5{Ly$YapEUsm0askFLo!EmP8U z31s*9k!*;3CSsIH26o#In>!srz{49)#7MVslLUY;^Lw!v-wlf0{q24gW9_g{k)iy# zDg$Hz)#S`sinWbm>BjMst5Xx(YyvZN8?APSpjPQH+IK$kZkCwXXg;rC#4&OQfe)@n z#=Gq+u3;cU#|uirb(>sxb}D-2GbQaXZrnjS>0-$uBzFIc46oLw!Vjmn6vI49Ono^- zeA21kNg97Hl2%X4-Q!tUemrpc@WkL^jHyB3ycz;>5YSkfl9q3*Rfy(5Y~Xh1)ArIz zW#b`ZEfS%A34D*ju&lFktlFW=;6XijaUO{h?I)p~V2C~}G}K@SGR-K{mQV3g`Mj;1 z{f+^uUW7V3`EZalSv8h)xE{8})5g z{E1asNX6)skiJZb-I|X`!lfEd+J4DwdOcm`S=uK2H;7ChN_)>ttZc2PkID7r$ta7! zml@)j>n@ez{26;xS)k?^jW7jED6Fu%S1kJoD|J)~3hKumDO)$HfPxF>;)>krG)KR0 zRoPCIGZZm`wq(Z+S_H;?-8lp;+ch$d_;Kj>77m6l={uBOy<*Z;BF}Z2JGGl%Qnq*% zUoP^=1S@=ec()7fgacdF-+hA3l(DhX*^Lr0YJb-R62)m? zro0}+sVr7vMa5PyHp{=WZcw}?*e|?yua>o37Ybv~B8n9F2mEcIYH7uDgR);wixEdV z9uS4|rB5krR&j)s$U-o(b$ozmE@=^UYUdqxH;z&?u9^`&N0#|$xjlzz6+Sr5>b_S- z+3Ki9nSSugEY2^y2j1)=>zTJb@#o@fS%%z-XbALrU*mD&u$-O1XCgRZI-T*S zxafrpaogYwtL5}5yaJ|&`oKetzM&* z;^V1CkD9!gbMBMqZ*XP0b@rkknuJM<+0#k^({#Cm;VXT@q~3V|_w7DHy={8f{I^5JMWMWZ>DMIFQx zC+v|lWfMiW_+RKhs6Ml6K_)+I$;-3v`edshf7AXZ>cPi{1ae3Gvy{6@I7CS>G{ntA z%x6rqv!r(O9_8+~XF#Bw@4JvOoHv47v`${_qwCyQ)P(d|+Z8)Xr^P=L z6Bw*WLd?Aaaj?0#v&eYY6;%^fsf+W24nrf>25bih91H`#8xGsAQ0d8Q>@vKLQ_Kd( zTZj{XX+A4EX-X1J1!w!DFfIF*ly%%s6TX%i>I7&h^=}XD12oSK01fK;|2-mX*hOk( z)|Hx*-R;v&QgmX1OPIhb6qvGolA>7*>YUKC0So1(ShSJ{HZr@J<{ zO!qL_zFF)|ZC9#TD4D~+%=MEmiQ+^gNEBPH+tDZsuGnbE;Y-IoL_z1=UcA>Nq1)Vl(@R@1ZpRT2 z$%35ImX7{6!!$p^Va;e)t(e4kpdbWn8086Ja!{qyw)5Zz6)H0YmT^pAsB+97F6n_$ zq5IeY_ZV&dLVd9|gq=|(2;`39T1+2)i~ox(BURL2keCeCh^B|QHCEvx_Reo>14}pz zdpf=cMbru8j>JXz8l7hJ{@d?qWFh(ku!yy*=3F)5S2kjRU$@M6zo~cx<5Oq)C{PfutU+42S3}D4scfdJIyJrngXF22 z>VCm1ETmJ>RX}h!s^6nTn53s#T@P={@4ybN-d%2Oo;NmSrfTK(2CV?*kZNUGVE7<` z1=|R}18cwd{W<^99^o{Gd8<_xvmu!`{ylsXy_mqaF*bX7uyr42WWOzEEqYRCkejC! z8v8z=Ml615qn_h(tl3a2TSVTIrQY088wV$Qv97eSFZ*v8i)jzMfhvTPr`CQbAXLxZ z!z%{gf;8!3vsokV45_rdZ)ru|-j>KJX?GBl+0gIU@aTv2CvE*s23=tJmF>a(^W#aW z+wHCrZFI|1-U5##q8?n1UL*csuJ|)bk)@aY06&Y3>Y*~_x@NR~d>)XIVQz;vqZlBy z68V#>>ga=>ER$Wf-+C(;P+B1vFi-t)k|m4T+wR{NE#Ar}ub>;X%6dk~dvKQwJG%IN zPtFAVG;QqZ{E@qLRAyDkgUv=&_X+VN?eUJw5Z=0t;OM}B z5TJ(|@7>ph&eyCVGKhw}4td!lp`OhPU@t3h`?@!o?lOA38uF)okR+H4~-uXm@p zGIVp>_pLTh<*=zfbYrouBu!Rup(880=ke}xfl=Fcv-ar*(vh>~A|$rXe_H>YE5p3A z-JHYCbIk=8l)FUB=dO8%%Vvxo<2X0MqJQu;W6X{2c+KEj(Jzg-^ys{bJgL}!?M~I5 z)%Ln?Dg6%58W?ho2bp;6TQeIr9Z#JCN%uHlAv7{+60*#;^5QZbs&aomfsPwx`PtrP zp=O~`vz-lx%;~$-aPeuuRb^Dy1UosMC7<{Udbd-C7{stUC}+;a{B~i){Yh5Wyr$mXsDY%y+@{CrBD3{<1WPdlU35&`ZrQg0qpho$F&AegYu;Vb5MHdii`+Ps&_S zhKyUWMF#T4UyJ_>xgvD$<(acjvTO*ibv_dmew$V*1o$-h$AdjMqwC{e(l@te&<#PO>0WVq)^3Z&AG+S_rD2!IFxrB_0I3v9-)1{SUZ&oXR|dyD zQFkT#kSyM=D`Ws`mPVKY^- zdweii2cA`q8n_DbdZUi_|8qtTf7{~B$&w>vM4iWKCk$PFy2Vx?^__GwULLf@Qyz3hpmnq1ttO6&7aO zSr(6z^nLp&eRQH4TW&7=ydJ-r$(tI1VI4fzN0(l^DIHu%^wGAVwmiF!tqYC&d+ZePl_B-=Uo zusU#7+_wiSJ`HLY`T%rV?@|s7()y57qg5f`&nuH!YP@Lq-zx`QX%RqThjxi}QN2P$ zE_)148l4xtZ_nZ{*ov(lBM8!T;yUcBCMlhfregVi)d}`nE{$z1^1MR-YJ4~9;l6T6 zf4Tz$He}b9(mjkL`KlYHWm@2|OjT$!bdh`SkabpEwZ|e#n=5b=85C^;BYHiP#6-_# z_7JqQYCjZ*i5Z<$Ha;IIp1=s!`4CflqFs4lg>+E%x=>C>n6LJ+4RO4MNwUgSVV~qvz1FO< zRy!R}c@X^3(Us2^s;v)@j74kdA>jQeacueoATf~jd03;ua2ri<85%46wP4=;KE7*GfRBQ z%LYjcsLWWg#@yLGDNq&+>BrnT3!l>UIGCzhJM4LC>^=Lc;cS!}bJQnZcm|6V&^2&~ zk3UR}(x?288kJ=I5ZAD0b~73F5kM~N`Q!I|a%91$2^D3E+++}>649xBeJf91W$61$ z1J~~yT?Xdt4|gQ??7UuU3McevZ*8QM+8df7K_Q(7WvW4HP5f5ydG3Ql z5BfMeBF!uH+e$t+5#_RU%1=D6vd(T3FM5^Vo3D2r`a?zS8ydB3DP8`$CG0IrmFh|V z8OGrMSjfr%G<<_200Qn<)vBbW=i}#X)P669+#>1s@9!}TOklFNWCT4HWiKw9BMozE zz1>Lg;vY2PE2LiJ%c1gJ<6Q9pPzo8$8&#&L_}z4XptI6E*~g-)2W*7r123)lfkmB~ z-JL-5YQUV8lm>RDMrUj_mmYyt1pcuj{Y@2&$|~l!h`hcI8be!pLM9c1fAuI7GU|Hh zNe$`q{$$<4ReeDMZ`Kcyp@17D4WM zy|SGD+A&hoqADq~L?=cpaAOxaK=J8yn7gSuGcaBSlvq7T5@&u}v^((opI>9Zx08r? zG5oUe=cdj$o;8Wq?8!Pw&r?dvMaD=`&2mpOxtqeK)=dJZfJ*jWdiAh`!Fhb~yGUbowp2>hjA?$iT zG7qv;w1zYv^aHBB`%EE|yb4CL-V(4*3-*h+M zUh-e-GV&L$e4DfTxnnTu#;h2}ol`z-gM3C-1FhpYa*0?PYever=q^S4*WkG=aL*ea z?<>XNrJpkKx>ww97hG|FEtl zcFFc;LfTI;{pJrA0Of)GxL;-AjfB;^0gu=`$x}+0CO4`9#m!#>pV_m5XZ8D^10n`< zd<7>wXwgZes`o2hHLr{IWzP2(AI@WckOxe|ejTqCW3kv_>2tOMVUSp(21jP-eiE%K zvo}el3PKfT#KsE_x~k96a8;s~lX>K@kj|*q@Gj+e2V9V;IoHS7?XQmw;hvZ9QQd1* zQE;mmQ!ne!s~Y5kl!Z*Y%0CxYU$Iesv#lP!TMTJBcGRF}a{8{60SF78^sl?Jj;C~K zY?_F2=r4HLX?dTqVmYg?I+|+N-sv~E(NbT{z8#Awyxr0~M4N($SAT!PmVKVI_Vp`x zrolrwjsn{laO3un)?m)lu208WYVT3l7ZA$1h~e+kipG9xW9D4_38ST9W&e7c2wQtz z+ElL5jxpT!z2#@*$%+PJcyMIrep1%I!#~XLRM1Q0w!^)?Q-+1Q{#h^}ppbA%bLur~ z^xmKk(E=Q0Y3VX9o*k$gtMR7HMPYx!X08GsZ|#9v1O({_KX2G8MSWoe{Wkc__Aesn zwLI3QmM0m|W7ID-iT&>HlkfTdYQ$B?4N?1gRR^-Bmzh%@Nzmg{$35D3d-^la)y?7Q zsy&Cj`1EVIDEltLV8gLi!qPV`b)LzPY&;AO3K6$_u zNwD^U!8>TQ_G57^$EQj+lS>Bs9T75P%_XtHi1{;MER>T^Gg$QB-c1XoxgRN zO@T1^z{zoJ!$TTg*zZ3W&5>qJ|Ja0NLU>eYeLtaMFBSVd;Qm8;uU68%`28zabnG)) zHu?`qR~g=M_(X=i#Rl*D$2=$?tnD(M>Sej93$$9c&)v*x%2_4CyVG0(Z$Xhv;Zv@<8+Ju-O zt`=Uy6g(_9Cqo82H9~Y@;8uRI<%2-24^}5TELRULdG)!sAbh;=SXY6m16l&s_B~O8 zfv>Z%#cCnb6z~0NM~%WwgI>W7_1KG3=#p9*H0Qw>%vE?yw0)tRZSqzv$M3~z;_gq) zF7<@DihjTcoXooqi%y20{PQ(3$LGX2ihg))jDKfGU>r~~=02lnSNyP;#qM<4X$p`N zl3pTg?C_nH0S)|(t6(F)Cs$y4+z|;bddlpsgd&?t+97=quJ}z0n-d&rAG@Q7=vv<& zAm_cj_kf;c;R*%?ENU1}PwKKjndi$Q5Mr07CLR;v6+4z!u4}6D?BXTwrxN?P!^6rv zl{(Yxeo9HPknbqtr9ekQC;MnM(sb(jUFGBmtPEWi!b_iiT*QRKoP%^P9=jDvrnD%? zk|4g$fzw`m9$K4-ue|oU3Q{jUY*Mm=Ib_B+R2z@? z`JZoxA!4mao(r=78D%X~h$^nezq=defjM17Xaevdmt__7sMD=Kwg|;^S3iX?&?WHw zAsM=E!pZ73i2F6d$H7B&~qTac1>T;d6D2-ju#Z&ebQMp z`Nm@BZl>LyMdBt@JVcq0K$+j$o%fKQL2q{n(4Vx44*|0X3VhR7nE&2Vf9z{j;`gpW z#N>pT^!H{kB=!r{)`;gx97vh1ceS$MH-Dek(2pcxpxObjXpoIu@9?5%mjut>k`a_xk9B zzmuuxnv3y6?O~Prw_c-lxYD0PxWkerJn>2v!bpiMFth&qW6i2O{+~x-(a%~kD83{>Se}?~!fJ>JQ12 z=R7ixh8q#s1U4Vk#9x}=2H%=J$B~)nM97f(Pf?XTnP&-nvBDebF^8dYB?RE5SDD;3 z!o2H9k&ZW;^ z39MzJAj?iAd@|cBeGU*4)X0hJbJud0GjBq+@?^}>xgsWw%@x-Y zR~Din04#XaY5nLo{!IC&==_5RX{$@U*gDk*dC~V1;|$t9mKvAsjd}T<`x~7a{_J`B zHF@`mT&e%)`fbl&zd|e?N|Am~0$s2S24h3Fu4J=u3d5$BvhC&V58?)IX|CSm|2~&y zkMOv}(-3be88#@L2s3|}6d%z4FM{}aSBxDV=}EMM33*aZc~f0O$(;gko!hJzS5RH| zWS2UY2uN4y-I+4^SE_2A2hPOi8tSR(S;MUZl*c!j;x_kPN=VdhguuDSoi+8+2dA=O z3>a^wuh~Im;Y@Tve?1Y)g|5f{MXyb^K6Wgf`WVu;8J$aF6&w3?(`9|7e*C+fKke5p zZd!|f6@G94H^3wHniCzZ6{!K5v~|v#yLBZ@miZOlj+BRHGMnDyiL|;YG)R#j;K4q+ z?76Iq-&DE*DI!zuDF5$(Qs1@=65KIt&@rw$CfUFMVnG$MgMUjw$^fc-_nv~Iui=B7Q|FGSbPYEmW5|S!M7tYUcf1z;LPw@3 zz~Se-lG#j3vv4{^-1Sl+ZT`IYY4?cLzm2DxL8J4rpNkKaGS+@oU!sK34pARLjif z#&_!5h@7_sB71Ex_C%k?yu{HnetO5*sCxn2Bj<<7Eem{SOxal8 zU<~@5*cl`1$#fI+P%cihvzehZm^)%AfY{H@qILQsE~)T`4ydO4+EGbbrn>vSVOj)2 zv-J&stH!=PU;|9*V!zi+dVb>(B=>S1+x$;jie$y884wvs9PQhvT6a86rdWP3se=txW`gZ+?is@Lxl-lkHJlS&;Rs3TGfS0z&0g6;fCaFVDNhiTtq0TX( zSgxF2aPb!3-LXpi>=t8ZOkoE9wq7-79O?0*37i zO`p2MVa0%@!TCmRo(B_qFd8z-+D%XA`sV0*#T+lL4T+4io3(3Fl0!B4qr0*HHC=Pz zGsC7%r$^M#5&=_!VH(*;cyHMtIa=uE_b8SVr-QAokgT00nGez^TUSFJt9xUc?Ay2< zsfEJ}6=A;x-|;q%v4LVm(w}cs$?lu!Z$4$bG95!r(h66OPP$4A_B!rwPQzE6Os8vW z;g*-sb-(j{``X%PoiL9rpJIvE+X*M2`||zWc|<}!^4nJJwn@VIXXp3q)JZ@=_qTD^9De}_r_a{)GiZV$XDEs4&^LL3)_K}|k)B;$eD)eQ? z#?(Z0x90V~?;J(XtnVk?pzy5%?g)vR zRm$ap0%Ouv#x|RA>(=zu>$>1$@G&FQ4gvvv>j7zFeEq0o7=W zRfGMUMlph%OhuAhdSUJN5Yn2D|9y%0v&crKYQ|k#^c)XUa5naG3p8evYgaw}VFW+8 zI$v;T5R0}*U2WTabQKUVoy%Gyp=cnW3J|h+I9()rf{yxko8Vj7>YYnnD66d~OpJD2 zLLek5YW6p;9uB6vvAXkE-s;{6u^6{C8igFzi!uK8k-(} z(=)l1k^>tPwYO z?iHatD_?W5FZastc--~dIj)i#*uhW!Obl@Ro)IqWOWVA$vww<~wQe!QeAKM0cEI`& z*II>*R1vc&D66rf)j@P9i`(8gi^tE#g={p6lH6g4V8 zz8aHZ1fxA)En|A`o8}FCpNYN0DNj|0k<+8h1uK=V2q`@3KXre9hZK5-~cv%0%jSUz=?^IU~`!LEH&$g!r3MJTN1*l-$b|`m?WC*LItS z^Aog)6IMn=|Fkmt+Uh@!4?tT}o2XA|nV+4wblsy}@Kk z&)?}U`sG?Lr;{NK_+y8g$kr;5!E|M3j^1v9{(}LhGhW-qgGF3xJ>+@(dCWoZ%_)%4 zSv5Gc_)pj6cZ4SP1^?=A&6z7Eew{?#@;xS3(Y$|p=37TtYr0LkI~D?c(QFcc^u+Z& zdJXTKIc#z}31UUQ`^acBfYu(o$yGvE0L@a0B5(oP$_8Znhf+@x@5Rn;t`AS>n{&N- zja7>;Dwy*ZI->B?qT&(xTY58w_FHZP9!=^5`G40prykiZofF(!9VHgidEwA~u~hs# z2O>__POs*pgKGgZo=);*;}0|qH4fLioaBqEY>`j##@7ARre3_`w;Rms_S7owBVP7b z_Q+Q%I~8nyNi*YTZ9d|kCDqPj)0gIc+U>~G`fOok#z$2ssy7zO5{;*cYCidyS`8S- zLy$lAZhGjnI_hO!yLHsSTrkrKuWPt*s5R&)R(dN4{-B>u>Dqal@@{0LA*MN(IwpF- z?~cL?eEfcmg!aAH=z(98yq>g=v>}Qeb}UT|hvvQeh|Rq zzm4C2D2y;l7;&a{$V3YG%M+Sf_6seu)s92QLNCfg{$>Zu-KozUqk;W+xKKz1U#ELH zeMXi(ji1i_S7Emf%fQ2en*-D?4O74#Nm-Zl6E()HG957fe-W}`xwFds*&0oU1TL3zn?bL>Yr=#l^qykXg<}U&t8CLND z(DRA&R*}Gf#l7nYja)O5|PFjX_@pJ!CCC(%ODgct*Zc%u zH!N~5|MdJQ649ItEW08V4;^}~aS}Kbz^-HclrxNKVN4linGHPEMe%J)P z?ja;j%rOG=KQoDQAW+CYB|!YsV>xB`XZh|lHGNop#rvx61#xNvO!j?2ohmtIAs{W8_ zK`uhPLEybMEsH<#l%@y+b449`W+|7l^b`#1hR+z?uWb&~;9l({&5yy*K{j|3*kyFH z@Ak9fEUx&s*nplrFr?|j3EKvPcOla4h@8VTQ65Qeh#K( zv?g*F6Vt)vJL8FyG`k-jh@`^w0L`%mm4pDlhfRZ|(@T+cZ+WrHzdL)YplRKkropNh z41i|sbI&D1+x_c4E9_6_ME05JR1q0MW#r6f0t22n&w0`*vljW2$vC1dCZdk`^;a1~ zk0gBK6YA`)f=5*JeH-`(yD;NJavcl!0t>V&i9c|B);nrHb*=GfY2@te(tL2kWbQA88`ht1H|nN5+jr*4ym>G+aI|aQctfQyK~t zp20deBxKOh*tCbNfu3^+oBe zoWO0TZ@-HIr!ip&r*(u*j>}rSiQ1T&{2mE)WU>mr+k+PQA*(FO z5(nF$s4By#n<|^vFd@cDHWR%1OUvLcSNP@x^!x6gO;!n%-`DXS`pw-yjix$~^RcxjUpLdst(rtcW=iM#+{YM0cze}A-b1a&eei@7WR`SvDvtscGY znZC{AI~t^gH5-|17wtj!Ntr@g6x;Q&&`+cxe`o(3gu*;gj704tw`#| zCl$Aw(RIHaJxXk0a%6UQkNGo=?01-uepzyJw|{dDaJH$uI}_8tp3lFVU=?%2E&=kY zB~Ff$K`pV(-RZp2^K+W9U&0+ocuyQG@_Nq_7}2{>nI}(n64#dNcSVO9TBXSi&ldUG zzU+B@%g|P60SAJy8N?H`c%5|1?2W zL?GIEd(D5TCf_*Q+nWpe+vZ7}=e3TAJ;5o71F*brszhH7mO72{V~5awo^^sxi`hV0=mwP%gWt*}Qw^&!EG1L&4@QR^ z+rr_K<;_ibe!w}*x4SHxjYT`K8pc~A!XBXnv2FQTd zywPKPbo|(zz=>T>b!D!zxnJ#xKXfZw4$J*70ER$$zmv3PqPjz!4N`q5jhd5JQF(p!s*i5*Y&&0N0!C@uWh!VR7pN(i;mR4o+EWKx~ zIHl2n9E$HKJTCp0gbI`;N8z+s6kc?%c1N#^)={m>K%9U$2{Hk@e#bkI&C%5_|8=+-@q_Y?h?yH2d;x zct-v7x~urZwOmym8XOoJ)3|x|Fkg&aQd`5w_P`$X_ROyOqh`K0F;&*`)qlw5*tG?h zh~7F|b1@Hoi5m69bmzSS2JY+dky><=soT5UMZ_m9A|HEy{rYp>1!C_NcdTXEP;2?} zWp>{Q8|N_KzN2+jc^nejK*Nr!G8mP`+H1Y;*>W^Lc7hC|#oX$PyLC0nvK>9%=>S@- zUKylUEIK3IJR-CPr24+KT33?vGw&TLclMsMX3kgvciw0-#%^xOQs5`N;Uou0W7GD1Ew1$veEIoc=Pose zr0a}2WG($7-Tm64haWmX6VukwI+A9#3}T&;{VxXb=OkNgFA}0_EQ~*we(kKqUZ4fOU?b?{_USgIPI2~ zltx=>HKP~JUe|hly((+(mehScUpdmL*DH+n$^fB4#+Ti9M$T$D_qYQ@-tqBq^;-6< zwTy1BSNLpiLP5Hi&#zm#->+}^g2q+D7CGn!@Mkd(2Zfx_EYL3o8C6SX7q zR49Y7DBPTltHN}j3DB0I<&-W_GkAdoWV^p;cI!mUY}p? z>wev))WZbu#!R(HRtUZF)>7)(Xi_B^tVe(*_<{nwz)gszvR8(_g#8@Iwn>|PC zdenV=sx_eNCDvw+DSm}|yX4@*)I8u{GttLS?tsifZ*4&3nhY_S{}`f=G7KDNG7#2l zsoGReB9sFOb~WCVo*|H-HHRHA?X60mI=NKD9N+Q-ii=P+ANGJg=l>7*+du2bI)$M% zI|xpDY}>4daT}bxoZDGh*D@#8xYspM3(Nt4M%>CR+D#?6- zcUwHtY-uyZ$haDN6kfbcE-P$LKXdPahnp{NHJ}6;kqx`wVZE_M+{liQz8fGiccJzK z>-FL&S+v2UGHrQ9RHkbKx87;%aMg%dm%MohEl{sq5!5amjJFT{K$a~pq$B4d)L2In z)OvLR`r|Jzrw#uP|NL)-Ey)^nYd;-nYiY|-q_ji`Kvdl4>;Cw*qsoR4_qt;($%_SR z@Y?Qc-LG4G>a4o1_4RyxJU*^#Rn^-5x-%k0UzcFjvu2g#;L^OEZBW+f zqZ-MWnaJ5Qk*(_~QF=O8OvTg$GPi!eRMj%dw|3&7BmuWV`gKG1K)@d z4*$x|F=!g_QSP#bX)U!mOCM`W`BufDX0L{|5n%7-Fw$7tu!uJ1C%54;u0>-x=}uvK zU2se~WRpj`1W{Wze~G9XNoe=mnBrEs&ss%6WN6DhC3mIAjNEJcA=?a;XL5d4@QDK7 z@U>nsR9t(j`b0oFY%v^*qH{J0UvBK{X&qoVgKI%bwCyrFmUg2Ft5x%`zeeXWN%;Tt$3MCgZV|EE%uE4ULog#>_YGhz`+B`l z^>JP5yyA{&X3fp+$m`lFslI)Cyj~$IUN?)%y*0LSGk*T|0>QH7zAwLDA?U_vch>8^ zx$KwECuRqldgRZ-udn#W->&WU(!@{6t!jzU-u-D8cWCe3U@feZY(Z9Kn4>GNK_Giw3V=w(G`UB>izob_^d+ zG)40)Xyi56vS7(xRM>0~4x~9#4*ADu0r{w(XkF>6dg{z7l(_*bn-QfB9ZT@E0PHM) z{%`;GPol)#QBHf^cV^KovL52z`l?DDjBEMxem(D$+@%V1eto_40y8o*1JJH+m0&dP zy$}W9aqY;wGa`zpmlW%5;)DB+0`4gF%R;;oRWZQQwg2)NME&?!*(Ul!GiU*;K%w3^ z6?YivtFd63_kQakjSdM8Vl|!P-gsCQXj#$oy;0N6d?_^=(-|6P3<7nx6^Y(B4_Bc_ z+dS2|UVjM#`C$C|%P(uKrUPO)31?AAmB|n)(;DVe1=rMzCh<~pyIz{!-PWv021*wA zm>tGlV@iMv(in}2rKV;!ylAHqTaE1*hQiZ3{UX!oN**&fE8#8Hz*=*e+5U>=v}Ne7 z=WvW@*4UrUqp_@VOP5~w%6z44s8!h{-S1=qVqCYLNCSW@Ay4uG&;pSMo$6~+g(_QA9%Ep zAdSeZGIZSHzFF+4@7~=sVAcu`)->EWp0PPHt1qUSu@Qz;t-T}b^}1X8o;e#ygcI1> zD>D^~_3u?>OS_q{A6uTKHjK-*yU}bjvZ{P9xoLte@l`fGxx_SYOz|D7gL_n~?*v*V zs+||#j$8G{vt)l^CpYAMqabI6%H&$z5RHloAr~`H!IOROvWk1_NA|^|6>V4oJ@w$s zy$^R*S*J=a1an{xEt*6m%;4t#>Tmu=xR!?bCO%g)CDg4mv-V!vj2D@E*~hns!J5~h z2MDg^%WeBgq4<+^UthPmEw|SzXj9O!zCEstkZ0*QCWFT9M#I-Lj&+O{suKwC&tETd z`(a!Aa&)JX{q-<2?nAv06+v6|S}bYp2>i~2@5oLLdwNNV+LYu$8pYh~yP+VAb~~H( z*sD$P)^^#{Y2D(fnl)q>flqVbDExTYZ$JOUwY&ClM;K+i(k zskw#+f+qQ79U+XA?yB`UF}T~519}RJM7=SD6FODr3vD}L)c7`j=nZ}q}r}H5BYZph41Qi28~TABe{(a=VE;*CjZ{BQ{VSg3d-L;M8fVS&A`$GyKPrz-5Pp>j09!y<=XruhD$FQ z(yo_vg<>^fA-D%%X&TUjnCfMO9ai-~oEtCy~l z%sbG7*|Ozb_AUolcf0jtI63-WCAGof!rUngOut!Dor(b=bq$)JT|!7OeI5X+W(U!0 z!T@?p)H-8Zkp!uRG|hgqzv2&*v?a2E^y{ zcDHR!(0P8n?o9W!mZRw8+TNR%vLn=ehblWoWH;N_zVFEF&C{<}xSM~sk?30M`3xGq zT}uW-8Z}BCYUa=9D=KgEKmYdH%P+SIWM=yoxmApGjYCzh);o2#(n`r4e!T2se6UKaLv2IsIezq0S%r%|5uRc2O?#Zl8OS zqk%@cynRvL-Geq3ccX~r5i96Aq+tQHj~U|{T3YAWi<;~xw>r8FWxc*@C~erYqv(CN z{~cjDfYo3jgE^d+c!azSeGESZM{0fqX-1!H!GmwG(-Y+5-Z=Ohb~y{-hmuS_+%9qp z*)Uwjk7aJPjfS$WhlfS*c6X?rpc>CiQs~B7JKuX1rY9I=Lqh^{BBQ-|WdYO}w0Ge= zD1)0-g^s$fcAl%u z1Kg?jE4m{y@0aaOcSN8$`K&vtK%ZHqI4!558WZ%lCZVk$LSU23X#yfsOk5%3Rla zJ|o(-meX;A<9t+AU(fq@zx$?zvp@Xsq4~vLo*GY9vz#6Dy?%ZL&3=6BifjWgv`Mc1 zh6Xg$vl3PE1;#t2o1uEc-kWFMXd`jD5^_2~NLdq-x8kjSJ#TuoeV4l9w!p8EMB}?R ze>!xrX9D(*KkT=keoo-|^?GCIZ3A0-xdHbp%q=6GTzi}hJN8v?*vN9HJh0+>o*A3; z5O-W!iHq4oI5ALfJY{3u_D(Hd(n}LIU4=F(%d+8@nOQX9GDp96_N*EPd>qJa)~Z#C zxH1YgLBmn~0L#$qpgM~e2Ddp88skVehLkfh?G!x&Cv)^BW=v0WFxfhBu7#DdeO26)b=z8IjnVb(a?7E+ z9m>Rn=3a_0-BO2$Y8QUC?|eI{+Q~TG6nfi6ll_jh=et2b8{|66)&kikCmH}1X1f)t z+OZm0amUeDl6HT7Nz~Ra{onlY4-w)e_87(8;*Pca+FM?K+8Cy8&OqsmN}E2mJ>xcT zuO+jNwb$p@a~-3Q)yJp=I+O0U_fng=wr9eZjfF~yo%X58RFHv{FDNaE}W658a8!W8>@B4ACJPOc1X|(>1NOL4)6YRB}e}?iRfonFeau z>jT}|lmqQOCSoa!fQ_!q-4r0i$cU3XpI5kEQ(oAInkocj4>7aCK6fE2S$bO+78M$_2uy$8I56i-~x$oD&* zcnuQVUL0N98r}@f0TxQS?a9>5x)TfFa{G4qWsnE7vz+zVw#`V2{*3RxR`oue7BOa$ z+-u$UeaF3gdG7=C{s3ptp?BXRPnxqL_udBuB6-I>(!A-@r_#5zWI(1F2FQ%Csh`)i z-OZN!T5;dQh(}c}UlKHFN}>B92*rmX7n$A~Yq4)Ri%#TeURw7xiKTaO_4}Gprteim z-sx*Y6Q<0(gTjcb;ER%|D4nQS+s;lZtuKctUQ&cgYl9IeYb z3p3rhhBrNOHo8rt*Yifru>=-Te6#j0C(X3;TkJXlwsuAMzy0Tb+nciF8ns+{%v5zu z(V?2Y_VxJ+AT)?D0`RFhmrGb!%EpFs*t$k?B!Ma>i8abTn!HT<-JCuv5sQR8O z=``>w1g3Hdcg!>|yD^$c`hXlWtC(cTmOp^wfhU{0(G6kZc+EL$(cpJ!q<=^=UI{0+ z!BHQ}w__jQ&3*OuKU3r~BE`gz za&m0J&9wSD?tAUs$g{qb_AvU2`(7H1TaN*I;V}Vi$w;8e_iE^@3Yqe0k&3S}>kb|8 z%I3awyOGHW+5P^yLNi-idN)x!=mr^{SikOmg|e?>Z(UH>g~^1R6#+NU>yA0aT~)-_ z)AkzU93ayF$KU@AkB6DDisDN>@0-Sy)>ba3sa0bpb=fnb3im7B{krxqzx`TJm=?Tc zBYouXkJ@t?RS~iGR(WK``_4OLr{}LbKs;4zul37s&pYFKTp5{JXj!VQYY}|DUavdW zWBtR|{Ve>GZ97o zQWMkmd6?TSg3*j00i+P&_C)S-=@s?kx9k4(=gfB5*VU>4M!zeu_=wNv7OF zs?`I9w6UeHhVuGrh$3PWp1iFJn@HB?pYv1nLVK8!h29s1etSng+HYd^j`yk4#+e!C z@$AoK)v*=q`siqUhX#9)Z=!0b*{ZEL(87u4wau=jMX_AQ%ki;nyO8v^bWroX9KMSF zX1=#Y1JmW|I!B7qO!1z@2Hv zjL`B(>$0HJVrFGXtv5DPRlb&vK#RXw6x%9ut))qW^e@fK8N9ZghZ!LvfwuhAK5*^-c4Tzne^zd`8A)NtHiwHz zw5q$0=&1b*+qgMZ6-f zYvrA5`M%cc8E&@LYPiSvx&x4-D>CvP`iFj|SoEK-m+H8$=WAb!hWk!b-jQa^!sU*r zwSAda)*YcW=^)Ga_4RroBWqtf?lR)%JAMoNcP^XL{N%5`a5a_)RjjQ znDtgwKt{%Q!J>=(VRJRjc6NF9tGV0LK^(9xJBo!IE$^uBd?ocgyYN94?OrML_Gyvjd+{U76&WHLhKm%8|>w zjz8~EH{@4-_!y$7&llmrkO%5+ELlbiv+JT!nArk@%=9;wTi!pfgZsIMJ#rlXiTC&k zc({Fd`-^VY!+BXP!X2i1PM1}GwSK+&Q;bP5z_pfMPd5Sj&xx&q^W|6CrNE(*QtV4EiwSQWfd|BwjZ8bMBJ~Z@1?dM zg6WoH=IU-fC|0*dVs!MC6isEdMQ6@PL1o8#53GFbGKLXeqjYD(Op=Vb_bVThUW(Zz z?<8|rZ!Yigoqng9v{52xK8d+*o!a~4h*{K>@ZNOmK0X^f-%GrX<1R~3HS(YsDSlR6U}RWw~>0|NQOscwEo>zVFy;x%2soHW$j=%hz&L zeLi20k4p+7cNZ~(KxAm8=`y3)*VilM(p~uW!(((@+Ce>wLf5du{qN^(Yus;}4B9_Y%qJG3 zg5=izo*KpQbhZk;_Z=o3s<9&7Yy=}?m|dJ0H{J8_TeM=PhMSd#71K?UsHrY6L0w;D zv!0KD3q2*4@g4v~FKo;P`NY$xb_G00Y4PN;7R5Hc(F3Lt;7y?I&Z9<(hqCE)8VsKk z(&*=RaITJqpY645w1l$ZIs?H8e47Akwy#aDZrEg2j`TfQc%;c(bhl4rY@MpR+#)z236r5Y~Q z?x3b>DXLr!MO|}r%Pw;+#a($7&~4B>T| zAGT{nB2w2JT9@O!f7~R`+gsj7Nc}U*9}~;Y5ZL$Ra$PN`r5^~bajLAkv;+%v+GqS zH(RpSydNQV97TLzzf*6KZA@(@y`>Onh1zv(1+Q@kYKWRWNNnne;`Lg7u&o*ofmeiG z`+7Zp{pA;zh`j3Bn`hc6Zl$+HfkyL@sj*eHgDayKa%V(Cx_a;#w+|t~3FO=MQ+6dX z)mM!+JPbtSc8@zpibjsl&RYJuZ>bWNnMNhOfc&7$f@p+mn@N&@S>bWW+uGtRq}a>= zE2P_Clrz~qNZDZec5vb1TM$ZOwesW`B2UyKs><6~YFHdc=%gG_&GEn+*^t4D^LVgK ziDhI~IR_h_l;11^)?=?_{8)B*GoZzNc6T6sc}7Nv*Vr@+Iq0HTQFLY>OPjhmrBH26 zhgoGc$eZu&uD)D}(`~j^3pO3|qtY^8;=b=|UxIP>Ua8G(qSmwpnVP4(u1%v%Z!`>y z*WC(Cv7~$bO@ni0rp2$B;cMHJE=`PzN(|$Th`0}v$K2(kpZ6HWsE6=A3VKZiP<>ja zER)y_kf6D|?%1}g)d-Au?qVj1$V~FujV2B?k7SuuL>usrRzPP9K;6OR>R8RachU5z zYfd_cjNap`E@*0e?6f2Sh=9E(^Oc1x8vK9y)1NA{3TvCh2<|H~uWPNfUiWJ)-)lvb z>}6%L`1sggpD!U|ulo+vT5In{yPwY&+zNQTZWPw`y=Tp;_+G z^2La3syuPOZgXF5hUQ3$M*h6xr(b^?dUw5a<%%C4`!BEi*Ju5B?aSI4GTAlCaGd8<1r?<*e1DE`RhCwJ)v}vJnOBl@#=osuYwa83nl5YMC z{G%WC&wu~-GAB2;HW%%Ldz06f29&#Xph@3g$k(cASn`>LAW z@Vl+v13 zoYrP+Mw0otG$J}b ztwtU-_pELr#sj3SHpGG?DTQv&{n~kokdYNK(_kR)%*V&}*RP+ye8R+*%dEV!?mMee z_6Vd^r_M+->-0Bd!}DlUz=SIF42r&0$H5@jJHh z`f7WlDi!4=_dv>ix?Sa<0g(XL38|)TCaTOBcZ1p*RAQd50B9W1L8j4MxTh#_7;d8i zy1COrfM9#)_g_?rgSGTHs5cO*o0{PtFhgJrnpNiv^)POFfRfJz#_ec)i=H_e19jX0F8Di9RdvrAA8VqFm z>?svCB5qHw$Y`or0hC{o4xI$q{f+<_eXYDBB5mMf+e@sf%TRr{&}4>#czFNS&?(iz(i-C@J&J7EHz+?puME0Y z(AK1%9+%9^g8%Ek{gcg4r@3$Y{OxP+wXc;~_t?}a3yF^IlDcH=<=tP?Nod?uRm;61 z_wvWJ;=b`6lTqi1z%t* zCL}^$WK73wZCE@|ZrfxGJ7VSo)n|tzENDR^heGpRDLkW_I*N%dc(eg5`)-W>z?zc> zZ}T8dFVg7F0g-BK09fbZ_cUt$_ed6vEDqfg%Xlqp+A=KTx9yi>X}xErnO%ED^o9l@ zSBD2;W^21d3H`wa9u=L@h~;f?Tvbg-KvCQY0s2dG{$4;;$L6B9sdBK<5N9=f23YdZI9uNk+%msjKXmKg$_PkKD~l;+QG(5P{C zNrk!gl9PdE7#T^S-mW?6syif$f(!%iXj8K@>X=C)02*NZr8oys1+OR{C=E_QUk$(* zwcFd4Pl?mLSdRfwg)XRtprT>D9wztfUWnU~HI@cooZg-OumA8Tk|MqDy$}g!WG%Dh zo=5^)z9$J1Ygr2ys#6AyDz5jn#1p#j5cN-~e(&{q-ds(+E3nA5JTqStG^=M64G;^W z!f=!Ch>yo5YNz~P?nomStbpYfQCZNskhtYFUlHj*(tdg-fZtu7QHi!0s+k~SzJ*3m z+P>JO8%~d9S(WOtrwXr`kqu{c%IlJ{Gzr<35&(M5Y|o~S&b2j2YX4`U@+R*Qn-Z(P^DBO}5F6jEqbLhU(TAyG{MX4`{R^a4qKM=rM6Mko zx_li{nm(su^qp~roScaXn^2(@3OK73rq6~M1>cMACW8wbH#KFco*>Nv8iFyWq+QLMvY3vRAu$j@{p&T{SW()?Z;X!-i^ak$C*^M9# zq*3$0W%zJgtZTWm+{Ce&1zplESg-Lti-4Cgv+f9y1n%Cr$fc?K9R;lA5wW}(5u$4K z1h{vy$S$|~U(lw=tyQd|+1}d?af)BH|7v!s0Bo^A&UB6@%|u3K1kpk#TRyy6F(p+C zr_EFiSz~kEl<|&Aq=M{0W90KQ!#ZY!*n9n?tRcmYxz-7+r2ykbE{o?gmosyL5*1OT zU5~1ST%Y>zHd4}m>Xukx&}}nBkj=H|6?tdqL2C7~Ml7VmT`OA6m!J$BwIQwdG@@ZKSyOf(4#;C_ohXdfnu(*=8ebGkVQXSnk>qopM5?wrAyUPo@;c9+ zCl&Hh%YA%#4x?33xG@SpOV;}mewF_gv4{`nx5dk`TIX$U@HT)x z6U9C=VsU`1xJk1n7?^P3CP1=RfhcYJT9Gla$;K0Q=f{#rh*kS5>KP zaM~X~m9LY%-Cfi%b2k$T(SSoE`o`;eXkz4U=8TMt?C3f5i@6JRX_GZY-Z{6`eVG2M z;&0M??Jehxt_hWKt)GlWDeQY=(Y5j;)YtQtkM4D?Uq7EP zKCXS=w_?f@_=m6ici*lH%!67gx@1#YH473!8(F@V&Tq@m3b5WbV)mA&%b=sD#Hd-z zR%dg7YQwdbTE=RgGC6EV^M;Q!Tc$AzACK$$`sM5EB~5|2B6}}2=gYUqGkbfxo6QjH z@CS>G3<+>@;%G!_WLm|nx?fsouQkCW`MPhnE^SmyWVVttUkdTy%QKJjMy2{kr~9DX6Ef#fVFD|HoY zd%yEz4rbD1xu#7u{Bl$?<1X8GT|ZFQ-#g;5kVd=QF5=-_hL7!+w-<*))@d`ZEs;9j zJAH_OyiV8E+_j>K{sd_t1bJx?=PeNG{$Q=G!;h&|g=xqYi=&aL%!t&8#ena!ViNTa+j~DLfc}8a62pKaeTM#hw|KSgRJkrl=xf?Vn!*`4O zGRvK>XB5v4Ayk5^>DbrW8CjSK0eMGi5FxeCY9Cvxs^=Zo)gH}R6F^`NI#7*Xt&1@Adihg{h;yo==!<*#e-xp7)PGeh|z%-3YhLdOdHlRXN*?+yCM7 z-pjuUxf_+G-bllCeZ9K_o3g7JHcAgikS4dSKDccycB->>dH3-MpLsvDxIi)PRG^yQ ztM^tV_k{fySN{CxpCNmV`Qad+k@p>YISAF`k+s*-%*)+gcPN^R5z)jtw(yMY@dyI8 zJfb%eAaOXq6MbOLvy9eyaX}OQmh*ny=;cZ6W?9o;_m|Zau5v&HeM5a$&2NP!08+0s zcrp?2yPD$^zvq9Ww?}rw?3mSiJOC(M8plh+%@yO02hfpyA8bwkC+9~PpxfVq^f3q- z!gW8X(aOx{tDY@gPG?=l$MR)(xNVcuFW%-ybJeRG7YPo!!jcLsGmg(aB*t?)i{vY# z`wb=0#)zsl8^wL?%s4k)6R9N1^#LW{kXT z>9jZAghFp8sXS_8p!VakYirtTtHol9Hs>)~V+B2YR^$~HVeUM};(~*Rp(GPAdt4gU zRU|TI&OKPRYg=i6z*=<&Mfr$Z*hQ-Zt)+HT$km|Iq0lIT6uoKM{qv7m1Z~dXARzi7 zN}htS=0;#`cstv*^Qr!5mjCy^|Ksbv-8`Y*%76WOz9RNoHWHTvB67LywTyheUT9!i z+v=)|zHHuBRVGW?8Y8d0UlAGok`nd&ifgZoa<}D+KxEO7S@H&2+cWFF@9SD$&->0Q zVNdc|}=r#HpNeG6zz%v$HJ&vY2R<*OzyRtuR9g)zRzdQuT zuIpK5FnW(`>;wqlc*eJa(MXMgdKz|8f}Nh`tUNk{c|E0hzD zO%v|nr|+K_EsdNDc+u2aQ>NZ3X>Ptf@6hiVIDS%*kJm;MkQI3cJt!&GoOY;)Ksk#%igTvcHc4B zj=9dOUzyK<?HkN)-NpZt2Lz7R5wgT@f=FlGC1)TxPbka5l!Q`tmM+{c^^-EIzY zog+~ilBQ0A9-@yD&+R}pP265)%W%;i?$=oLJBmzkpEY8U!ayHg0j_yt=DrNGTA~UO z0hV)Qv})J9x8Rys`SxzW6-Kp?bnop(St?fM-bq-&#L-zg}jxACJuDIKK%nin8tCQ5m`Rrn|4@d#T?kw1!su zSVi%!J7M&_#Wl)dys2C0?m){J-8l|+IBe;xhsmI;_#b5tkvH}}t{l?z} zc6;xFTGtZZ^xNkbNpnpGWeZ`y{`RzGX1rhd@o`lo3U{VlB4ol)S=ZzG{Cdh8&pAeS zYa#27kjYs@d_A8e_u2rK#$S=jF_9V7N_?Z~MXLJHO4F?aot@Hcj4LB+E#Hq_QBi5d zB%T0iAFBmpb5;)dt*kkWLd|+I(Ou)Hv|H0LCXM1bWZQ4I3vEmOZFV}mJ7P;Tf0M>0 zymihkcZ<9Bk01XC_wyMewUC`RGc-#nqgB)a05qP`P91A4TvwMiL=(kjiBn2@mu2)3 zNkL3*Ek|tsql&E6ZU&gaOqppwZ*1+NMA(OxIGZmTh!A)&=t|ph;ADkv1jk*E!+GR; zByXHY*Fk(HtkR?QpQ;%GahB|Hs-b2GXft8Y=R{xOWH@vHz$55lG))5mUzFOsckV8l%}_wjz+PFwDo0d2jRcX@E2 zc2#BbzT=LJSl3cg?LDNcRe$Olx`Ay%#A)*RYhOg#L#wWd(jsT$BqZ380xfpMoKx-xKHmGG`e%m0t7Rz)!5er6V$+c z?yzxKm1t12ae}l;pyIs_XV5nPmw)p|z2`{mYdxPg-0o(osTr^|KA$hrfs*fYMz3x{ zVJ}y~u&H*;(?@K zuO-fr`!ZU#+dg1ZYE7f7-l5|odd_dH*=dHKs?KW9Y2vtobsWdkSNOW|U;em${=2`6 zw_BuS^|Ns}TEvxEDG`+HUVD^7%0Q=yYi&g)>huWV-e{QGxEx)g+R9m)1(`k{x*VrU zRnk2qIb|=UrHDWgtTB=37+iuT&ojyMr`2lDdJ!W#fHsc)9j}QXWG+$TeSzU7>s&SP zGQEGyy5R$H469D(q8Sn*W@D&mc1%;h=6=ktkFE*!C+8y-#rw(;S(+?;SLRvBwA zAGS9mZWHrB0(h32x%ETPU%7qtT5dMi$d{|5>g-|-!R@_imiO&z`D)RJN*2)sTfU+o zjSEAIz@sd#A09#dGiTnk(8g2u(b)MDsWyQv-L&()4~i5&`Rt z-B*iMMv7p@n%WI5%{qfxv{*t#>8LGK-O9_jI+z~+*E(p!86`9}awr^_vUB1u-=oN` z-M`~OL#}}bIp#4@!eq?ex6*J^A(z?1u#KC1EN*Ad43}o-;%K9tq(;e1a`|D5iTkuN z?l*Uv)Fl7D?}PLUFdDHB%`D{E4TR%k1bF@r=dZQ0k=d9KjZSA5)Og714ki|?b{F}Q&)k15Js!{}8R42JVFgle)8)uMaKg+Q z&<@Olb5y5|BH`pBoO!uD78g^!RPR>IEg}35Loi9&zvM$YprTJFJ})KmKz&jA%T7EUOi>**QQ&>U6JPIV?8E} z;v@-GGbb%#2B-2?6IV$Mhn{}hsT|*0pzfPZZ=5%wf+<^&Rn7faT)Y43)t9s)?b|mt zJ3HP(`L%%Z$3-_*+Fm733{eB86(z}Q2^7{yVFr7>d%I12tKQN_%*0^?+9OdRjCNg> zAtCs9PZI?!r+KjnL7HQ{WmZig?U?WX?eBkoXR5l{XRnIe>kb0fc5{o&tXjL5nCU#q zi!1KXlxdl*VedWqyZT;hP+-N@@^d(XD)qI&SI}3|4E3%Q%`qFh{LpM&uAB zCO3!@ho-l6oi5`l_OxZ3pL!$p#7BT;GL#wJZcAZ0q-wL#Y%)YkDD7a@*`z(|rN9a| zGo4h#AME%|usg+5RylYXuEopho7+MxDkfwQygvP%Rl z&=KNf?h$d{(q4-#+LkyntLi|xm$X;DOe_%@L1f(S>$n91vernW_8M}{3?`^R!^wgkrBIzXL}$e9f`Uf({s0!*ekqZ{|`sbj<3vngQ& z>oXJ*xRFEJn<}o6==a%!>p>aZE8+mw{xAON@5M86e4IE9#LqtP7_Z`>r>)LbDPzg%b^#ILON%gRgrtkeTIOoXA0LmZa`nZIkLx1%d`1|*ikI1s z4@Y}a0W?8)7+gN@_MXv3YIpB_FLJCO3+A5mS1&72EsAO%=UI#`=2?x5*>pb|^NoDHg*C5WBHDlxMZjs=N(aD6CJuk1wN-!+hXNXu;@>v~g4)oHo>MaLve0h*#6}nB8I}nBH%?L4o&%T8QG1-%ZfjbD4xr@cp z@kvG*6QQNw-{dB?*~9q5WgmOFmE1VZn#5<*4UllHYXW>JT+2fh0G6~HRnlZ=L{a?- zU~YK_24!1o9X^?6A>I~6X@t4OopGm$Xd$O0pckD;_u;M<&6ev+M?}7%Jgs$ORo$=G z^>_e?*Q;^+aYmJrQv$#H4qHwj;x=~_eXll9gmj652q4j&&0;`an&4$Gz;+k=kbBpg zw3;An?}}7?4Z74N?PfFKl55ed;wFmAu@(SC&Kdu`T}qO)lK9`LB9)xWh?yNXRw zH)JTXaqr4tCVh2ZiGr`LuT`HM-e+|@R63b-4}$(b{P9l$O7#abbJh+}4tw;0cDV2mAVALCSRZ#f2wiXOF%ZN1Mj>sr!qLvH6xZSM`ulv6C zB_f38-FMtyacA*bOI4ZE)>?PvTAmppg93L0@B0Sm%QW3uu4RU$g@MmIK0dCm=Y2=W zHz)4|@LBciD}ML!SXQ!m#56MEy7lD;Z7sdPbzO&$+gRY()r$F#<{cWH{c}`8W>v2> zA}Y}bx;I2f#apz>`bz%q{)_9^|MZ_=s{=Q159%^tN)H}+_GE)a#4AKslHTw;6{VIQ zjOXvn)|-{60ye9Chye%yPe8E0M^rbq&GYva>tI1dZY^`2R3xK}pHfDsr zuIu@H_CPygT^qo5*NQ*W^24|ruR%)$atgKTs-;J)p#?y+tT6K;aLhc-K*rZog#Enl zim=CnzUtM`ib&9i5P$#{G!Qzb9qYZRz*_ntg_0}9nnb^iP1`Y<9)Uws<%G*1Z@#TZ zMw@=nMY7TAwJ`fgkN~J^j)Kh9+P8fA`maBTP>; zuH{a%6dgvm$KEQwnnFKW<-T+9I}D!$65ucWeR6NFqS^9=&~7iRCerf0BZ2F&)O3SxH4Z6YnxCrjYHjS z#s*wlb$4ps2=LeE{pZiu$6l8~RyE>WiQeGNGPM2`S$dA?m+rr+%Z^PhF0ecyy{etm zWs&UO?8cT_^l}ygboZuG&_2IjKm64X|Afz9e*Q&-fA_QJkOkZs8a0hm0jnTCDB793Re2T#jpT&-!E$#=TcNYw*kKTH{aB ztN+LzK^Ku#%NHv5a?&zx=|WrPC_Uu06$dNA)|y;WZI?$7jJT;?z`gEIj{Cwxs(|~x zg<`umu{!SXF|Uvtr@%KYZ}ol6Y0@THqnipl7wzom1AFhEoGW^co0%?Q;p=XAx{B_$ zH+5*;>7H>TdupF;@o1<-Y3qcwe^&M`2;9-DDcpE%f^>(=?TBzMXswYtL{t51CH7-) z8E)_Ay~Mj}#{_E_7Y8R?ZCb*~G`*f!cqqjqbd0jR*AmMg?R6^?_fONfnWBNy|J%R* zgKGG_w^sS->8<4kyzZN3>b%6Dj7G}k%0wsk-YDD+D%o1jBAs8)8-STV?>lCoe@E=K zE9(`x_A1u%>s1|zDkAr8jFmlk6OvHw{?N906tlaw$l&%nx<-@oU@iapdOp5A3Y1t8 zilIYYem-A&uZa5H4-Wu$rkmgQO>y6hl#Md+msc40!*)sjPmQc7QJE39FE`rTFtC}( z!#IXEm+0A;D6Rm<5+0%wE8+WE)&|WyCCMm};BG(tcK=uZn;-7K{C$+0+Z_$Ijt_=w zejsVPC{XV>H1uwgcih8QXd_ukB>ZeCW>u(vw}?>64B5#ybk4OL=(0;RJL}SnV;7Zf zo@eX1{HfcHsz3@Fg4Bk+y{Xvvb^DU#Fe?LQkY`S0Y#Gxir2@Zd!CYWL;oDDW>O93tDVHnJu3^` z&8&u@Ozsm6vejE~L0;EZnY`9gF!g=K>(z*pCL(AyEtlK!iptP3NpVKfj)|QM3~7Cx zvb(;El0l(l(4*}PRp4GKeh<^ED*1Sjfbeo#whR&W!v~ht>o)T@wvdfZFsr8}ZI+}} zl!XG9lueF(y#X~fQz-UIPrw~o9&z7F0KFYtoVn?1#R;jifN{E5Y1z;~`5T7Q^brXo z{QvMze^0W%m)3jh=VmQ4TlTu+71=GFOaa}ETt@ACx?iDqpGDuESrP`^WoxVKOR91Xx<`B!KJWatmuuy;zsU&`jGKZg z#F-qs%h5#8JyLSTuFZ7f8yOhC@F_v6nPLSglFW{qhrJ?iU;ot)_4@kydV$7BoB|wx zz-6PQD$Z zD5kgE{7{J1T0^Rz(hj{0VPBq;lp5?i{!`=Z)1BPmAWz3RQ}H1@j>&1WiB;3+wT(&W zo0xaVbB9UVQJX^}v5_dfAJDsP8g5@RABbh#y?z;f9WlhoT$0!TBtgct$zV~&BC3cCkOlHKouA?Kp z?|bcKzT$qVFYHqtg=%IHCDd(#-N+gdS2zg#cH*6LNZutITJ zMdj;Ol<3usG_ra^h}nG9OnHm1FRq2idPR@ps%$OACG>V}Z@!8X5%48qa^K7zyt1;e z7D&;|5Y6Z)5lBT3Fvm3CC0_^ABe3y(Cd5(`phOqDneA)b6b*c@WL++<2&{irqt^f3 zAOEQSt{u)Idu6nEFaJ7}W%^+Cf5n5Z$8c`MURZ^L4+1K1n`{q&8b;A>#i> z)t@)nmSoqR*q+?ay%8^K1VE@kh*hWw1%eHTY>JYsWzzTm0NpaSwRMvX5(BGHRhju- z#5ukP{js*6$OdUJppf~78|V7jzV=$bl{PIR=NOe~tYhl6{BayY5`oFBL*97eo1}5M zGMkEL;LV57$}mM|cI=i+F?&BRQKlp{j;x4W;Zy~9H2lIOm5q}QRxuH5*jU9q)cOth zo_P^JhaAf8QQP88#i{`F!;SP^BP~-Ax+k{Md0PPP&@+}WvTxx8vQIXVoi@97Pt%!W z*q5tbKON)k59h{(;`i(rt zJ5PZvd`177FwvW+s%FNW;4o3uu5N*yM)qZrRpzJ?(eF|pS!69Y6ip2+Z+Jz| zJfh63%*Etc?cZY90GQFkR9RKssZ~V6=e4pps?aaTLK!zJ2W{?|QP1arPk5TCN+k*L zoaR?Zvwi!Xt13}-97BXXa(TGNag6I)!0Hu~sDP6aUTQkbIwP(U9ymx9Wt~K2@`v|a zk>7h5KA(0O7coo!2YXnlzQXqe-44IqahZEt9zGSefmg93ljhT+;|8vxBaE7*D>c$h@!*`u3I z4uI;tZEMCO{e=Rj1rkh?}!dynn=;>AEuz1lIhWmT$~n(_UzEn=ov3tNyp7oaX#n_6coV^jnWoWDszvH5c`Qg5wMKhj z@a){l$@`kNZ>Y+`f7<$L?(17j~y6?x|SwaiQ)?(`gi5FefAGHbfZaTt14+2mX>W0DGo zr)RS0c`Xm0$8=BF3}ZzVJ)V!o9bls(8WHfvLq~f&#(Ayx>k^U36ykL)F}j#76OJk&LI(#P6dk&CaPC)yFhEo~G<}2TdwjtWoUlfK zx0hYINLi(Z?*vsasZ3?8K zG^MBV(Jqb}LWupsxW%;VL7A}V!>w@ zV@~ic)C8*z?oXQgMrG=&4{plV*%1QhUL+#K>Qt2?Wf&gCTMxu<&9^sk|KLOw)C(I{ z-TreMADO9Qtu1jtNh``?;&jIXqU{`GP#aX4B8WwXwJQ`3`3)mwk#dJI3Gk!3L(%PL zsA8@REV3{}kuX)V4H{N!kr~;@#(AM z;a@H%_)aqN@i15=WYep0c;SSFO?tn~TRrKt0^gO499fkqqK#>X&@bFOBNxe+$zI6OvhM3j3{@ z7dW?2CKf3$tBOw?DN3PedC{xmi}YI`k zEU3AH&93nmB*G2d??th8s3yVwmDXyq(gS}BXjx$3A=bZ_nWYAYT+;6FWf3zCUv0U= zi9lUuk5^_ATNj-K5sS(g)-fFXv=Ur-(RRvX&D$93j1)0MlaZ9cb9$k;wy_{@= zxEKcN+|7ilR9V-B3?5aXI=5fA%-dFz3%2)y(>EkS=QJ`Hc3}u99mL z;cxCh^$gyZvP}Jd{_59(awFXRqsADfO3dM&MZ%&iW7vw|qL=3bUU%rk(YKoq#ZpbL za{LpTz@K|*0Vdr{vYm2gtt9u!CyGAObFx8@m5yu25y@(f+ za7JecWjBK`Gd$+tzTy~y;GpfJ=)yBKjs3zf7T*2`0_1leIz)|4SG5k{Ajq&&CjCjO zVt)wjm_VuJ{)~I)DnK5S>^VrVdg(pQ-D|{|J8%f!Dmr=bQGA&CYvV;_iy`g4V=7~| zE>ht`Wk^kJA~!mhtE*uwb~q?AMcP!u+gBTrgh!pRpsLCu-gqe{l>YR-fjcOVKfey^ zmUIlH%tRFKpd;->Wsh}u8>#hnQLTfE_Z(drey#C1WGK~#IbEP6B+g2+ukQDpkkF%$}h3Y%~-t29;&QzUd=)w-d~RicAUDT6ZEjG(ZAG!0Qgq*Pdx zsLErnnz!GH(XG+_vtr}^;ZY^4v5tM&$Fn8yhwP?DrA>g~PM2)N9pnmpAjF%9yQvMm z8SmI3{OwgmrXO3LB2ukL+LlE955M{~0g}*tj#x)vpT{EX9yUfsRF#=V$DA)g^O*8W^f-1{ZJ9U^#3 z!#CTsrtZuZW;&zlIR5CRuFqfW7;%M&cm#e%ydx9$)dYB^u9)H?W>k^ozP-0Wp>Qs= zLV9}~@NCg{MpP^i_P4b2toM#fu(KsBRNG3?FsA+R2b9Nt8|}O}-`^$tFlzIb^t_8DN?NUwSBc> zU*3W>5=q4y6I;u5orpq7t?xY9!m`a2Vmj3K5BCB%cU=qO^`EQ!Tz zd#jaLD4gImP-`bO3yF2^yd~74Vm8BznMEp{%|KZGn1w}2_QYV?CJkVnyDLkG!HeI?-rr6VN`%&|2~GX37P+p~c{Y>tTCq}u$F%EQ zV;F>^j#?0(@0Xg|Fx~F7KfJk!eD5(3P`sPwx%zuZ_j;*W$#tu!NE;#%031x8-3j18 z>y@;H`QpMN0%MvffAcv${n4lI)#oqYoPA27axf99?BnY@M!h!qjTy=e55#b+2`}%_Q2g1K+m1 z4{}<21c+#eJd~Ad2puNVFwrD5jehc#hv}H(I}?4s6RVPW-*=0!gg}Hxv@J8W;Q&Zi zca-OYuToj)+uqp4sxgNMcG`5E7cq@7!o7QZGVvoc&_vh5=cQw$CllEmSc!uh;_i6A z;mij?qPUsZy!u8$7``U=E^v{`dq{REwVgdI<5#}W*xbl zd3)GJ$#m<{#b&55fZwch7`&zG`(L?lejjF#o8`S{e7y1zn5feK@XKFwzswz{3vg-DGgEKId!dE63Je)A zu@l))2LY=Hpjc)eI-=_Rx`x@hyrdJKo?&LYgjA9!qmE-lCZWB%u@5dHp6Qu>CBEBc z#>5fmj6mCqc!V<9RF7C5VAt7DRXMLEss?K7;oo^zbOGEAxVgZ`aw?1sT-ZEz1xZ4qYia5&sNf*NH(ZEo70 zho%D%*%+0TmHk@IQju@o;*bC2sh__=!5y6G*$c+%ZB&+g$Kjy6_y$+MKs{2Xs@%9D z-46;2yStiI7Ip^J>cKv_d*Q{R0?9Zc5Rco|6yR+Gf_OJK?sAs<5LPU~YtObteKYG9 z3Z%lmn`7cvADs1Q5}=;9D^&&K zEqARNt7=h&2Na3+rDWC=**)qK?A5*&P8~Z2{I3}D zY>r*~&324}>B^%bDM?gRwZiX7v4dQ-FF0mQmCMDf6A~hIpEtL|CEJk1JEkOKvMNQu z-GRU%VN+#>{4`WH+DvV%NJykt6R04-Pdr_Qkt!D9J;HV-OZtzE8~_VA)VOyQQ~AR9 z&)`1PwoLS^>du<=Qny)h=r3o1-hFQxDdW~ah)Mtb&;Jq*C0uZ-C^GgPqM8r6K?#U) zFAq^!E9PTlrm1FB5sfj18Ekqum~@D*Amzx40*P2uI5HSx0JA8+ofkX>9`Tri3L*@F zur!AeQOC!*F{)mi?WKqClcae_WLjAmldB58^UP%Mme#2FmpsuPjYQeVBy}*-#fw1 zr@)%B5y@)3?WI-^x2RfNB*dZ`E8fNa^hcw<`I>n|1S)(Q3Wdd=wA})^Q>dJZ>s9}D z#EileaM990aHt}pF$}G-_{@n&b<-P&vl4J4aQLW-tF%Ye;@x>BT|dCp)@EQ}LH*tq zAYqu;ZpEN);#;n7^J(SX3)Nyo`&;w^C6hswpF|J;IY_Y6QK)z=O1LOzwF(@UW?fno*Ts z3vVF3U0W=ET|FqMZG?JH2_j;{aJgC7LXE*gD@*6-zxc52s3A7hIasvPu_WxrsqYrs zP|3tIA@c}*M+MJnX(!RWD8OQA&#unP=B1`T;bdJt_XSGd-a>e zQSR-kk|uHQYc3&|NR@r%q7TIDuA}AF(##M)F5-fi&deD^z$0|&j{Xln|0OYBYspUH zW))@g-f)o1cB^iHB{OjmujQ)PD;mN`9+^myD6i!e=$3D(MOa{)164qX*Ye|WoaZ&i zPyq>q)@mm&%7-$`RC|jp0_nPnGUIZ`b}IY8>LYrSS(+N&F{wy^PZ4uiRS7hPZj9InqUYVJ& zCnhei=8Yw5LvKcE8(r!(FoiaE-5?-yiSiAHL8%DOb&@S(M)MK6h;7E}Z}H z*d>~0Z+B*qszfGMxV$~cB;qU7q>HlPwa*jY>&2k=*Ay@>>U|F@cV_=&cBZ#RCn-)3K*!adZLm&m%%`l9dFqd zX|U<4(N{#v4B%OGl7W#~jb+`TkaskMDiL@yfz}Eh4rtaD9pJmIsKjGX=PjO*gyy9<-6S zQc#JaE6!oIFya@COy6!!u^ z+8OT3-nZ8zS8$nCdP{EV4XJ4FtPk zI)+iEslL5kW7sf7$v17KfVfZLF-An-N)A&OQ$wyvIHJawnHfdHP!Ysxx;%>S$OgIl zF-H+C4?s;TNr%B0JLeE)zoLjdbGr{0{C3y1#3ZA@&Fg4a)#dqZ)Z5C}sS0WYjBZ;Q ztc}WqL5jBBqBRYwJE2~Ccx=j=w47k3)e{O5G)z@v*dM-r|KXqi@HIXUvvtN8(l-Sx zYELNcV%iujxi_+DVU^M%ki_IqKs1AM=sU_3Sa@P6~wi$a$pKi9ASY6DRP() zx4i^+VO&ijQkB~tP{fUdmaH_@VTMAVE$qVn(dzD2rFp38F8P*SCP4cWYvEY#mOmh< zx;G2w6jK}2aFnAvW^2@_OQ@Z%%srvwWKN74TT1S>^~^oKkqG4wekdE$5E{xtskOywJrlw!=!B3Dy(ba#?T!iDwTyczs#)4=kpnBK^4RUQ&hP3 z!0#N9s6Z-)k5kakf-P1L?^Qa?9!IXoNMW&Q$W>vo7B!tSJYuao%@bEM8xS{R6ucHO z_4urhhm2Wk5o%lK(O9@{h8_9-jtLoFTb2%dZ(&*(03$GVGRZ^cQd>C2J!tWoUXZu}4Of2*00;g~k}`a-2kSn3*OOtaT9i;AJA=DHHSsi z6>j*EsCe|mgo-Q-fY9~MYkhgU<{ZTJey({;Up}U)$gqK>R(OnInHe5x#zpX>swsdi zyg`ymjAHUkQq{-fNOYpBvG6cUmTxbAImTg}kslPNVzH*D6O-Av*LrvOjcMz}cuF?* zo`D3~LARzA7p_Eso~1v0jz9g`5A!$wY-U_VN$!bf4C5`Bd{ENS_b4JT&$T%O>gsN` zW!W==N<`O*!lTyeZRByx+B!+)wmCQai1zbMX8rEpL-tOLz*60hW4D6#AjAxBb!6gd zv|XrsynyooN&7=mp2{TZyeBRYLKr zCZRKvlsl!fSWoCM7d8lm86LJfqFT}a@-2>k3~_hDM~H|b^?&%;FPFQh-Y#@iZEq1X z6W>l}GgX$zVyd06AQdVQT?+&>+@Vy*Sj!)e1K&0?zV=@`+)4`gO6yw+}5&8n>D%aK**={B_O{UQ++ znXJs&fhg^24~Yz75!LHjW}1;>=rm!XmEjR{3^k3SwU(L`iD6*$yw+l~&z=$WIt_XT zb61%G>}FEVxVOBWsmYzSrM-24e#*k@tc&!I{%H6=e?5)^lU?<< z0^}Nn1_hD9`cGUUtSaG&qfMZk?GExbdt|9Fcn&z)pcD&pM%^Z8sAp({#`Q^5B1>u? z*VLlQZfq=Rmjyy19nVvdK(mkRMy}eb#g;7)`2%Paq6pNvs=gxZ|G1Ca8{`f1Nzs1! zVf4l~s$HPFC}%r3woSNaaO^#4(^Cui!!}T+Pz{lx%$e8|PDA&24Mmcf@%gJrQsVDb zi9j~i6=WhL{pwa#+;Jfp;XtuzROHJ6Dn%Sa`!u0BC&UhjWZ2i~FbGG8l6G1tSv8O1Xs#2TaluwYf&Xq-GCUw1YO}WXGKu7qFP9u$dCZpciGJ zJDgjFhOGjsG{Y;|;4uuXqJl0;d<%LLOMURMa8?RQQQ3r~)`vSB8v5Y{tnFATH6`MV z9K(84*=h1-s_;j`lZb>wL|D0^J0zu>U~ouj8*F;J+1U-*U)S3Cnk^SGknFjs+thf# z5*A}N=0nAVk0FntOf?Mw+(L9vZ{em2?oXoJvoeYDG(+Js5sf(kfabji4-aIrRMo7x zpj5r`!SM>sotw%nHYWtGsDy3&czIx%Lw!Q1s$A!V0YXgeG1qy;wZ<_p;m^nHA_3K` zisd?>q6F<`vHgtkPFohwNmRL(&W6*AXc4oS46AElQ$$2!1D17)%!s~hxKk#ZGQQW; ziF@$#@Z{-nbeXpq5eUwk(6Xn8iV&8{U}176Ujj!5BEL2&obYQ_k%FchL`(=?^PSGw zysh2#!)A4Nso*`)@j@wIOhsy~MBh{=s4=Ey>o7jA;?8PwFw$WYZ4 zuEcOA#N}$jMX)qP(NRCo@h;yD%D%N~EDMEH3eUsn9?%lZ18 z4*;03q@rdxzyU2H&Avd4ECR|@ZZHs@QG{GfbOS_*h0{B*hcS;;e*gRHNB`F!J>UNj zZP6 ziA(KF;0BX5!wc8sE~dc)$IP7Nd!pRZG7*74-WP)E^z$|yV@+2($GiDKLq0Wp@6SI5w&Z;7+kclHAqgh?)K}D#xVxsWC-^k_& zcidgnV5&pEiQt%+$2L`JC4kh(Ky5M5RveZ#{q+c zjDc-A3(G!CYb~UGFfo*`@<24Vx8;wQs^mFqEn%iM7jh;>W}`%8M!(~~0f>sxkkZQr zvw{SX$xJ*9xDY(a1^!q66nfh%I9RK&Nv(XB#s!(Sey8croN^! z**4_X&P`NSRT{Jou-_^#cl1mXkqyHr2bG`h1R)U`$BSqj^IEI)ju&v0XVn;Xp4aj? z9uo`e9wo2=Pw=%VcEc7`Wt5q|Enh3rGLEn&mm zQB*LfJ)UE^ql7|CzCGQuJ{?AtuxfA<45T-H%1PCc!aGkVoApXm+)&WW8xY(nG7N5a zB7{Z0`Ii3Vmw%$a{|yBT7iX!85)*Yeu(D<7u1N-nd=H>iG#|4I=(OPws+MrA_0w9a z;tSD1kp4T<6`871#1%o^HF~=X8L3e`{jmB45(k0{RY@;|?$IDdNnvnin#Zil@C^5=QX9lnV7#F_2{G|?VYIg~0PJR#^?otKSSA@^$3X;m zi?9f()LKQP!~E^yA~Ds+Bi>KQ$|zguch2(mYiF=B&x4jz1QDA3idgtK^8I9Z-8@K` z=WJ+31P>D(dA?(D*Xb1m2yV=J6^+3vwHAre7=##z8R@`2kkZ>VA$qSCc(WN`J852E z_LNf@P*M7cl)aq5mU5Se)&7Gqa@y1)GjFUd59^zB6%voo&I%ojEc)+$_LmSVRg{=l zC&bt@*IHF1qLCpgTaju+W~%@qm6guLXLcC{abX2T6E5j;W^S+hXBdU0KnNDCNph zW>rOjTSUCRJcij?A*vH~mclxem?Oei9*_C?%R63waE!9jbuM@kjycckGBaWklXH1R z&SS3Sz{oMnTh>MVI&^NG0j9vdOc#l8WT5uAJ=dzuCN;x77q)2X`oKP>;g$(hz!5P^ zto%aqCx7+>{^#GwF*DLOo*u*YdATr~us0%z-2;G767q0P^c*VUr>G$>_}((d9MFhx zY;{_MDm*en?IVtTudVUwh31it?iIm7+)pcPc94fcafX_G#j42q z*LF5y3g{Cyi-ff2blFSFj@!|;*5Ip6{U5fh-5Vy6@aP5pzEif53!$FOO=M*z8X{ng zPNBzOWl}CP^r_MTOeCYZf*oOlLbh*=h_Dza492V$y&6^S^|uz(kKZMI-|w z3=bPHGvOqHz(V`xOGH_5%-M;C>X{-cG2LMJ0_AcJLo`c7hMKA*^^l^XrfRC^`%?X2 zgl8milREDG(s?aX%@ys_q*`kYGuoHL8`l{HPNS%*uh=;dCXvV-W_~&M#juu#sf_)& zM<$ExW0%AG-B(qQ$1o9Jp2AA3X3JwmJRT2U;T|?LtBZ3olT@Hghsy81Trbl;4c*(D z-2y^{3MD*P2HPKPxYxEc(G6<`n2`&`>O>@HL?$9G8T$D*-^QnxKlziF^B@1I#B5^O zo8y=jWu|>E76H*lchi7-pva1vc@(&CY$pR@!o^{V$O&HB8M(2dTNq&=3c!K@y^|o@ z0p14j8XtI8wj*&1^>EtZx86^VMkXe7=)J(P5|glQXWmY#H5CMyx%U5YgC+|2+x}Oe zFcj7RJ6HCd%x8NBdhns`RN-y%+rrD8lAtMdAmp1F?ZhT>Nkj~0 zA*@H0X{NZB;5(=)+*{eW=}uHcl;fDP1^A2>Ln5MmP%Tjgrjq%1T<59HX=zsw(2o5# zGUX~UvoR|IZz%ygsT~eNqIL|fq9~%WIjTyO!6sot>>$;^ort0`v#gXccpqb8TG0Uh zN`lbb4kb~3dE&w9^Eb2BC1zaBy^(orBao5^QOcNA0CmzfEfx<$4pie$CcRhYL0rLi-thHA$K%x+F8!^?u{3KS6{ z%4**3P!iI={rNB9#Ehs)G(So2aEMp)?-khJyF>?Dtj@D_)hx!zfv>TtqOu8)_=v>o z9^cF&TEL33it@S^KDl8gEd`b+n;AM;Ooi3Jpi+_L{&)<^qO9YXiQ5@cZN#FyQ4e&v zR~B#U_YS0CJ=H?6BZ-np%$Iv_fr~P0&T*a>6b5C|Ve53JvSAxpOxJQ2VVKXQuGMg6 z5k8)iJ6|9iHIMoAiirIFbF394UFi-r4GzUycEx^WL-ccdbbE+IzCt!kZQDp8jg}qu z&;R(l=fC=wKbTIRztP8$9&_q-Ivx@nxW0TlZ<)BRYe@%LxmTfW1?fm4ehXL+ZxM^T z@wy7s$}GwR)VPy~m3-q&wOd^8`MRife2r`Q+V2e9zS?$Gk?tf)W+|X%d)THO;y`*O>tnyCEGB$3EQ#je zDGcWvRF01)ocVYjKr9f|urX9jt+P74%tjcB;3%!EF^rf*)y7D;KDLP%3eq$W8Qq0ml^>>&$aP(?WT+X7AYsTw;W0lwug`DgZfKGNT8)TA zbXYFuU5+KQu}XdS&Sc{3F;$xML0k+P>4nSH0&=`1Ob-B(!DLXYT=kw5w(thi!k&J)e7gTRxprQ zp^t(jya_tTBn(p^^-Ea#<;TK^+w3eBe)!dWx2(VZ^PkRRa!>snm4J{zG&rwi%`+_N zKLLr5AQWHJo#c15v1o$^7$yAD2S(O;>9}n0|#=2J9^|g5vW_>R=W~AGjXo{7ZX;v`_ zinUh6nA6d0U1+&gNHmohl`3qebI$Ors;>Sr3wKOyU$1!gd_8Qi!p{fF0QJ1rSBD#U zh53=%$d2u1S6e~+`DjSR*rGyJs`k78`nwi6}R?pTC zAQrbLwCj2?=(WHqDnw|mD}0AD6Vv=2MOjrMoLVaAk<+6m%{w05(Gu!_J5YAWt!c;2 zIjgPT+n~`yjBJ{A!m`+hra*qa!z{HA(A!r4{sYeLw)fxXY`4I0bqNmLskwH}Ar(%E zm--#C1M8#b*{-D*6*A$WJivoiq}y0TOlX%yB{AzzCcYi$%yb(q)rPNy`(I|L=unMV z2yI3L=v2%=JcG@j5YvIm!fDH*I&@A;4|G|Dmkr}eL^EW!$F_@6nrd4aY8v6)u8`QA zrP`+dzyYe=Hp$ovBXM3OkXBVFD~kldMc@5WRrd0fX>Gw?i-Z*-;n2z`n2wi|Dt=CQ zyH`Yc3Ng=tkZOCAiAcDXGgbS_Hb@7r=cXJm*II0Y?$D5wQ)r z!7{XCff$L<36Lp7F={Oq)_?cQzk&*h zedCHK+{IMG6Om9RQsu<@0`_8XbE0-YfIad!j@Fdn%v0{&aUm|uMbbpULM<`~QZGaM8^Ty9~qQqw|9 zgKBs%(||oyM3|QcbV1DQDts+}924m;HmRaHtccUob|v;4#zcNCF*z@1lNAYus)(5C zaU2)X=as`$61zAR5!^hO+i6!!(HT(X#!XPmFDO)|0`GBOt~<)1y9r4|{@dUG{>T6Q zFZFl-L>^g1Q2?uqArZ+Yly%?V^0q(hHa0Xj;$g<$4pUFGAkd;maWf13T1`sr*&0gV zF8w5igB}>8=79j&!94o*u#av+Rhz&`jVXUy{R`hL>F;g_l3TRi!s6Cu8|6_uqm1Gx zBu$vOla^J&ibnM&Glep#>l$n{2*C+B;HceEXpPrSuE+=Li zgIR1S6_fipYE$0l%q$)X%iYWJXn~~5Q6fUYfN5RXC>zxN6&b>-dVYNOb zGb(~i`$kGcs4?Pg^B7he&9lbB!I7%Ft6)kk#F8Zl)v|wb^GL7+=LlGeM8pUP1UHL zLLLD?fQ-b^pWFWz5g?|BT6ui)&F4S*tDnZ-{};v0hKQMSxeoP}%A}?q5xtodLRK4z z57W4l#F5z7WrT`uesQ;!1i)3oMWyYzXre6dX@d5>{tkddX4W0OX*DC8Ylu^|1(E(3 zh;9Zvf1HTD?&GG}NEAMh*p-8vRN%A#1K;03;;NJ$i0YoT8@N=YJ3j-)>86Cptx4uR z_-v;(nh>Ul2_4Eq3IjDzD5j!<2oF`|z3^~%>B3hm3zMs`#D*b?ifkzKS%p8hvub!H zck8t;r=oex$WZQyA(q#Nf!(PP;t#=E$d}E5_YDpKyionTi0OEJ?$1G(c+BbR!e^8o z_yFhg2{386>liE=zABS%jKMHw5;J=~8APZEkmvHe@T^qeoD~+~W3oz}ClSdpxhQ>! z87BV5^V-0Yr&kCOq$Z~2K%}+>XoEfm*&xuX&6*}kL>XkW5AQKbmB$nl3deP|DmjZX zMEcx^8=`+-)56BD<%kv~?kihmQhr2OA5WTIae81K(%dB&G7pbt=P#`pMrihS7;`=#Fh$%0oZM7pfIb)l9lmrajqB-z zNJJtBTvb`gB8q)C=INxy+jZ4!wRX?j?IZW*fSJm`VW9RtLVC1z9YDPlbgA$j;5HhcECpSXj}(A|#ABdvd{m*4UV;!_H5CrbJYT zbW1}fF?N>?16d4#7#rQrj31*QxG1De@yC^=}?<*J6>r)S}s> zO3ZW)+Iu!e&N~8J(|A8CO2&{mD3V8KK!{n?5E3O}iWImV)F_hH;yDWXZvG78Hiw$~H1(tYQHO&hWZnQ^uo2lqw&+n@g`V3wvnt$34|I_cXr zE7kt#=G5#;C+3ViJED!sN8|%{X(^G~z*?s_7{tq6n8zH~6(&+lrm~jr=1n1CGjoqQ zEh@Xtl8Bi+(?rHVpFo9&nw-m->G?c-1xOM$RG6{NBIUbego^Noi6|rhPj}GzQMY<6 zj|hbihWc-mn?e#EpT2ti{N)W`5Fzo1Vu_3~EIfyanfZ$2d3a=aj%mVD;ZzLt7tliC zA)?~l(pMU#p ze){yofAPBh@t^F|>so6J?K%im>^DZ~JTWu#}$W2(wJFJdyZEn~A^82qIhbt<_!845OUz7$#=5Plw1HbHrM{77Negk+D=Q-PP30+!y@Sl&ND76B55v8%|FjL*mq~7nLM9NgxS~dnQWp^4{hq|Y*h-w!C$gN$JYM%wrH_Q}U>A=L5 zsVeR{#_-Jd%W=_u9K+PE73b;P0~NuyN6AH{hHqe5JG|JJi;8zR4es9T2Cd4hQs!$d zQ&T2Hz$r_Z=P?nS1;3w*lWQ%n%y}HUah1*N%lkRzz}?qZphl=N$FTG`hI;t%JPPsv zk3ew0BC!o6il)2)v#O#(ogoxizka zqvAbxixAX%Ebblx^g%K(wpE*7*C0F=Zctp`v4E4_w^`a~X$Osz{2jYH#0LwrlF1x{Lc=@mJP$}A-s#|=q~2yEgakd!pf?e z-kwED_;4}IfzV&+QTyHv+$g^Lb538L5#f zrgc2pREB|6*MD{EcpemL7#E~b!Ae?^Ci!B9TT5}*aYE|&XY5U!D(%wxm3Yy zWHrne{bsEMSPuVWMac7&sK}ShS>f;}G_nKZ^F0#6>jHxZZGH>R)(9pdo7L`prrK5w zrVh)fQf9gn0Mo>jS(()It{{dy-CYDsTYzIBX8qSc`MK=&Xl1xK+H)o<9w`bpq0(+F z1#hLzYNj5}M5?ByzJi;14^?`PGyUX9bsSEBwn18_2Nfo!@(2K?s0!KWA-a5FFhsCo zd1S;m1{V(%Q3kcynvz2qp(1$pzS{y9RdWvLssO={F>pmyQzb@$o_h%M7~}0cK^nrX z*M?SPRhXKPsPbB_L%CR#hZ! z_Y4otoKw#)moS66ARA7Epoon@+u@K^v1L@ct$xgbfu+j^@N%e@bcws^ZKvq6_%1NS z^iWAS26~9*s;Z`Nb~-G-L*&>9Dx%z+{;INm81i1x-=CbcP|#Y+{ybs3-qp>Ge(vO6 z-Rsjt3*9cK+wN>8(k@`zk9d(Oo77=4jg1MIOqNpF*OKkt8fM(6zYe?3WBBDgo>Pf( z!`2{$XWTd>y!nVpRp%HPKIYU7OE_L0P`7Uk>0SnuncMQKyIa2JDbk1ENGWKS;4O}Oq_ zrqsXw$O1?P~Z;}p?4!IDlv@?8$jT*bZLWuDc+F>xzCP1lZi+q zBFxl1b&Jno`uC$f(cpT?-tq4_C5iszsYw6I{iO0Irw-J?9Yq9o;73tb`#F z(-1le;PJ5bD%o%U^FROiZ~o%AzO65(9MfJ#MI0{&W(rfqV5wC2sFAqA2LI02Nw=Ah znA|RD;ccZr+hK-?TH8??B>?@n6HFqU1c=fOUwn+NJ-MJ~1ph8NnV8qLuv)s}Q? zlgM_bV|u&?`RZcOd%wPqV-d+p-o{l`1!<7!aoBATJ+`1x8`RaMP%Dope(p*sJ6oipt zr(1g$or!Xmc8S2QgR&$L%5J|=N7nem%*Ukv{a^fwnLV2Mi5fgr#ik2SgQ|La?Bqsq zt2IEFnTP4+G3PW9Q^_J#fWsj}gBDrtt}OQ`k(p+aL=g!gWp&F`scIysL!i9Zn6@rA z6&w8WFDybz%RfIXE%OWxO+v_4uC?1pyN0) zG9pY3k_+BQ+C*#0n?gif+hW0R)b(swqAH9^7nJf4DmHFRd1nVdPIe$~w-&S^>1rkj zZ8LF$KietAxN_dUy8UVIKqBFz&AaAB0I+Qi*DX=(R;D!7uJJaR`2Rz=Xbd}swsW@V z|74`=n?&io(9EnRkdTO0WneAF``ZM=ICtQMh<5mbiO6iUQB_r#BOuHre!Ow>q) zRLLE14`wb(IQbA=NfxG(30IK~xWrVvpGpWc-qZ*&|JPu#JBIm%Kkd`|W=@q5IA7)haR`s^x z`!5IAj&~DvqpmPsq5B)oJA#zB!$!CXD;}lnZ*qjgcU32S{kyOK_;3Ct|MTx?l~{Xk z#>*ip@85ngQ(^HH_;b%`&=%ChJ>~(&WYsQWIx9ZT&6MCNKUqp3Y%s5({dCMR*E zmWf~of@z1DgEprEf4N)MAmFXzLqV=WmKT%ABawd6XC%L;u zuNTcd_*3sK@ns8dq)RW~tG`qj^VNij!_$Vs!TRy(wZkN=i)U6&qhYt zP}v-hVY;p~Rn%TE-p9k-gR7>^$bj>oh$xboE9%|jeZ^OYp=i7_%3I-;yCUzyi3>jEceorJe&fi~e=lbKn`ZNCRZBv@uV zIh2R-VPafDBq~vPOals?h)kqkRNMf5X7Uw66&ZbfCPJ@UW(_k_%?!^(j%|jnKBH+W35&04HjqS2fC@E`h35IS85zgRuShq zMTb0JDi&{B4x8bZqA|K7=epWnd7E;0)8kadkeDxkLs>PW(|v6zfW{KRnSB4Gp@732 zoxxS4Mu`8h+-e%&nW$)?Zj42Ij|nUesM0$$Tf%%#@e!1%fAbf=YQunPCV52eTeF!4 zf_mFsioaucun0z#)=(P%-%m5x5wK^>IpC=XhnYz{!ecMQg`6rJ&l$NRTH0OKlr>b3 z=Unbg3~U(K9cCI)Fh1k@i3xGD+I!26a{-6N9Of%(xBlSI83E5ovG69H=jFfEApbCIe{6>$u_od{das$D2Y*ni*)4q2lDBXciZ zQ9qcKx(n$Ff-^{}+|RRg5`}R-d`v)4emY@KCNh(39yNV)$se_2?TAWC?AtwUb?xpZ6!g(+^&_qMz}joSU&QknLLk*#in9|P_Tmi zX9my7Th!WSB4Hcc(Arj$F_rR$D0W*Q{=>?h3la|R{7V&OkPd2SU zkH}&#`tFDF12xA?)GQ1{Vwgz(>LU_+{zf<%*cK1qX8mP8wpt` ztH4Mz6^xKTE?^anQcK*9p+-k!AIe!UOQOUK->4+=<&8`rE-GJ(1-m8$9Z`7Nb*LVJ zzmBPzvDfG?dRrS4>1Eq6a z%hA0RiP|^bGQ#%PK(wkN&Hz95!>vqIG{(38^$+r+Pv8H^5B-1tyYc#>kB6C1keKo? z5r#KpOw(cG7?la@_k4_8uByq@W3_a6sU?VP=n>N3UO@_*|oy$PQ8Qgw2NT&o4U1H#G0`?k=wgLJCum8@6N7dW>F3s8@p4T zo!P|KeWX)yagjhd6n;!mN|jP(6;h#wxpps2g-1q+;KnUP1&4+-4vy|=ua_;{n@_W5 z58_)k=D?ChhneZRFv*HXT7y1n93!hX4W?|)aED?8t$Dt_pc4!9L^X@^QWZ73-p?_Q z@$y1Mex4*ezxt{&bFGZfE@>sv5!ZWq=<}J&<9x^Mii&D2`}E4HwU(F?F`3ak2;2-9 zMND}NV#@b-DEhUW=cGs)gKf}xqJxshNZc^gDnjlflBuqX9}gxfUnIg~kP2mR5vj3F zQh|XQw|}&Kw*kX;I=m8xa<|ip6r=b``ox|GN;_F_-;LfI!$86;!x)&0-7(AKd)~zHhW`;?*NWN*c0v${!S{i3l97*NUY^VxXi*}b!wN)NC`*EHH85|~yFOue zm(dRmMJdREAmV$m@LH?j(a8dRd%4d!fd&;}F*)DQ<1vS5L`e4^hYekxgxKDuJ&JH7 z5tB!_$8ii5_9#&fkC(@>mM2pcjbW5^txyqn7ZVX$mz#~Ex~?^j3EQ~|qq#XEgQ{KR zW?#17V#6#3mV?vchL&{xL$F%S?m2hX`ISrS#Owf(>x|S%iM;&q_qM>b=LQSNoAc()&9EeNr ztl)>|ly`&=_O&hEGgW>(Fil}6B%#{1jT&&lkarKa8;8`@8o1f+4B{P#Ea))AcT z*1=d+B-HLP3d40ZWuR_eAGPL4$IyrAkTPM+7^qO$sD47VpsC{8$}Bbv)>USbjV<2B z0dB_&G2o8A9DZa`HMPo+P0Q(TfeKU2V?v`dQ!`DE%n;(pdORkh?<~V?h?$=Y<3cQ# zVLC=Ew>c|vJSIK~W~K;Yo}a#2pFeX?ns{*t56kyU=Y)z<9+fGN2dlt((X+WoU8|Vw zapZXdAx)-4T<`C6EuKay6v1k&n&-Q`exl5}E*TRv6PZUZ%#rHu;4Ei`90yksYICW` zj1h-nCe8*1fZWRs2!+&zga~G2NM4xRVESykAq*b`XF)~u}{-HwPqaAPj3^sj#M3&l()LPFPa?vg!6uo&kWeqH#z+#5H;dv@~W z!^}+GLqt42DEy<*)YxYBvWJC-p>>!@#a93XgR*h!1~3-lp-NQ|5t(M2WmAOxo|!%7 zfB}n$#B441$Z4=dD{aqdZoFj?CUMeXnbpNw5g3swavp<=)^cKgJ6CZ5YJ?OS0_6zc z(%zxZF^*v#=@F{3Ry?0aMXL1y>YmIz@Wx`t%uhp?M^hqux`G2b?-+L9yQ?x2pL0a0DD=lIg|I#zzy1IH{rt(FegDTV z@n8Sm9uGMNzaDDp%W25<%{hj&#X?9`$I$W+;q;;m9j43!hR2?H`;Jmnl%>NWqNyE8 zc!ImAa6go|@QX@sj63~9+cf+!-?lepb2I2J#_bz%xo@2W@>#VQfeRux#JNqU%?SM9 zI1_VwllynclJ1ylnkesW`i>Fc&yiFF*6$D=%0h`^Z)1MUk-mrsm;l*P4AWllz>3k6 zva*>*1RMi_p6MzguvzfBU==|%D4tSghG$01grzF53>#yW6ri$gnz1tKU#X{kxnrlJPwwEVPNf{&0ld9yHXi3t? zQ6P(gRnNpK;9h$ zFm&Y%CT6^18N?-+Hf5)iMF0Ayzv^?dk#7dsZp=|c;f|*tLoUH^Oe`u9iA>ip^GKuv zq6!3OQETr3{(1n4R+A?I{H$$L0=^PoY_XOFGLZ(9^SV?8m^l!C5PB@4V+>(&ED5Jo z>7ncx>iel56g;NCY8u%o`tcVs!7L_(eyMRT%UCF{fevWmy zH(9&?A2cY`pxm|`%#FhPP?CFh+|gc{^SA%;zrOzE&tAvkFW>Ov8ShJl)ucRyNseic z!By*uozB(ok<84iuezVo9tY3Cses7dPCu-Ai_XR52VFh_LtIQO!e0n*H( ztzrNkfU#A2_$^^6NIhzeujSqF@KKV5pWQY_AO<4)lR$}kKR0sAAosmw&#-i}QsqM` zhZZW?x-|`h&8~)!3M;cIVuYK6O8qt^ra1;NnJK)3IxHiwl&;%V&fvHpA~&D!m+$O zWvG~5U*2^L1mr3r+{H}iOmGNQaqF)L8HQLrK(mEKF+^lvbzUq?_`|!)oYeMe;UQ5mA|7M1S|KdH78F&yqIp=Zt5F!0WL8v^K{i+f zR~1Ss4~CZ!Yqm%R!4+;A;Vy9o@)RTXo(cLvgSzVZQT~8Yvie`?85i`ZeRfmzUmR`< z{mXy#3#{n@&&Mt>BO^0BNJv;rJ4TSvkMh0*GQ$sNnvL2rUygdDwN^uoxx!(ihKGb% zIJ-m#EDHkkn^mrB&EuddW{FPjau&G*tw~seUYDZ@Gd$bD9sS9eX=EkUa-U`@a1Ip- zi?FX8)37R-Lq%9wF89h*X3BIAggyez^Cq|G%*<*E|3*80SkCv$rZGuXjbYN&7@Sq< zIe@YwCM92iAqw)hd$Q0Nc3sZGCM+V$U4)0(=eKjrF|1|QVFq2}@4x=+On?0PprVE6 z7(&M;W8huLRU*_850!f_a?|crdyl3zfB(P#!}tI8mv(-`-+rm+J6A>7oc6GIyX+V$ z#aR?S4vXbBM;&bSKI7^z)f<{kjdunZ8Q)jAr5~CIkrFV?j z#S?gYEX>MeN{8`OW=f#NOi5Lgd6FDe zKg7&36Lg!JDboK`^>{uayLGTIznijX#=0SxHisQkgx7i6oZJ95!kv+UkYiRBrMJ-nG&{q24bCOnU8yyGn6GW@0J?w*Lbp1W&b50U zcz2UClM3ECBwD_D{zdK)(>HzRfZio)yDb_!x*${@w-nb7qeR-X?g47sc>eWIf2pQ{ zrh}^Uyz)-P2yakE`OJ;L#WM5O|AWv&RtPeFU}sQUtm;i#Bk8Ey*?hMLU= zYV|$amcKlvnWE){IjfeZ>{d5o#xn}e3%-_X1*=>hk2!fWbF=CggNw$r^L<4XF<;lh z*%vEpDBeS3s%M$BcN>51$V3HIMXiX}*XMPu%$mm-!^+Y2MkWBqgbjT^uQ`>8Y*@HQ z#==s*xQMQEJ)YCOP}XUtxWw2lugZvG@|9*9RFO63@M{fIC;ImC?>&!0_}v|5IA&BO z)rSYBjbF-ys7Qa#LNO!YvO{6-0ZOutDw}*r?y%Ozv2tslxM0Sww_MhzLmjQHAbp4pcUq zswv!ck%^ii6|-rvT+l1YRFUJDBHEhX2h5Ljm>grBXJbNDS=iq$I}R*<(awjav0O`S zM0hT@#(|}b!C=~fX|eksn3>05R{!!Qa}r#Pj|X{FW;bDHkfIUcnZ zt<@Ugb;)rwsPl1@8mq7&^T~1?Pzo?;W6{++8eip$MTuZB$n9{>o*ek&4*)S1>pr&8 zDE;0g?>ihS;@|-3M0oFcdzHWY>R6?KsK(?0^hI`a0!f=x`YW7?6u$oJzxc~$Aar3j zGYjmHRi(_wV@A|+Z``G7W!{RO4T#_9es4P-Qm{=t5O#!@NU$Dc<_Iu`Fv3vX&n6b- z-H*g(Rn%UUM%iGB3Xor)rSoxUXIWLM^q8bF+9SCs*17>)2#H! z`Ix?ZEnl(mj}Qun0jWfrPj9LuoSsF*D!!r!^&iDU&hsQ@n|fZWA?PO8@~Rv|D+%K~ z5sC0xfwTpLX2cZ36T&o6sYq1KF~%@>5QnOn4Y4!gzkU1m=@{P|a-r>*yOo=$a4mpt z4)zTcs)&%y^~VQpsNhz~H~;n@fB&O@|5xK zxhf)}9#5;~!;}$bxty8iV`h|WR(&OP2t{OD=-Q$T*9y*PF>&8Zl18)ikySgCHO$uK zy_=$b2)DmeZew`Isj}TNbR(yG*9b+0q#Nk6^UYb?2eO-a+jZVsRw7c?@1FjBQEhlx zJ%>#trhE(;N<&zM=IHY`vodO%xY<;U#fA=5uF}T6K{Cc)q@j{6cX8ow1Sg29W%bdI z8}~Y|s>XWQ97KifF3>b065->RVrp}Qdq&L1(U%NiN5xdte0>?u2Nj)P-o)&9eIcUE zpsHM+8Fm~Hvs6_~{XD5sfbXahB0{N^37aKs$Y!-JEKE6*;O#r#MU_P4m^225)B790 ze&WZYu5*7el;@G>Nf{q`!pFg5*0nmQk;yiAjLIZ#YJOGLx`?SMaqA>iG&*);YUH&3+o)-w8kiEdlgGsiU|ImG$PP*$Sjr6No-vUnnPsSFvI{5Odn=|SI%L0TP8|Sgh=JOE}|-?q3RwhoJGR( z`8bLwtJbx$>X<`_u~gg}1in1bBY7ML7cQ-ZBWw<1Vf=!XRYIRVD(lCu5BK!QJ4*t~ z9ZK#|3Ais_&{?tdu8sO4%HTQT4{x7;|Lu?ehhNlh{vkhK^6ACC{}fM@SJLu+`LBp; z&99zn3igMZ#PT`JJ#{LiOd|4(IZTwOHT3&Ap=b&$_W_Ew-&Sn)g$qsR0;@9#OHzAv zqpFz!Ad$lDaG&aUgix<14bkI$1Itd>Vl}x}g**F$kEeYL{aIEUO^KlL%T0jVLN&ub z&umg~z73`4(4nk*f1|2+)d`4B?u0))e7MM4Wgzy6n?yz=Ha&2y?r9YF+A)Wju4^f7 zT~&|gLs(!3qM|XUuw=#<1{%Qed}QYJ4zjZfGpUR*V)?o*5nkt|W)&%hPWMpNTuWbH zvlxNsg8$@0(BRCtTg18}iy7icRdFR14*9(A zNoeHSYf08$|KyjqQB)br^iI3sk6rtj=E&WyyrvOvW%DU=5;$1;amfG{Kw^o7Htl7NQDjG@Lv=XtgyvI$Kh znn~L@479ASwahRP;sVmPQ#JLqc<8kvvW6;EE)UqH+e2DpX3{OFb$N*LR5O(offAXn zhGUsybOsj|p&m+4o)$8kKcqCs|)MHCStQ;EoeVMF={gxv*b3iE*(5 zreI^x`Vhrn4Fv;nm+G#!9j=^kPWKD`HK@wtbckHE@m_*LqfXVnTRp* zsL~u1!DEnGy}uDNiSn?DAdg-^<46*b$5HFT_P>}ZoWhacV^uQa@FweHCAb$#-C%N) zP7!$7yynJ0XOcUiYvP7zApQiK2YX2BtyZAn1&HNe|yK?kr7n7uz00VxlP8kO9OfR8+2WL2Zbv z=0GudBvHZ3Tv4Ey9s?pc>lh%ti}2}j95b4{39@LZaz)v&wU*i7BpaH0w^Pj1jG52n zRIH}w`xW6fjDp6{<>BtfF|P9(5LlJZb3qh3*Lr<@p63O;hKd|>n8@eL|M~6ulTXK^ z;yno__9pTumes(?7W(2q@1?>rlb8wp36y0okMp0tzLJ0V@BVfEAOD*j&-9>S_WiHw z+pl@dtjcwXj`Eb}k?$uDB^A!n!^p!973E`EMvTJiY%){yV5~!lhe+dY~(>3>{{dAzG?12fK(f# zTRy5ihHtSA-o2LfamAg&Lq(+^ZZMZBn<~--ff8<_F?7t4D|D~TkLMx65dr4G@i^9X z>aa>u$P-1h8XuSVvch$^YJ)hEeLe*twKw+V~)q+?h$n|&3lm0w8n$GV7F)Y31a z60?4ss!EI%LG~aPQtgmP?GS7u^>_X`JerTD2&hDe})HD1*T)d3mJbH@a>Qw!eq`+ zU?azv#6Tk%A#g3MAG1zDg$gWqEie=E(r7T5O9c8ZP@$!IYdP@0{fFTW~(rXT$e+#9AhjG8&XM`?N1Vu%Y#MU z&TB93OijW=SV2%Uv&Z9DSKvkGk!C~SHdMV<;Elf`3%@LLM9yP~>GCLMVfApeVZ!K2 zx1oN8PV9;p#UvSEm;^qAx3}|{M=<^7%lU)H{NdF16;1DN9h2Q1pGkV)Mb)t&{8QO| z*gX}_S=2H8;wYd2AC zpmQg)`3|t$n4U)U;y~PHD5)xGlQ8MBUuf9@@rua4ke+eK(2t(nVHrV-T#wt0Qao|6|r#UW@dFf zELk|COqHm#bFze&2h_%{`|9*Mfy=`p=UNf@cuWyKFK41L#++j+v`!h?v-Px#Rvg=ZK8w0UyYyOt6!StCEV#BX3NaFUM;xUkh7az=l0^1!@p|N!_ab7HZY0|C%1pRE40HlZR(792^9kA|4RlP1imGG;D`VNKqRbMJqNdtg zUwDvX6Wwq_s?7$v!-v@N;I;&-2yok?c6&!m8T;*IRCyE}GUg0kRL>veRbngOVAx5_@B4^@FB!-gHtg9?@j zu`%N6WZgLCI3^g)qUN z8b6*CQ4wrL!n-TWxT?b02ETrl=R1KxI1jQ>MZ6#TeM3Uh52)yM>kKSJnZiu5D3*xy zdUK5SY3w>zZjT-@5oJJ#XtaXLtw>ZQ_rQS#TLFvOk4w+URfW$)nwZZ$;J4>`SH4xH z{^d`8iTCII3nJc=+sM(!D{$uSd;G566J`|=xWi};lwB8#nj#D@85y1k(FNG>UCdZ! z{T}2_YXKr&Vv6Tdh@yK~xU$srx)vO$n8M~9Z*T8vnpEzNtLPY3Nf}`S-V(Z+l+VO+ zxub;{sGa_LN9oYFw-aCW%{q}HgYF_qYLXl@CZYAdL@A?esEPUVuUC51pMEvh z6$^3pOx#KEqeP@w+r&;0G3V&pDBifZ=0Bd_{xAP&{^Dm}{mD!G-Txu4pE#q|l}R*A zrc{&+rYN138EVQ&buK#$Kq5PaY*+^#Hc;1y$~j%^nh?PYy?8dU4_vVw_u|)a15D`=W!gz zx=zR&S%Kvz^at*AmAt>d3-@(9BgQnavBoiBn-S)svSUK2n~%en`||NPD$B3a=77@> z!xCrOaoFRbk3+{yU-y>CUS1O!?yB-jnEl&t%9q$+)r^#5vI#NqaZp70;yH1Ev3s5G zd>lko*P<~vgOqvB${?uBB5YRIDaTRQS?xKaJ712DG}Guw30{;QHX zTkFik4ZWaR+(LG>bDBg5DE%x}qav;#F)F4EDzpdeVWaZS<72#SCsmbucRwUsKsXeEDsK286)Bo~yGNaj{*-#|+sBPvKW)Fv8 z&BWlVnd&f;-oNF`Es@8f74E#0Kb_l#~iRuvoaO43XboO z+z@>@y2DdV4T=hGH0^Ka^ysh!abFiYgI$#Me4?Sb&eN^+Kt>+NBZ}Fy2Lm*9fS4f+ zMq)i5^PTvgzJ33}(+;M$75J^H_kp5l{1hdobZ_wAUQEOL2^Cw9$JhVU|9brFFJAxX z$Zx-;AADcj`DL!RlbuMJULKrOQTmvZFdY1GzVfv;8>u-G)DK9e~akPmgdU!!t8hn5n8&6(ZfxK#00!CVS03foJHAky(1;)%*DL$-|)@ zc;uJ|F{7n>J`Sp~G1l@}u0zw;{N5)@zP|mIxKt&s71w3R%PDhJuB-g|;9gyqKA$=V zmW1*=SUA6YCL%(L57f{|O0N~zp2?V{A}=qXYE&j?)V0`*ZPs~G70h=0^4iPe{<>AW z8j)PbEk8rlNR{!sxKlW@HqNyC|CI5IL`H*EShaJHMAUwV_M8w#+$=&)m6_F&xNR!n zINw|8&eR}Mh~b%UUZyKgg`uj_hiyOFOUjw_V<^OGtEI$I^r30}27l zngV2D!qToU8`?nLN)|pZN7+@lBjyk*k7>9Tqz5eIEL2fyl97}(re#(|s&G`9*+NLt z&;eZTnMHuUGt+rqFVBZZg!}XP@bve$YYZdms=bK(=JMZsIsf!~&zC`8&gjO~s%_WF z%EX@pavRw8z%}$%462cgum9uUz5d_+`uURio8ROL8EUT&@+HG)BH3lhOp!E%mfNS7 zilTGL7)2$fTomVpA5%y5s)k8q=zvjDs?aA%)J)XcRX~Wm<=$q=Ud#xd3Q-hsP*Q0p zFBk?ssJD%ugGot^Rni?CVJKw3c?o?RUOB4Uq$5Mlx4@rOp3dCQqiOg#V_1nUSFfKaD9<2 zv{T=JWBGhcVez$qjZ8GLCw$+u&Shh;vY*#{9?X>PHYXFWFK;%6juGb)6MK0w*ZT5B zhp~vCS6=V-eB!dtHmFjLqnOKGg!y>n^-h&yV~=}PRXPW{etM-211PX|^PmValjB+E z#mzy~=7E4TkH#!WH*@5dFI1tAmmTI8hy~?A;5*NQMNtx%*U3b{(U-fxE3yaS-U3HQ z1UGMtVQ9hC9?}Zpxy6hctu0u?!N%0AQLRj_Vr*zxnFaNOkh!2wb_H!u3JuQ0u|v$H z249E%_0NBecQ^)@Cii!e1WM-oL_I}}qkw`{>Jsk-HTV>8UGjnu-P~SDh1PK50 z?R>l(9+6$zBurR-8l?Z@F?){}XicSbi0^2ADZj#oP18nFL=ejunx6BRAQWa4LS@fm zj4{4mHH`k~b-bNkT!MD$+r7{RGll02xz^js-mOu>mHhkv`G5WNZ+<=3=la9fd_3w} zc`l~PZ{Ku|j2>tB<;kk-F5#qF23w2jkg6~hC}v|QRgJ-BYD2E~rK*9Sz6oUd zBSW*UrK&bMbEPVA(~&w7&Hd38HD9#JzpYvH2BqEBO}O9QDB4zL>Ucr4GLG<7b5rm` zmpLMyTl+hN4usNAN{^{q&N{*K%T- za}=fwyjLqlgjLK8LWhcC6+FzKXmgg$DJ(W*KIZjyipUsNkva!4#fryQuNhgHc0A(p z>;2v45#jSOna$Tpnf7?tap1C##ip3*V=`+M#fl=*Pp{kENdjA2M7i_hSO()qPDpKn(cZ(jqPR9Y#f0(|W6J&Ra?6{Z^ znGv;V_s)e;SF2EOU34b{xFrPIHvZmeBMS5vfA%w2WmiN-BH)P!HzM~R41;cBYvrD* zip{c`?EqWT6r*qtMbkNqoMd`7z5z>J7Y#z1i>52)WB%0eTY;ZN9kEik$GO1b_BC~pw%n`ZDiDe7t?ilW-0FYf;L*IB9B;WHEfRx z7wzL1vAnHTSvH4z;=&FNPIv+@AwVIUeSUi*=I1dZ3UNocvrLtDfPQD2Tb=S10RgK> zhp5PTu3_p66}0$>7*jk8zrUZ4mq$c^ZCOmedylK?&wu=So)Jl=3a+f-6W}gLgr8ww zB70s7PbwKWRnO7R8JB_wPn z5pnnQgv&3zsSH?P&6gK{e;co_Hb=a_AK(A}czIIh_2r%BJih;f@))0wMLZvb zw$RD~BpSKX+9gKJB`T&AL6xF*BVrn2m$d`Yef^@$KH0a2fuU`K?(e=&Z6?~S;doDr z(WW&ri~i~-zd+s?5sl1f`-mzEFI3dj@J$gB?z$Kta>^9l>|Euz3KC&h(_#wWX=bKM z(mjB{lTWG}Mx&8%0%=6DAYIlibGt0H)pPXMiRragQ9D4|`&(_)bN8rJ;mi_KVR%uy zg24L9-z(N)T9@NHqnjy-PD~VTva*S?$hCse7X$rLb(-Opa4jbl`>$(Tb;e5IJUM-mCqc#OjD zDm15^=Vhwz!Jkk6^B+Aa>g|dilhJ4+l87{~E$f1Qsfo7Ve9ASYDi^U0?1v&E| z8pDK0Mb;I|Q_PqdOCa280sofUXoT8d&<9~VZNUn9areU_(A2IZf=ga*A2gGYND&Z# z4B7!ejI?`irB6V72%;k zGj=~IQZVk$DyEhZRcWT$npSU4KbH+7=-Q}g5NtToj#13uRjCR- zAQAJoH#`;B=WlIJHLHmBJG-OsDKqoDbPQ1~cih=40_blsttb)VVYn|-QH9em7S8is z9#cdBtf2E^vl3+!$^e=GuSViFflwyR(V-X-%-4vJl%LGPUwxm5>bmstD8Jw`uI2oE z!h1{z{y}1fWpfN*LiIR0kb+%AiwSLb;5i`uu@IXQ`m02-dtfF8^sf84duV7yq(iVD z1%@4I>7>a{==3p(wWPA(VQo%RclLm%Dx$ys$*+hxqdHYgxLOhr%yW$W?}33O0fs36 z?JBsE-?zvX>1l6e5 z<&gTL>h0|sLn~^`frn$Q^tDtuBf;LS%I6%;@{fP`@~1z145n}2E>pdO`5T3S%>X{U z+B~Bcu9cX_T;F{8@^^p#!~gOBq4_mu#BcsUb70`0F`22}PeBJ=Mt=FCpI)lSK0PT? zmE-NAOcn|cX7&}&ub%tn-b+(-!>e##*_h3gvjE~Z)yf>xI{gSE#r?TsA4K1!+Vai^ zG7+n25fu|80(?i5B{BgGhzL&Vb^rdEW@aPKI`;+tc#C`AjV&sZ3C)QL@dQkGnr7rM zOdg>s%!TM|i;7hsTHs=9!)S*vgazEVR#?#$8;)OUuM_C;`gHdOl4LQFn9i36Wl~WT zSi94gi&;jrw}IH#DIzvUx>p8e_;oUs*vz%o^*&!7aa}rQW$Np*!$TfwdYmp?!>tiSkIzd-!AqD>BoG;5Zb z?fYW`gP6!OM9oy95;QxJk1E`V=mkr|fm_*ZxF|EVXO5^O(rt{&g1rQ`Dh7Iu+kcHp zrveVRT4RBY98l?ScqB?xdAd-?PGlvL-S5$rb^5YoNMNMgpW=*v0Du5VL_t*l3!6{;n2CtWHd+&#alP?aX8DJ2-+%Y}AN=3{E&ZqeSz2A6!s)|tR7~uhEs0};cE}}YKj&eBdgeW;T z2cwhjsVqe03s$oZ2X@G1hXFB1cd-uFjT~j>2(KbDDV()MBG%d(@h0-O%^z@WYI@n_XB9-ltN@~rf0jX7gi zYqF@!k?CU`T{2lknepX~+zDmd&@IDRl%eGkrO9fKP^hQDxGNnY7*i6WnaAkCAT!hs~p!`%T7@|CkKeDCO zQ0~Iv+k_3It_p-+4627sz04Y8!T{%4W5QtH!ZlTmX}INO zWIG>FVr{ZK++&W$Fpgp8}ujRxPR~D1af!-!uM6fy&X7}Zp!gQX?%-k2N znzZH4BUl7|qdR7$#dN-1Kony_SF?Zk{TDU;?|$_B=9|yCcVA|@8KjyOm8I|k|Np7_ z*C$J|>&g$@uXEh}-iXXBRDmD~5O08->4%N1)oR&{TBFCz*!=O5Sxago)vXrECO`mH zs8?o2#J%okKjyu8-GyulR#|^*7_gQn*GJ@u5CEu}bc4W;wzZlcr>>+m_F}2$3qAGF4l8MzRO1*wUoR zkE8V_M0#G@ng}Pn-q_?(GmS_Ujm*Mj4&1zX*4A`Q(aF-J!a6}k;Cwe24YA^zG_twF zrXt1mfBqyU1BV63k;MN5NHj)QW*|6-tJcL#g)0$Vr!#^G_z@FXH~6V1PDHr%*;r0g z)*3UlWf{kT8!`xZ)|)m>{AV+qY?$%a%cj`F!@=DfdX8;b>NMvLEIF!xMtaSTVK`v5 z_Hc!!VcR-x$4t!#>#`J)ERD^?j79wU8R?P?x87I;wCNfLQ?Ut6O}U6lA0FX@rXn^g z7Fm|s57{n+`;94u=k3P5>-Aa@TuIjH;Xy>ogH1^cJg?k$QlnFaM3=#|#D!C)_6~VV zRB7yTc>oof_8lo|&YF3daZn}(dCH{7%HY;d+;QUFFe`mU7zmq8)&}VYFLh7>7+Ym} z{nDTz>zO=~tt7QR)Bn=YN&oEs^PTdY(rh+S*k!9a7dY zj@K)2Gn-;@!Q_;6q8E|x5|R0c(5&ty=Hav3iO;52jKEdYmPSnOLByGb)y**+wLS0` zLu2f{VW2MH^M&2wP}L+enMn!x4n|kbDo}%o>X+{?fpIWte<@L)_1=j-nz}P zYp%QmlPN5>ry}(o6C8TH!F8%)gxvDtI7NeqZ+li^K-S>fInyG{*ILL_VMPy{mOJbG?r%&Ff zP3Jy1FoBy8YQHl|&9agS2U&t+0guPro}W9!!rl2K^Wu)kCs_$6?rae(LTSA)H!0;( zm1eA5W-P*xWlR;U0?=B(g%GCR4V2MSLMdWy&4$PP7ki_L!BJu!$G%=RE>g21_AxN> zh-e%G6;>ee_O(YudT2AXmL5dtOdz7NNo1aHH{Xx-%^On9apZ9ji>ZVMvBWWm+16#o z!3zz`OrrMYG4DGpWz_6DWN-!(0cXgSMJ#5NPG7*PwAPe;F>3ljXa4(Bm(O(6dQVFchEnPq;U(Qc`U>9 z#*FA3y68!N@uOc(Ln6~~pT9^IoNCMr!x>tKi4Y~yhA8B`TC&Mxqd?7^*|28#&LfYD zO^BXWf$*HeOBHAaL{u1UgeVCoa-EVN6Df?UDme9ICirATWC9&mFuJv~tsX?_sscmD zJ*_F7qz+Xb9;fG*BC@D@Zv{sa6)E@LILo?`$Qa&QJMldyWNsD}Ov_FdwXVWEM)aj6 zaO`FoX{M~~9>lC{SX3qy9})L7(Pj#XLq*2nX0opRI0A5pZk9y*KA7qG<*sICa}59o z?&sY4g)^{RxrW?LrB`3arosSG{WIk?{86+3O9X5s#NIMbw7a8aILw^CsfGGS9zDpXc39dQ!Cav3E6#MDjc z^)aK8Gf511Dzd-nLry52wHEF;AC`5)3K6e%V1QuPt13T^-g-ua`*OLk$g%HzY1ndQ zB+gCKcHC~Z^u8|Ra28T+dE3(u($*dyj;9wjBN1!LM0pIq->exi%eob@n)vNN+%mIt z-SXv``@*X6{5(+`t);t|PWYf$9d}WsX5VcB$H5{cCpTu@sJ9BI2txC)8TpvE<>B(? zExA)UtJN5IYf4|~)h0m1eZf-#ak;{$5D`h0boy&6vofT0=%*x9XA$CDY(ydf@hXg* zM3!Rgv6+DiIL*t%%qR^weIWldR4~I)XGRLwU4cY1?Fu>_@q{}*GqdQgfATBr453)R zY7zKKv9Q4@%LNJHTqhvGIH zVJdO(A%%im3DPbMdaCd~4u~PhF2e2M0obmL;SOv_WX-fXk`NHq8Tc*aYx682&8*(r zMCe4B3JSAW7?QM^?)!1FCP}B{uK=ap_JO}&WHyuIaG7kVS6)QP2`Ls+6N%x;)thC6 zC|9I=Bo`5fr)S_N5}A;>&=KV$lB%lw2+uH;hyr{{3>^RrlVV@6dL}(zh1FLNczfl`S>?~PoKVh`&U0Z{*V9D{`}8bl8y3Z zubXp~e)1^WQpZ6ft5UX=MQRKpvddbLn(9_HBv7+iF}CNv#`aX;U*5 zqGf538Nfs^aTcu8_$e!&8mjW}fcPLMB^-^*Laz&qxzVmnCipG+%9kwtVI;Zkz%$`{Q+<(m`%DpI6+^Pk?s z=6?|E8xso8wDw}RF)t-F+g@h+goue%2{b4o{ncOma-JTiQ7>OPqD7+O;gJEpIh{5m zEK)>Rg$d^CbI;V8;W3)hFvftH3t%(LLI)czD7Y;~O{jvMac~wQHDNge9hoN+N6;?? z26j|}>0DJ9f#<7$X>v804R+Bgq+OP-_1Y>jb1wMHk(uttzyJ#p-t^QX5k`fDCys0= zm2hDrA_1v~l^s;oNK>?F&zxUmjp0i-v@$az2Vd*fizz*-2%;35h9?TetqG<&F`2x&P!H*c>${*%XVzWR`i1?}uM zJME8DRhgl)$CITQMW%;bt{?vQf1r2Yd;3>Esc%1LXYynZeZ0heaBoEqad3?MJ<=#gtyME-{?O(8ZuuQ5LbZuEZ6Gi-B;GNd<}_D<@0{@Hp3bo2fEY+}n{B zV&=m~R#`KQ{{Y^~RM$>B9nEYY#)3&cVQ{8S@~%h~RV89nH5ni@(-{JM9fmOBs)p9> zntj2oy{4k@g`FCp8XgNa-Da<;)Opl#cy`_>!3A za8^Ozpsp)r$g-5EEQ_uy6`{bJo7FLRTl4uzK>RSJJPB|}$`Z2)`B5GW7*iy2E;J4{ zBen7|iAZB`?^RjLN-Wq1k|-}r9e1K+>uinMQpbIIB;C0+3ZLF(Y%C(!j8z$ceEn`y zbe=B>AR)n%h68uPtxgLOY&NGAX9g5cQ{99dF=8oHRg}eWxxg(B=$E~^+ThO@1ef$L zfA*I&wUnB?QA~ECqI1GEYxV!%QD0L#6E14a(4UR>49bnD08~=^>k7aOEVo6>Volv$ zRYtf?D;wGLK`Twd4rdlBXdyV_w|0-rjEo{S(@FyA7|TX13|}LCV4=ZxHQcSYm|asu zXPj@i6LHdaS&8&)OFMRuH!8FAR40UazYm7}T1ABS;mi~x`f3r0jcfRfpQk5QOp!^M zt(mZl5q0L+5WzK3VX>}-b{3Y*1h8}*QB~U2_n3YLqNo~^+sf{#5bK%D6qS#{+fs2uJ@6gUOCeg6LC$ol1vzyIymA4XA!ZxTygru4+Eiq9HWoGe648M<8F|MlPY zpZw_Idsq76A33G=gGQ(|YKX#Mmj{sPCT9-KpQ!82F&uhMM!}Mg(|FofkSil50nT3%Hi>hJ z!NxnPgq2VH9r0@?0L|(qIBGlCP(LZ^kW_gV8A@fSJ>bo?}+%@ z^l}hqWmBc95rLpEPXbZS+zJXBMAdW*2N)>9z?*)cQc*|Zey&(3Ghu&@N zZEfyevs{LgN{H8`byYM;O_Sw}d-DA_dT$hoZ!Vb5tm4NoQ&t+o(Zjj+9+4v)LG;SH zJgjlJd$ev9IgSvM7>?I@da7|>y5yYqjxo+{l0)J$oyCJptjyTdt4T#ARS*oUg%;++ z6W+Y9$qB63IbavTHS(%8l=B81Ijxkh4tyN-`&CE)t zpqx`el-XcduOkugz!;YVcPXA*p6bdb)S8-*C^wWWi--`|a%QY7W>7x_14j{MBr#cQ zh+RucigUFts-R02YHT8xbraR&cGt6{H6#2OF$A*3GTQ= z<@?beF6kp9wJ$K6`o6P>K0Z(t-FI%vBI$t_VI0m&uVb)?T(58+;<7L6%BFGb74QdVi_L5Njk_V`o$ZOh6M?1-+Ue zrfCz=_LyzL3jRD~NodZsMVWlo4btm_&ZR&5;V@1lD6{u$6i_Rxi zvG7L;2o$kQs!RxN+He7^$*5%JZVd$kYQ|0L za+R#y4^HCiwM6*kK5u~A%0*)0W5jW2mNqSBA0rM2gjtFb7X)Y&=w4{H-*#JU3~wrZ zY3VW32bp@Ra3Gg*vFGeQVLI!m=jMUoshEl#&94~fVAw!{-jGznuRc6N^NyN9**`J7 zB1}!WH_7m&+pP39QQ@jumY$Iru1&e9;=BeiWmeX-)?v|iTAni8>!d8Ou(noFnZY7{ z4Db;t^Wp6qqDqenx7LH6{!W2$fgwaZ^P~1`QGS)mmi)I@D!(&%$?bIdaqzoH16VnMvod5T9vJuo0(EjLD z(yAzfi^+tb>>wLyvvv!E_iGshNJRSUpZp~WN;gj1ebQ@6+*D^fHFjjeHPM|pujf%k zvyfQyY&ezI^PE-X7z1Mmi;OW4R+JftKsvFQsF@W(PAD@2Rlz-)nSl+BW)Wgjjdb^z zJYOaz-p633$#wJ0LalmX{as0F^16Jfq@)~&gO@=xu^!r_(BUQmxu$pXi2WEkG1(T69Z8g&vK1CH~Q4U98AdK^3sw@&w z(9l(79AVwSsbS&P8fsfT!%X{xE9t)P^KdVGp47VS_kFu8nZ-$>jF7E+_TFOnk@UxJ zpZ@%Z-@k75{_FR85)5WfSP_uxlNnObQl&CTMYO;FpZ=HaU;P)WmW(I5JXY_;7Edqw zc&Rb``KEm%Ayv)8WohG&?@73S^2j4eh(xKI5Vdt7B5f^B1HgVic-Fbj?%>`uBa3TU zIxd?5UmEf$H9-P$K7~2E`1o`*&s)(1c8QRvXDH7SE0l!-2o8#&oJGr`in}ekF=3!o zp(boXScI7Ix)>yv%3PKX(r$E4I~B2YU6yg*nMzyp;ceUc+jrx3^W*3b zSMHstYPhUx-tT_90ma8+$p^Xfx-wDihb(Kc$irieL76PZ%6S}YeHtec$D)xnQ7tG2J}6VxOYZyh(w)o} zY73Z6Ohgq>73Pj`*(!p<&l(+~h!JaYWDF~a2&a9MbQHdHr(%_vz7 &p8bnN>-XV@*{xych+qTz-Vy0 ziI`!skY*8eCJsbY{Jb>5Sx8wy)!H1@n;D;&n{c0IUS-eZq^Kl;9}B2F0o{L2g*Aa; z$39v&#DBObDMfWZd|kRIANy!atEy#bz^aML?RK9nj+N48ARduV6t0S}sD1wRhqvz@ z|K$5`zW)3hJp<6Moqn^XL=`PInD10IiL>;>zyF)J|KopS{>1y8MsjA}cj+yy@!@m~ zQLYs3jfTs%YE=!-`@!p?i{;(vI4UZ~s47)cQz7v&ia`&yj1;Ey+%8L1r3ZL^$Vu6m zjhbO;nZk236F9Cau`x$2%j{q05k*0H)+T$YM|BC+SR%Y_Qx2}87;yWsqR=F& zir4j2w1Xonl1$l@nZ#OU@J!U!tRE!i3RdObikVxlQ&^q`x=7fQCg=?B@|+DAL^)yq zaIW@+M9K%Bm;fjUuUqIjY=Qz~T2%V;AN?{Sb*c?8GApxC&h-l_jVEyAge(yeB^8yN zv6SyjAeGv*Vse;A1QDy+E1dUi9VU2s1nX(MLx`B^7}JhDEBJ_s$VAPgB43>(s0BdG z?<7m%#Rs5L&RMj|RC@1+dnGVTtP}go)+QfHMY2%$H7fx$pt+#OuW@daXtM+5G zZdv5v#Pbi6kws&&X6!QDc>gG%%pgCh<6-HvC#1hDL=d zVKPOlw`^O_N+j>Ffmaoo1uKQZTt@6br1gf!n26L&yJhCF-_4qug*(b*MIh;9M)YlM zrsEiGmSAK=#2D61tf`7LJC0*nnl636A1bV^Rh&+tS#K zGHMKKy_jkYw1cu5U#?^zF_A1i?srkC`@J%`sq~)2+*;l5M8&Gy7jow!Hj_8+AT3p8 z3FKX##yd0zM@V1jlv&BN#-JDw8FCC_WizPBRl&;&*E%mtkw`OEtNoyG5+PFxCs7Q% zwcm+YmbJ1foo$+dhA|urxNw~T>bc68r`GIl#cw((Gni%d=YR3*n#uUm**7ke9zAF6dbQ}|oGqT8 zKb#LIQ+M*_V5$WXgPD6bpS;lNe#02TLa&`hnE^}R{eBk$)wdX+taGy`b1*km@o-pE zFj+**2m^h0rDYZ#2`e`dGf<)^3uh)X!`sUvro}xAXpxE<;Se;1r7A~+pSA}SUo}%) z0b+z0Q0PfaS)?WrX(lY<;h-(!4J5*a4qTYch2ayA;o3wY>gkl~Fs`Zz{_uPXT{T+` zNlmw{M&!{^#A+c$%n5gJ@?dYB_F)+Y+8pi zC-+{NblX)aQQh#ow@=@2BuB*Iy7eNaNbbhW#+1wz)w&Xqc2$*eKcaHm*0B#UuAJ{| zMCyS~MVdJ>sR-}@KJ7pdj*#(BXlhfz`vCgXNX_r5fkmogKL zVtDXM^z!NVVvHOit*ws__m9s|_Ylw!Q6koE6j>traIM=-n{iWu3d~wQNLaUxGD%gJ zp5b!2#`Cj2ysKjusyud3qX5R7(`XebQ&D+LEZoBbu5P@ndB4$q@bv+LNsU2b{P2*^ z&$@0k4oC!4RmUN{laDHr%LDgLNUmj)57{1Ogbg#Y2t&9q44zDtOhhUaP8Dp8i4yI} z%TnXuC^q9f0Ue6dKUj_0!WFOUK~iCnil9t#$9P-dCGiP&F6P#Vrn{Nx6*WGg_7ko_ z^(r?L&FY_-QJnSBpLRjdB$$G>Kom3WZw&c!#;nFAzZ6)9pY66*wM;@q4WZX%+I zR6Y65^EZ{#|1{~tr&kZ(mWlSOnmItzic&QQA;UveJrV`XS;$Jv0Qx-lMN|!UVW>SI zgm4iHS!)?pXT>$96&%GF{PEn&e2H;yt+j?zjFPwt97$psBbKGj*}vus#&Z^5F>y?{ zV!=2lC+uT5(&uWb4sV>P4<{HbO&IMnXKtT~`m)@Q1I4>?I(PAt5|P$)I2Mw69G;b; ztjz|B##Mv>hf&PY+#s zyzKqW8Xs>Ws(t3xqzcs-~sy|94Y zSP&vIqV>f`V1*7ShmV1)st;e+l?5zfIla=fZkvw-4OPe!`7zSNmYy{$E>Q~Fw&nKz zNr||rs^u}3%Vq5Q`tHrx478i({=$EX9CP_R;s zT4S+-6)%%2D4fP%E)pSCp3vv02yUI4@tmfUD!CH9-L>DvI#B}B%Q&f*bzZz%Cza-!$)L%W$tuqXE#FZR*7NVTv3*Hb+TtS{VE^!Xhcj-8- z?^cVGaEt#sLo_&`fBBQYOaK6RR%$YHWuP-iRFIbe^^FOx?P=wiv+&GZohD+Q+ij@B zz}8^npC^2sh>l}4Sax;d^HJ)9l5C*w5PA%c-VFOj*nNb>41msg+{}P%PE`l&_SS6j z0I+pD@1#}Jw+Gq!wMJmDk4;5S^MyaOjGd@+7!AtGc9ZL;YzHk?(~P>eevf%|MQ2-`pp+#;iWZw zC8wQ!zUvtrBBiS8eSG=;<*Pq__TT>-`tozNmZkd11MP!J%AK3$?M5EjT0Y;kbB$7E zUb}yMK}t#1POWpME!~zz5uDk!MY~ayM}|jhO`8Fei=V<6qb(h+BAK}?eGKQBouyM; zo>DR+)Gv!r4p*%vb*q)Ld(o5jA%<6|8MWpXoe*Xy;yYwX;6-e0&cvMlqZ+7~ht(Y)Wt8jP^4 zn!~5=T*cNaeA?Yq*{mXE+o}reeC~}~BaF9PRmG}R!PY17*|Z{f*>W6|DZSO$C;Kd% zTQ68ktPvGAHzwZqorHlI0*ur>0Eq_2IZoUnQta03em~Ck(y5Z0&R-Z~%}jWs zHi;^H&SD1JY$T2kQyuQJ@zZJMjjE^^XeFLJC4#BIn&SPc%svy$SdMX2!kD2Z`{C=> z(;;&wm@UiJ?zf|LV|s;-h@(8LFNDJ&5=-TAKO(a&I3r40`~7b}fAi_P&;InYuYdoA zp1lv~=dQUOpwl=n;5+N}y8q*s9`|?u<-dsE{AU);eWxmhdpfH|YO{R4u~M#A?Ka-O z$m7ODY)!W{UhbShMElFGin~y@hl`IIFSqrw5^-Bv#e8E3-ebbuY#gJK%oL25^T{D= zjg4zA%8|Im@_(8u@VXTiZ8}-&Dp?g#aH?W^LI0#7yQ)m2DVs7X?0eS}H!1)X5TlY* z@i+lr7bqutI-3Ao+#;aWu%A!_Kp z@Q=^ay}Nd1Nly}DHE_*I6d}O5Ob?ISO|Fk5S}#v5CT4l;^5&B|c9amterIBColWu> zBtqe&R++qBVA|oW(I)v14`)ZXE7Dk!YjdyL@0tzcLxzqdxbk^V!6SXvSoVl0g0Ym z>=_g!c=dp31N(+fxFQRa84>Bve)x;m{oXkmrmgG*-rMnvSvDOJ;P z%(llFN11dyQjD2s>O`F)GnP5TOq8Muf*20FJi9nEv$Ylh;0aPeW!6yDiP#8! z4mkR!EvrbFx3$5w(p$s3m56xG!zUul1RKTPO;u1p4vS(Y z_136JO~(kBg;kYn#%)CCxE*RTMxd*7zYkMc7ON~}e!1NlU2`J6n~#xITooQ9bli`& zE?;XihJh2&+2D@Ar#oUwLRosfKK;jk;K%jdfA!Duw|_6xs*0)d z2wj9ocv(a_ym(vuk6+8f=I@{NlXtwWbejW?s%mS;eeZ9!+}&^YWn02C$AF;EfEq%a z=}paUPq+25!YUpaq7(PStgN!%hc#=hov=_w?G8^cF(9H7amN#1$4TU~NPCJMU;zU< zD9Q?K8KoJU&>Ww2Lcz?GnR+)kMDZeLqV=+-dwNhsGo#2Dj^re~I#iTn5b!CcNCuc$ zo{;W1j(Iz-8si`$wKk63tfhO5f#<}QZmnT;F>ZI;R%Y2hJ~5QXZo(q1<#1}fT`v_O zDtQbwjh7dF_YPlYX_m*3-g#MwDPCS=U1Ysb?|Hw;y5@1{w$#gwGG*y`Kcsg?mc2J- z;dL#Re158M5#in{Ld^Jbt?=3pdGnUc2-vk{6V-hB2(lup%DUBlCni#3(K>ckIT=Pd z-FwS68%bbcURIW~rG-uyRs=5_Wt_!k4FB~UX`2yq9e0+q>{XbV)5n?B4rz^xDe{z$ z2~iF{1!R1-5Ek&)ncN>_2=Fqe*D~*!h~-xS`ph~~hW_%$zow}tA~HLBF#oBEA7coR z(3o5^Vy@k=)@N?>Q(%aLn3|)~%ml1#v^^EUV-GVnk{#|rTqlF+b%9A)=+z;CMS6~*L`%zhWay*zb%bEf{9GS;4cxGD) zGK(-V$#8GY#&FQ_F-|Irn&sJ*L@Egmn)1FMz%RJFs$nnW!_B}BCldBCx2BUt^}6w| z6Br`wW58l*YFR}|rjlI340uox@3$Sv&^-IYn0Si&CZM*?AFPV907ra#xmVG$bobN- zt39izw)XXx-yY-m#XtY6k6(Xvyxev6>`X@w-h$Be@WaYX-X3m$^MC4R?;d{rv-rn< zNb01*pMH`fA#06Dh$_Q@DPG>ZNe>|i(=2Az&F?#l#<92UD&6?J2;hA~W2Y+ZjUu== z**2mg)qHu@>ot#2KDf8^&}Ct>g0HEP+{sM02ky%}FpmLuF85B%x!>6bkmCfi2tb-#08d0EAbDD(C*;rH-R?spD^{fLRl8om1X zRLC8<7*ZuQwnpi2!NTH($RCZJSthMijVKuaHQ=EMd}?EDlaojD$e=T2hu`89xM?ol zVB4wb)TGzf#oUOVfPp$!iStICRHZ-r;V)n7A($AARx<0W=H9HTtl8wra@7>n_#}Q! zc@8fS{9(&;Sv!F=!JO0I22$wU)wp|W4eB#t%LMU$ZsEQIsM1Np=Cf*xX0bOxMRt#@ zG&L3Yj_TZ-&ImZzZ^DS7ln6ss1~pn#-2<4tq8#pl5HN`~ORDgI;K)P~38E)VT!e9js+d)ck<#v~9Y>gI?~RMWp|30u>ybrly}7%Z`Uqk1 zaBC)_&CHth-moLG)~2*oAVt)EjBQ(DeyWs75z)-B9fd6#F9U2dRjq_8>hiE1`$+e; zG!nVr_U(GTz1%+k{PSP^tAClt@$su~x6AqpcAFv>ld2$}2Vqn$Z$JLKzg~ax!|Mp+VZyR+Uhp=VXLRRUe|I*a6|s&`&{9>wqNeoL@W{Jh;!+m{x2p@h7=}k@jHG6DF^j3RCPL*Yz00!H za9Vm2N$Xfsu?j_rD5+2c6Bn1X#?~q$oDI%)CwT_pSW`P<*ui$0P87S5cs zIgvaLzP=$J%EilqR-+n6K7EvR&2U-P8Qxnj6i&k27s4DZ*d->bQswK4jhD5sl+)8| zA#Li0;M;*l4O|8@w{^?|n zOc;o!xHqNK(?!SM{aH_=o}Mt#Ii9-*hHh1@lW^!@=(cCDn{E#e?6Xe9-iH}sp#julDQ#j{XrS~@7APkZ=O<#7_c3t=VaL){97G(}J zbrbcaS7ap&%RzW?W>(c@Thjx(N)XwxU^5jKxgTySKHNl-=R#FgOgJ*{`+-Y|yMxmm z9-yf$d!chc8S!WjHv-A0 z4=^(!*gO^$7E?WWicBoT*zVd~0>VTf9cv&>9NYi6o`92Ilr%PJ~t zO#y*o73~(o(+7**Z+Ba|Eu9iM*z*0*W+Yr0<92Uv9!MB{(z)M@sDJXQzuelo_-&WV z1~5h5@7Wf;TWh`O&oUskC=+Bl`@SX+6epq$4BxZ z^UcS#-^hntE@X|yQO7X_@nwS}jjdC79=mQEx23}2^uXcBqBu;sHD*BtcxA}46$#Eh zS$etGF?3ytiDHl%uUo0men{&WoQajtip0b+1%Q*yU*%qjJUX!%aWRU$si*>7h$l+) zZ1aULn@$_@?8z-6#tUgC4=X3*pI_~FJc%4|{ANsR6p8-yhrdWFVw%WXna!I(!A%T` zYR+~mfyxOoakGZilOUPHguS9nUl`Pi*^~`x#Ozp`>7+~>OjX& zvuRnaIlRv>kMKAK1{P%@rsHrs8R5q6DVJmC&i*;<=(G*R!( zJ&36H&N5vPc)6NsMB?XeOaF)e^qU|3@-II9^z9%2;rG@Xop&8XbwrefCmpaTvZPO!@v2L+eYz+KkAzYisbv9+G5{(^gn#Tk<>fg@AmkR_d6%cqH){X!^)c3aTP78DYK=7E?xyo4K$_!Yi#cLorQ8RMxUB#B}Uq-IinDK~2a?cW-9Q)Yqk| z#MG=E`_Y%q^(sitWMM=}}Ff%_6j62qwHj8~|Gd9UcA4d``j}PPdMU+ua zuGX|$?1v9;mraeuS|!=#!kOuzZ{EbyM{SL&XkCRlZg*Wfi?XsTJ@%c`YYa-~vr{x* zUiA6^RUz*;te(nUmW8d+aj+<{$m1J|!EiMYexzBVylzCo4{yi^I^U?T)E9~|!ytIe zHp`c1ZdTKttXL<~zQ3ZY=W>QvmZf}f>*P-BR>y%$4ViIO9S2IgDV$iSS%t&dMk*D- zs@#m#2yu38OjQxDbXq=tqwn?=EHg5~%p#%^FT>5w6H4(tBUm9+B>z z8KPDhBE0q{%Edf}!-#?dMTGa;-b|S}VnV2enN25(%oLg|6=`e#_@96O$)A7!hd=$Z zKmOfsL>r3)xNFFBobh;kAga>Z_VvH}o45b=U#~4`1bI?Ze)pIkpZT(-hrE50AD{Am z*xIRCy?^HGMS7<(@;Jx~F+sl%GlAi1yc~Av5v2sr6DzEZOw7{OHioMxe0cEqkAaPA zE^EukeS}AB>kjOtKR-@J`0%7;scO?5Iy^g`n z#2SqQ9~wkT5i9}6H1FLY_w}11u-}|@!%AUVw zrn()>Sthw&nX2A@kcS6d*BZfVW2Nd#eEcZeT4QK$HIh{9x^hvv*9a8j+SVe=P#*)M zYO!6q?@m>ewOzMkcOfQGHlFy7%!*XgW@?548YOI}L?98k2g)s{;&FC7^ZAd=h#8r( za+yeP*dn0&ZJIq=)9^?S!{)OX*U(qvznQ!|JtJ+ED5pOgK!|D#@71y{gn2_a4W=XSl3guW=kTj`sF3!*yBovibWDM3tEe|*NvIbtU{TxU1huEe$1rt za50;`Jbjrro{ZY>Bvc;E#C>6v+V7OX;e2^0A8f5!r#LGOr*mi!`mwSEe;}Ym* zg+-Un4kv(>Kof;Th=Wo^`J~`85pxO6M^n|HIz_}(?NxM+^OF{Yt3#nLoH>cWb%Vn+ z3q}Q$O<<2rh9g)+ghx241Nej)nHt^%>(T+wm}ffB(guP%XBH6qFTUUIxe7e3{Hy=@1Bpg*l02A^UZVeE=&}PC^p3qO5iKzl3mjv7> zi^;JMRZWkox;{R9@sD5L4*$i!{KfN^U)^s95*9eX90t!yAfz*r*B43K;pL#>erQHqH`Qa`ePaxG%Ju6{)MAZG22 zm}#U(P=W`f$8e!ELm>lYL@}8Hh9|x0F#?#``Gu-iCK7k(LPgpPBgnFJ$k__NG{GH9 zLS@2CwJb(dLev+VJUK2UZl>BaMzn5tHLHn;Qso$j2%9O3sKMSI%hFY?GLz-<*}LQE zrK)=GeO=<1K8F(4W-QXTHGIHCV9hAQk3m_s^vdGiY}+cbZ7UPyh)n0!#JUJqc-(hg z+SrG!%Ut^_n#Qa$h?S0=#N=|VahKL<98_NJP=T?g%Pf7k(lNL<^xw$4clpgXa(&2j zu9R)1zOYCg2d^8e6;XWnR<92j)b#BqweQl5+d?cPOaqEUqrPzOSOU;E$WL}YthAWK z+}Rook6Bb>FEv7vTNQ4wbQiQ$RnmT|agZvph&8%Bv1!JDH1rMijGb+j+a1 zP7H=4ZQ@LeGmsghN}8@=wAL1(#=fXtC2tG_S;So62vJS>n{oOh<0|^IAN>-dnoZOm zDl=a9C9h>zGbD|4A95z5g6*OT>b)`U;eHFPdHVgn`-O;xI|hCz(;}agokHiv94wf#*)n1*7XMBjG!Dnt+nHRy#Mg>^MCovdU}3(|J1iNPj6{uS~DEh zqEb=S`~J<#(^v05`?vo^ef~RH&A)nLrn=ubiBw93_MIu2xem9r#kU{ya*;Q0#+P3T zF+W`McGK&YNkS3((5;gvd8B9D4_lY?8vDSLyIr@Jk1y+WMY2?zn%Qv-Q1g9+nNHLh zk*X9`Q$dx%s^$EIrf7pV)*iK#kk6>8POjeGNNtkxb64% z?}cc6^XSN@n2DLJOCAR?QMk4~c_L<+q)X5JP8HI+EKA&8$UW~b+=R1g3~5p%^7a$D z-=uZ!y>2hOT`H25L0&E&bbk?MT37sA)LTA1QI)=VTes&r?$UeRcTnX7zKinO558V& z>|Dv~#_PtdQKA1xXDY-G66Na~8VB@BVwKi`F{0K#M2y^#x867NrUTHeCePddgP^-qg%y zD^?^?vM5-dXBzqRBw+Whsz3jWU(wkSfboGQGyujxIlqIla;hY=u;&Cg6B}yQL8I);m&su!{zU$}BT=gdk5Io4ac> zbkxFimw{oCtTiT16pKV=rq0Ya0aTW#ktWK3BQizQM~K04R|4`K;CVSC<2;R8lc(mx zN%(#^7MN->;ZefGG_azCL6Av^*4|7M>9VHU4GkBxwC2NAaX+p}x2amTH6l(a5N)>>EXODif%z9;w;Zh_f$1!?uFxr=wnjFW#pQ;&pqNektVuD^H=bjMMQWj|)-QLA2 z!-J@pOI4_%nHbhcOlaw3Tr?wyS=)EH@b5s7AU{x@0vcBl*eoCyw*{txOm}W(%c9B| zK`Lr?b}=KM;H+}{@T8`wwpiXgjOUy0LzQjWa26o1A*Q|$RncvOiMWb#jLMY0$h1Q= zN~%m&coIld^W~ZPs-Jv9?&VJFn)eqt&uR?0K2QX&7pfvtSsF#qy5@c7WnpHD;B?xq z8D96hg4ChL=_EqSLSq*-Fh$9oPG$x95Z38kQzPN&MO&5}F6)MJKkhxBKX7X#T(=iq z7Alf;Wl=hI7A|n(B1i~3z>1P(A(0w8Rf$P9Ot#p5WBLdOZU#X9;)Be*1Z$$A}JG1~K{YzAVf%t5q?`qg6AqbvsSg34xG= z&mi?2cunUG)Xy@JtQ_vkvcP*#K-JAe^7Q7z)CfyMM&k6D={RN#0rWewD$ulyL-!0BzA*BO>=CVJV6TYsNt&gewJ| zR}zp>Cjf*dg345mV-!(1stthvEi>VqT|UPEYlT=PJTogItr?b+V~k>2y5XX6+>h4G znnc!pKXiJ^Xl1b9L_{yB24bSK3kGwzm&<+2VFzzW@C=hCe^G zwNX}v+tR7EyzNYhjIzCX6EC;QQ4tJpFW{oB@zN0Mkk+tcEFXHk=KaPQ^5!k8v2clr zaveMOmA5tTJLET3k;|p_J1&95RJRv>^OlGzoLeJz%77=K#!(R>N+NuD;A1B=0a~M~ z8oOMtpKU;O>}(AlM7B<96hUg0!Dybv{I2?)CmKs) zp!a83oQR3s*&0=)`>AJ7jO;YqWzI6KsXNRh^ejyKCy7MDQHFUqldYZeGI~0gM9N)Y z!Xwh3{l%|{Nm!vC%wrdRks=kzO1QYrUWqgCI?vy%ss=(}<#{uk&03SrmZ;F3Y(LOS zBlA@;hoX#?D++?8wdJz-5u!2<=Mq-Q z2yK!{aY{&_Hb+!d!TCeXBI*$+CxmoXOiiWXXr2N{^~DfmWkI{~q)oC2Fb=J^v5)n# zs;M6%D@AzhM@F`#M`UF#Yu9Gs5hMEAQAICQf{sKgs?ugu^Nua1RiwR_7geH(xW9eF zVtGIO<%P`b;o{HFad><82(TALO1E^^-YAmH?BStaUZnLT5>xI=rPCPxa+B6bm?A~F znCgBjVtxAwrRVdrnALE;tmMhZJ^jw2vMh=49&sMKEGxH#x3ylLwJ)Wuowf3!g|x9M9Xs`zsY*R!B_6=gWJN}CHK2N zyiFg#Ayonnnmonmn6@};+rr=E~9sEYAeGo#`2r1Lja5Wx(1obqQ#+_^M{H z+BrbbWGl&8ea$c z07EZEn(H;-SSM(14)(?HBw)LUUk7birb^Qr?(-Eilj#h0jDie_(|=}sW;PvPo}@bI zV^EnAVXyPO!gJuyQWaRohL4ll|H@cE->3ki0Vx?${H#5kZ&*gBxU%YLN44J4gIPSH znes^`u0rW80%H`BSX81e7Q-=OjfB;%XheWCHPh5oNnn51xnjaajKkBqQWl0ad@^8i z0`MfJ85JIQPpnJF2v1zcZ3jy*NAxL4Pj6gC06T^KFlI?Vj~Eg*d2Rm_Br`Kp)jt2- zA7x#C_|rfA`uATj>nY>%>-`D&&x~k~4#cw~S#nN3`qx(+6c^tZ| z{BoNQy>LD6(j`YoWggDJjHy)>k6@2J%G69mS*n zG5Z9nCJL3(eIU3r_CrL>nvVgdIZEhHMUh!mw++EUHN`+9Dt-*S!kLvs>e$=1WF*B< z((UmIdP{*!fbW-*zud(n?{|Isw#MMr^!gaLn_X5aj{7rLiEzHW5JyGvvhemm_h*XW z?INmqdy%E*a9JAwaq;v%tGIW+-}G{+W3U;i)Hq~W<9?TQ&Hc`1+*eAk!}a=DK7@$0 ziy4J;2KR-SDO|2^>NqHz+_^6lNknnGQ3PA#bz@a{oY}1Wphz~Wv5R#;4%r&VU@BUc za;FRymDhJG5s6g>3$T2kgJQ5vZe32l1HWnjL@b7TG;)LZ^k&!shc6`Tib8TPv!aNw zq0#S*ZRhs_hc`}yxp~SU5&hYZel@??G=*xo)_4wS&>v4z0W(9Jra45Tw`y(;1v4z6 zz=`JECt6U2F~B0Ou8DJ#+aBa_yT5g0T0c?@NWT1E<(yF61ivtSfM zH&bQ^^8nrx<_BRWnjb_TDYA+=h6|wZ&`gM!K{)be zMpY2OnR(`}F?p$`C$POTXGUV-`+h7-o0O|aYjap&U0Q^pRek>wZwAzI&j) zYHRuQB+W`1E7N|ka3=H8cnl)Wq&g01jiJ9as}Ut!K5X0gdZCEk+8D817ZvrxiR;)s z!mT$Z82n4jirJX^(u>*mAu6!SW{{aN(W~x(Yh_VIR_oeKVtDVuR4QDRt*Hqc;txDC z3X~-(ZRy8xz)NmTa0xpOU$3i*W>#MehI{vD>%z=&j3QpHTe_pE*?RNg+Ix>NyOfLuWmOM$}IWp2aKt6S@Y#ZmzM6lUFd#ev}7!snC8n1 zscJJ~0jHm!Rq|<#66R$ksv3i-(g$xF#uZsNF=JwW`w5BE(+9=LD?+MNSpXYluoy$X zrpjaI)}?o_=!$AmOk1n{mhPllSz-;v-meka>3x2ci2=J;J$LidRL9nth3K`mil#Ax z3KJoMRuJDwmBr|kh?&4{o98y3Iq-mp^*`;@cxbO%cJz0v3u5F;|hUmv8?1Z{PgO zzg)*NQ|ZG)eSE4BWJc?jUwy@@@pRX1q1Kow_ra~PaC`GuLKRsavaLjv$7t6}?FY3k zZOQ#k2{oXq$ksOQLzqg$J&yZMOl-Y1b(68#aml;>`{SJc%pEDT~QSa22ia|cmUWi0EXH=%3ul9bMg;*n`HDaMG z63#IgUGA7B&tx)zdk1;I(Gy{mi54yvp^E9ZhE`1Abr7otCU=%8I;YR^87Y+v1Xx&g z0*=m!U4QzcUrzi*oy6Pom{c_!E;;XLlOKX3l3;q9D@1`>UGvdYJ|#SwrxWRHw?h9R zY5_`h_LgJPOPuFLpqo!6S9$oz%5yELGZRQelqwt^Md(^dRpS$)U-SH$xs{Am)k>iF z0)}y1Uea^^KcGwd>`W1170II4hHNzh@*hG65iRS2Q5DDWvi7+TkeQ4ZW-~nwhkXS> z14PSMICBoy$nO9Rj7ZCr4$gHVF@+~A8R=0x(IV@*5RvCh!T5m=bu9gvlBz1oDGKb( zW$8sUj!}_Wb-ApSDa3v49?47|X{M@j{uZL571_6?vce~Q1vD2?@sV&W^rb`Mx*y}i z%l^}!{n`HfbiW--@2@p9ao*AhofD*A{`KE}_TT**yM0Z!z4|8a-qiaK_3$=6erWIB za0UaOfKjU{6JttA54mi0-zjr)41~w+F78DvmB($jW@3^fM7V?_X}PTEQC+V~?4z%Z zRsD$l7}k-YP^7O$hTca$j<}w30Y)WVCMMXYjS-=~jj1rx3hYYTw)_};E?Ng%VA?wO&Gb&xlty4Ig z78+oIUN|8eH3KhNl_2)vA%v5BS`sF0L~m{lf+dJ;AWuydfPB%@~j8^5j?rh6!`YBREjo7}X25vi)4(#;vH1kn`P zYiD7k8)7Q#37kmrG?2MxYgSp-tYQiX(-ujbIV(z!V~DV+9mgReI=iTtg+=w)k1Fzn zKg7^#kL4Z_qsWnjD+#SYuffIi6j9-c;{fbb)I9tc-j)WVg_vgM;n8|WgRhBZU>%j2 zrERuOkNa**_c6wPtlLtV$3DcYXx;$O4#UM2xo$loO|>=U^QFzEis+*X)9g1{Q5uq{ipx!f2H4iPORn06yyhg?=xzhE9$=2zPqYk zHjbiof$g^rXXZMF^cMR_cM`SB8b_#E3}@41k}MLowdE1abi3VEkhd;oJ`P7Wt~L|m z)>>5^hnp%o312hc!UQ20ULL0EU9$@J-KyFytBRs~tTi3`krmooRtk%lLdz?r^i&hH z7g#q|jg0NhBT?O7UY5(XZ`*OdCuRg(kC&xH$b;u2>9DBUx`;@*E2^6+V>lJrWuv0w z{RdMXw<9B@8KQdSp)7pa{C3YVbY1k_CopD|J1+}W)@`?kD;06?oL<)D;jzY$Z7DQN z`k-)zgX?msV<$6KBcY1WWs%+qfF0}d__ppZ70wxTdy#de^twH*!RrCsG15=cltP_)59;lLpcpTD}N++6<0-K;BfcpX~vGh(v zGe>TXUenK&%))BaI{}7Q!ObcIeTlQP!i+$z%TvQMV{L_oEr=cp`}X{!&LAQYS4r zIMw4ANQkLmjteuzNDIj@COfQ{}$c{bgU4jum1RX%jHf zBXU{#u^$<^E^QnRxA?YpAO7`+=Rf`7_q*!1-+a(!*g|5roK?b75!`!x+`s@;?Y@b@qJ*{8I>k8gnLemcxG12+h@`6(ANMbvoebiOPO#}5k}v6 zm|-Cz;Vw)fvOR1#+?S;t`>soC&G5y@T-|y^Lcl z+ZtoAY6;&yJooE%B1(1T9L^NSy2`Rp^{irbx-v}q-`%OYmNLM(ROfU=Y88#R%2 zVb(kjX|2W}7XI|J*mq`2_0ZF<)xq0IsKKYhl8HeN^^Ji~2j!2}y&@E@t z7h|KV?orrfW!9-DAt@e_=Y)vrlWBN>w;J_>BAlMRo1AL~B65$Z7ZITY#mxM0W)|Ub zcrh1ooV8f^42k(T4wR8M7o~Q6OcZNhe*N(WfBMO_ z+gD$IXni?N4S8lpIU{9V?_a+E@Wof3{qw(~Km0vwOHaLS^>VA*A>Vt;OmsWs`=90Q zMu(Gfj*+&AnScExro6S-qdi?jr*O1b-PMmGeedIV^G}gVp^HhtnN>8 zeE=dumbJ3d2dR)C4y1V;tU6;R$38)Y%wnx7S&dj(1T{JY!!?j~ReaKUm^t^osARk3 z?S-<)bZ+=m&r?{ifx|C9t#hw-S4f%oLaCsgcE$u%7=iVO*YqFF{nFW>ghsubC0G?c zQ!RRCCaa1_fBNHJL9-CiI#&n!8Y?grM$ritAWB79F{z|6)z+%h7>4UGUL07$f!lV* z82J>>2_4=nSYh?0&%g^HUndKHRtv#mf`8`Ur*wuXUx!J8bsAmc!eY%b&cm8taU?UP z&CD{*aC)Zh3nWz{uq$WncUARn!(+Gu-c(hlhL1Wu(8QR-lc+UoxSq^RJl5>Bu5Ao& zttYBEA%XK5hNp>B&MH-tbJL(IQUn(>A-vNp+*!ySEF>j#{mn;#6uU|s%_3L>wkAHD zsLEl}rhPw{DJolU(@`U0CK0J;As$f;4>KKyYd7Gm%=FlYnVkhRXwE57sYs8ANK{rL z2_kA=eEI&}rw`wIbN%BN-?ZLn2J|LdA3t2#E-(N1#q)jq>A(Kx`StJh;f>#Jb=&Q_ z6$_7pmGb#eGgjeco&3T502Bs&1jK@UFE>+`Vz#v*9GFz$8N;==<3465nR4qriS93Z z5k(dFpUN_ExK*v$I0hs&=pL;rE^s2$nlx2mghtCu+F3uyN<#Gb_HhjN77Nml9+kh;*PWnQ5C7#<27Gy+>MedgROBmkG;k~lOi|G=V!JS`<;B)5B|KyU{lIq zKe$_E=F5}3eM^z7R^yPRk+r(L;H==j%(<>F6d~Q%7WB;O>OACW>5sAjG{tiB1z|EqfAk& z^Gw9^!`)__q_sMBqLS9mbQ%KIN#P_4+&<7bCj$|F51EDy$ODLLwI-@a)QT$Dd$WNB zW-wKh5YFbqX-#1L@t^-<)-$PA1j5S09kMPwuGU+`+$+u=>}fY)Do_ZBm_=uo6}-J? zEx@epLKt_Ni$zs9q7dP$ndczH{7_veOvf1WUyZ;E4Bl!?^phHM((>ScIyDax=KUC1 zHFq^oRZxyT3q&*{hy{_5;UT6mCe<1qsihkefr$!Nx2YBtIH(b!qN;3)HYpQkC5Fud z0d)Mmipf1AQ(2}KB-6YY@c&J*sJLoIW0;z$4>wa4#Tfx%olPj`U0aw{W!w)jRkLv% ziR4dJYdUZ25hn_~dN;6MvPe~0Qzq4>ky*w2?yVaO{0B4HpC0HhMq?A!1+5upWs#ay z<^4E%GgBq-nu$gDb~~7ef|h0Z{TE+f-&{WX?Cn=yeAU~D_mv415aNok>%+@$KKFI` z^rt_lKmJaV#VYQuZ?AMe$dmnG5c&6meB=nukjut}w!=oM%thAJw1$hxc)4dpzg~PF zx^`mRaD*5%-D2Sy(bolLXHx}_ds&w;MkO$WTtq&Cs#;T1QLbfat(y<;UD{$X#TC03^xj16_VkjKy)|q&_S=oHOV30GFg)7QAl8Mun!uF>Grl(S`(Pq9C93xLMonuB z=}nhTMR5#K71bP`>7welqrH8sBjWkRwiW%{YLby;9WXsVhAk#$G(xwfSgDe`#r;m> z;NC^8sCezn#(or${K*gM*eQz{zy>8X3g_)A+Ztbd&P>#ms&XH^Zp>W0)pBJ@UM}2} zBDpU_LPRA-Ou6syrB$Z1M(LSsZ$IJQ^XY?LuGB81N@Fk)TdT1Ph)bDu9MoHZE(`Q- zI_>uog)5VUWGGz(9Dk(HWAHuLV5UT8{AlD@REuPe|sw&1MJ&DLqm$WtW3>^8`--)uQxCb#< zObh@B)?!2yF+dnc!9pPWds3ppBO*O=cr?V0g=e8{1aQnsvyAS3-}}wr*6_TdN3k@ikR0iY#JMgM2oV9WWjUC5vRFHOZu%aax{<9x^_Uj+gAAe7e@8r!xJ>4pTiMVOr zoGYaDjMR5;NVFnyA97s?jSx}NntyoaCh1{Kx%aW}{c`brShL%Q7i$`k$3C{V*JF4@ zqG)GnCPLvkB5G1t1u0h{Zl=u)4xw%9dS(%uYE!97GxagdRD_S?XwBB^k{-u?Y!6q2 ziTbwI7;Q24piJ#XvyX08WDtuqsgYumS@037N|v<8hx_~YYRann%guVnD|+mE5-s2V zw2nJh`F+=B6r+-Oy>N}(hg`0zRQu3nVN4 zv{ejFM?p!w+|q|!uXW$KwW`AVoQ^@Q@#R8KA7xoHO0Ez2{E?g1;j(mM&f5o4rF0Ij zW9N0HEV5Qad4JYV{v`HWy*%ricjbdrS&ic$Q4HXuR*|H_ta5!oTRd5lbt5VoE^j_L z@fykLY*rb(-`G0t_tN?_Cd`g2u#~AP#I!OpLe`aL?FLQ(PIp#>htagiteu%=HXXIW zkee&dGN__THl>NXoB4>TC{S`nWz66V(TODGxrdxPElkiDJ16TIA_F|Uz&p%gIlybi zWTi81hdBsEsIO-%G>NDQaLidE7kv6HhzaVcipGh7W+9?N{}u!g5d{eZu`X2s_C6x} z(q|-c_7j$>!dVQNf<>{Zn`D_{_duH;KqU@@QWcOxb04j&jJ0PHv8aRtvXoS1rpItY zC%0)uB6~UiN`#&VwWh?RBCQ#E7b}ve%y@dx7B3#L$BVy9aMCQvCfupSLvQ(9a z_oY=Nh)YBiMXiaC1f80wG7{wm9@Uz z{F@@d%VSneR-un^xn95e_y4K={1@*&>GjR$Yzy`e%vAf3w~zVqLe}iM=2zdKM*}AF zxbM6y@$$l|#Ko5-hnJdcEAJyl5UAY3nH5y+vT)YKTXh?Ur~7_CkP|_}gQ;0lJhj+% zG!+xf;nbRR)7~UAP}$m+Ht{KpdjUhbo|{-tp!ypuVnkw!3@TA+YcafEmK*`WK`+~B z1ufw!AN}zGgWvk@&Dd|soZ8yjV?Xra;xBhunrv5Q%Hc_6>l!b&cHJ_F!o>`gS;Q*W zYdk%Z2Q7`pAXUCT5EYO8#MRTJbRw2QM9evy)`e8-<%w>$+O~F)*xpnFbaQnP0+!t1&V<&CAU1V7yeW0swX8lkV z1TMeXBr9Q$7D4Ei%;dB7geSfZLvd0hP0lDxZUXE$%ndVftwu8CXdK0O=Jm%v{ACfT z>Nq!eQ>vwzEfx|mD$%m3qOc@0yd1)DX1-5BqaqRwS~QzpXRr!Hr#k=AuUfmc24(hK zL>8=_ul+`ayf2K>G>KnSlr&ixv&yJuo1&RT=HV_Auua55%Et&~-U2>o^pSU~$joeQ z=9^gcfZ$7-dxc}m3fBMZ=-?|6vk1#W+2@KtB*?#*!{cmso&A)!^9)I{fzj<4S^Yt1m zBw9~T_U>(dya6JYFZaCdY|57lkx*71yF9#!=ND~VTC10vFzLE-Ys|%-%-ps`N$$_j zHPfV2k^A9o=}dC$Zl-FM!|`o*_tqO%sYqYi?RM|I>C6GmElD$Ly;Y^5=T{Z3|Gq8E zWUZ-4M)Ylo;o4NYaU@ltu1%YXavnpQjl=un8XuqIIJjwFRu;W|{D_x&j6+p{_V1s5 za(w$?y{@^t2>HuRdKX9sM6utwlEsklmcEEc9Y;nmBK6m8y#FW<7l`5%N8E1Q8aJc$ zBI`=n`{CimqcRD6Du|7^YiKRa$Fsr$i-D z2{XdqYFb&ty|*?y2Otfhusi}y;gbV(nm}Odw#kNLGa)LRQ$8Gj{@&XZaL|UXj9%T= z8XS$-Lo~BjYXPn}83~01Hpd)}!y__@%HUg6PET75;3#5No5XEvDn%e!*FSuE|8(5F znNIVmNEAA5g}D9hfBla?_<#OCHvdpnw0j+Us?~NC3d!UKUL)(fM^R1^UAlkyZF{_o z+t8)w%Pq&w$!0Qc2Qjy`joZ*Bm{sS#3*$&sF;nJ>++C(;%~qgd_kdm}Jo^(=*;IPh z%Hm2@W}${&p0j}nmhh|_v2{WlH;^mgZVWbPfSFdgt0w6+rzqle2!x* zpS}~SyxnXuF->B-Y;_zphKUrZTrWga9`bKBD?;#e*(f82i&75f4nq@jYuvhA zF2$^^=gYHhD{qgot?~54kB_w9DF%2NY+C!^%O-{Lr=%2F7ys%jefzFNWL@j!g?rDo z$lK3oe_^WH9(CL5=|d55?<7>V$n`P5{(}0#%OyX)mu=O@PcWR;?U{(Uue4oAweC0W z%RD8CScLjQk-YT0--Uv-vlO|*-~nI6Jb#y!Rdc^_23yy4%j3>s7!qlI#|yfxtk>M{ zoWZO>)|C%tq1GxZ_xp_1z3(;i9&ob z0*p~kE_J=yJpdG?n({BS$zf zRh86wV*=|=BMDw+Q)MELa(7lCk>l{J8Y7mq6H$)r-K-g2NO&OK9Wp|`*;8G2mix=+y3x(`u0sN z7aj-4Q7^l+##l{@)YCJu=)*(a4{4IO!>*UQ@5DrG%ct)$YC@e==JNto(IiJI(=pI) zZd7DTKlTCP4%L}$Y3Xhzlte|XD@C?8`Mb)5X|jm2viAnF&HAvxlBgzBRTUJ41z_(s z?mJxYlV=dgW^udshYPckk1{1SrlcfWdZpLlm8t8J#}G5=OYBE~xMGUZ>ouNV3Zh?n zc@T4jkM|$+%^PYxk71vF65qbJ$7@Aaru5Ec@!m#hYX#A^4e)q2KHyS%v(Q(-0HNO2uRLMH2P!KcKeiJ5M zHc># zJTkMYy1Tl%F`SvkfFKC&63Ydb#EMXa%m4oaxv~o^z+DWcd#0zm9+?s0?)RFRs`7){ zjT(;hhnkYCj0g{RyJ|ju*tJR!Z0+?vJ5J|9{=h-xc z;^{#-3?IqVS9(V}nT4<*5`q&dJP9Nsxj!UA5sg4@3oX-tMFbF*2xpZXMd=`pS^^u7 z5t!3NoUfF5DLS>ra3Ll_?GOEjm@KqqLzl%l@!o&Oj+D%KO$;1%a}b`4$Dt zjhRNbq+<$mQ1mgvqNwPAr!*S{D@}A{F0uu7yOrj{t<+d#9ql z?O}sexv0$6ArfX@D$GU5N#wNJmoG#jmoup%%LA7s?l--hKq5skoU}j-7s2fYb6P7Q z!oVLt!b7Uq-s$5vv4VM%Nff_RQ&DAwUCq; zI{~y@BAkW8EtA=PzjF~lkPmD(3Lq@@9X=w6E{_Dne&f0XGuIluCwhXWcYu%{kM+!@ z#O;+tWAsS?P8hTnilA`R6@=J!(tr8(ASpCJQ? zwzHH(RDdEmrD20bU`E1OE_F1516oid+h#Keljn7WF$3=M&wlf}X`9GcX;!8(hfm!} z4!Uh__TxRW5e8guSxVqM4h^E)p}>Qpd2~$Klb=u7|8|r!<(eP~XiF zTGDEd2r8=gZKHe2CY-E%CLo5B&W7LK+SY7Y>)5-wvoNJOWSAjx0b8}Cuh7hY z_~FZ6{>3l1t-oFGCA&g$d|^g}oY(D7KYqS$zxluacky@sL4Wye|LKdYmCU1eYCFcT z^Cd^1|a7O9HilaxnlSII5Abj$mS-4hKMN#zTZ&$ip=;0AzaN_gHe)`N+LMs41EwOD_ zYVLv{3>d%|4dLP7^Wx@IDwWEY2b4mWCrI_}F6SlM;L}Q^(RUzXyAm;$lW#Xx<_QwA z==~6FJs`9$;6j9GTYADs6)d^BWL4kpImsqigO*D=wUL3q42%$QDWKW=7jDT~G;Wt||b(dB`aeB0^6C#uE&{_nXeEsO8nZm)bg`+Xxa)Dt~@m|UB-&l&OU%8yTe zzd{NcDJ`Ht(BiXA}nriq=jmw^@4C34NM1WcktyAU=p?a4Jxc92_z&Mt-*s- z)0RNzhwQ7v=^@6TTCv|_7-deURP@1G!Ui&-*LNy~$|(!0NMxEXvLI#*KAltlB)6l0 zNe~wMed2vapL3yEIxc(+L`=iUgv1fTjz|_DH6`-oyUrut2O*d>J1*uS76Hi^i^*Sn z_dCx3VX6@G7`2oVvfeH{FjDj`?T%N(pUnr?~0lG7zd9iy%2_JFD3UZ!J#?QQ#zWx%<8jT zdsyazaw-HRFCt|`HH*kJsc1fX2$TY`r9_3~*;4C+L8|N?gjt0Zhp?q{Q=H?0PX|x- zb_4s~WESv{^_iOVhztp-h^mU}1QJGQ(R@;sS|UQ3gQ(Wxma|6`WeQj_QW(k0&w8+f zhQj$$ScF@c2b2je#3UTV>r#7bWziJY=I<(~R>?dC6KAn{c#JkOly7FNNuVDO4=4Aa zh*C5`97J)uZdtZoMIrk6^V_#yJyH1Eb*r@;2dtL0s2pjhEv0UC!&A^~`X&0DWMj(>>bHDPk!VINSEg~E z#8D$Fn)>jh{pQvxC+ma9k7FB_lNv8;*qC2v@|*}n@)y7P=kErPRE=l37A9*vX%ENO zqp1=`;MlqGVbQZOR3{8M|9tLACIuu?${=PIn$0nEAdr$Hnwyt7I;SfzA-3-@5fG-k zhTijx^scy5U>;0L3>!Y&!lSCP@ECsBF&P%33ZmXRX2~hfBFBX2$8^*&k&5IYslvUF zpe&`$P-A{+WEE)^rUcF2@#K+;vRkN%isbebK}9ny$;VYG7{od&3`QTNmW+C*e=DC$ zWo8sD%<}Bc@tF^sqq4+^NsA93!$d?>h^h6VBH`d7Doo%sX1Jpi(V_tqWd!ykt&{fGKDzx#9||KmT%BSS~T2i z(H!uykWq=jU<3iGT-M6OeedB$%AAfvCMgnqwAM(~_WktqIBs{XDz$3bte4^*Fmw0% z_+WdJy6RGLTbA)F0<`3O0C{}iQW);nxB1LWWJ)P49lgFG>$jb6K=h*x*uP zyZil~vR_zaIWP4#gLxtlVJ)N*0m|9%@_@3?=n#$3vF#A$WkE!Aqp_iA@WPd2?{mg_ zcmN33H(F0r#I9EeQC)~4+^DXwfm&yDdp*bKM1L-(KoIqs6vEd zf{JGRuiasTSn2VZ%%V3c3r0U~-I3KG& zQ9@ct6rz)z#GDCzQiWD{_Nq)@HZhUe{5KJ#CDm=v;Uk*0u9OdwgmZ#e{^B=(4bZ`^ zIf8=+P9qN~dmmt#Xa;H~o@s6i%`Gdva{ytM zh*|NJ?Tz_2Oc~}N>`aPw1Tb5|m}Zp;9ryuNQPH&PWf+G!$*3G=^H5WHN8qbU;`&kn z$d~@;rkmT#*;SZ#Lw6N%HzF!U4-IDy*|T7`yA~NX(t`5-%8X$}mDA6u*_EYP%v@^% zHS>2XPCak6`cjo!xP;q2X*xRfGA``$A- z*|w&tV~lV=oz^i-(vn|_jln!iye5j|pj6kQTN_}|@GP}{uL`TB4EHLs-y4_7i7mAh zl}zv}QC(|}@ogIrdfoarUwyco>kmJ^B+a#VBgz8!D7yUN|NS4n{&)ZGxpc>1mbka@ zL1{*OIQ!=pAn55s4AUasEqW&pj4`@tE#Z*Lti`&u+YJt`l9AeaTE^DWs%S*YY?jo1 zCPF=x(fZTVSqu9Z%v1^&74ODb6YH+4N-4Rab@#F?z%0Y`K92pNoWJ?1U2nQpW{EM} zM&B9%NV;K``V5zc2dQQM@}egl_l=qL@llG}w(Ha5`1~VAN2yY)wVj_ojJG$xZyCq1 zy_IwIZRce{Z&)v}p2J~~HaBO>y$ z+jc&k0~EcJ2((hEVDak<9zO8p3D$@}MZp9XT27SZDa$%NH~R*ybh@AvwB2v7T&rhi z3Vb@9^CXo5!Xz1$@;)PDqV_#Tr}dnMg(*jJ-bC+%LjVAP07*naR6!Y8RKk**!`SaJ z1~}_wC?ZG-kxUVIuBS45#ijbx8t(h~zK++26!rbM|QtoL&dd z@{1YbArC)X+{eTXCjO_2D!Umxj)9I+bh#=x*iP)HQ{JGj1c9g;hCTuJVwsU+vo_;{GM9j>87ITgu0;p7N zZ4k|qcNQcv(X?O#G0R(p#ljNnOh2d2=c7f07?f)bi)eiuE&zV#O>1T~L0TmBXm?vr zH9Uk9donqHllYoxPjgN#()1Um`vOG-0aXS_h`}i|qxF$Iwsg*!M=2TGNrDLTOp1#Q z8|HPXsgmdAAv~6~-fr9Zviiu351-a0JSd{O_u=RBYTeVhIJzZ!WSDh#QI3%oH)inp zyj<_?bUr_x>yJNvk-U3O%@3q3PJj5H|G(eE!#awODt3 zJloz`Mb?vFZ&@`UAD)RN_B#_}bd<&2&^CT}0L4H$zvOj^`$na3U8z6k zA7mb?ysY64>$LCu%`XAYNMR1S^!x$CWUcV@YEEte*`U*jPUm>}Os5k)eqa(>7qTAv zoon^sd|t6PoG%muQn^aJe1_#kq482;-(f?(`Gw!N7{;d)_MK{t`<+kcBg?l^7!fR8 z&%%&`)Ja6JT`4UPs^~k_6FofP`V#w{A3i_?HdwOEVWsmW?l+jn=m4QCd^*E&2j~Yg zdA5umbtjs;thpp)j=s}+&YrtT$&iv8xoL@GffPc-=$JeI+!ZwtSjwE}5s{}YaiqX~ z7(D?*!uB!*l!bzm=5d@nAcV_rzy0g!rzQbK)=1CuGMyzX)0=#7whlDi0nNyR2h+|W zlh(*Mf{%IZ&ZzpFgegEl`TYcE1SJum%k!ebE>9@P;VMbbakfk`G4W@ z+>M0YX2%Op2Qf*J`6U5!ueDN4VZXP|!WJkh&4!kledk2Y2Hdkm5xLMSs_vmh-FzSz zq|DjJ(fdd(SXlCHR77YBHSfX@vIA`Fy)HGcIU<~O2e}?>`;eLs3bk%QT0Nrov7U+zH}gbZeR;b*K0ZF2mhXT1qN12~ zJ*EJcif#Xg-~Z_^{)hjbfA~EN(Jf9Z%y8S~(^tOTsCDA-ZqY`x29aR#>o(tM`PO0Z zQu{ul_uf6Rt!7?|?)$Jl*2~&Qz@0!DzgkX}BEsYJuvqU*7{fB|?+7NYYn3COCBmb3 zT}p3_nBr(;9v1bojIEWWf=Gns(_(aDl=Hd2UFEd+*6enhNtjxEZz5!)W0;;+V&bPq z5KAe3-}Jo3zCqO6u0>{t`uQBaP2hTKu#U?Gz0+uPy7=`9ZzLMqMh}m^ZzKX;zyUB7 z1%Z}@GQ{ki!UIBN(FQU6?TyZts4MMvv^`lrvEOrd$7_vYA*#RlWxT!ex{}stJEg$A zZ$v?(^RmRcGO_PF6`dPDaGX{UktpDPdzpCPir+!L6xJg!e z&v@Z<9>1HZlUqg;l$N_nXX0VwpFXUzQR0||=7KO4y@(LvjE|*Xn3=ncBSAP5Ix-Q- zN5q3UeAbF1z_U0eLntvv+W?40xO*f?l||ldF?bhf0AbdmW8`Qov)bHUg?bF9E4U zMg);hktKC1%JswOMm*nWqB5ERp+%}HA+d~+M2`G*C8E#Gm?x(xV~u?uq%tg<=VyUu zsfCC~@77~I)d1Q)L^=J0r)A0WcyyaJD~K$FDI%xSn$DKI!Hi*nI6s^;Sm&XgS7a8N zdiu+@T~6zV^ZNbgFRIGN?=vFwblSiF{BrN#{dfN+{>y*l(}}h_^&xffw=4C*pS~L1 z^zi`^nF%Ep-*+G|5CADz8?YcGy9Ol8wK8)IBO)#YP+hB5y}#X;T2;mNo-=@T6QSk2 zoGz<16QRUOGhp3#5>!an8e;$y=DNttB`t+S$L$`3vevO}WP{hrwe-)gbXpPaZu)Q` zA0%vh&pi#R^p|U{2V-wA3pdy}Dtv;LittSC%4vmEvd(ougtv~u41^Ylkg$CC;6MFH zj}NqW5RrAi-FPkb_C^GV5;!6nnwkFqai!w4C|; z=&xV=ev{`9vE6CxAR%J7qb?{F`S}_xdgIIE+_pue0!BorOW2@NBb+I`Z3rYAFaH5H zs8osoD{}8wDtWSQG)9|R2MaOB=pbg*qsKLl^M*)DxDgY%5fCC-FH48&FdfSC6!P4? z9f5655IB*71#UpdZ@>Fp%pWGnok#!$#XO7MXWB5bgzFu`@ovFOI1|%s z3Cexs><&)PiJOV!a7Ubg|7qWj$lD>REKAmZT|kuTyrrJM1ZPLB9+0pyb@u!dxWSYO-ZY^w&VE2X+BKhCH;p8 z%C2-9o`qh?9p3IO^!RAbF;jXXt@q)^ERw%c;n9a9lG-dRKfPWbFXyLKKfk?YXKn%p5Rm2k z`k#LvPfx%6>u<*&|3F+J9L&5*ctr0~biCa_u-BWc%eXiF_-JjE^#GDt_^|X&1W{S) z%TKSvU9(8VLe~2B@;18lF?zR>a?|9+lc?AjNaH4F$llURs)ej|eK>O|F$2T31n#T1A9vi7}#eu*f0@Md=YG!yRlD9ot3E#X+m#=lNC^8*VR60F zve4-qFE4VrfPz$@h0hm0KT;|7cI9QI%OzSPCW3qGs3p?}yi~M?-gv38H~R1?_8TR2 zr!G{NV2<0Bil*rnTDcS|N^Wu6XkAEav^%UJAm=m20C9{)muH9ufrMkf14QdN!pTPT zL8&>|Am-S27Qy9#m~-DaAI!p(9}u|TuURli1TF=sJlIJ1ClMd%=0x8(O&Ag7l$w?* zyk|)ev!5`T)NTncP2(2;(n<%PVb-tY-YyJ$088`80^~1#^Ea8QOAVKa!cCHnl;&;b zcux$bRKZ73MFk-DNZ>}ID5jcmNG&weEW}98gJW{!V-o8RGu%NbnMDT4yUg?5!r&n+ zp6L}p4}Cd9-XJi^OrMaU$8m=T5P%%glx(;Zc6Bas1ipJq4Dz-F(j_=87OM zv*wP0Ybi+CN3+zjx+L! zWa9!_%XZ(>b)YQpY_E4>E{pWNYtbSSfl@T#N@Xc-UPMOkqC(8Qk7YtvPuGf>$@%ib|NOtLfAbg5U!CmFf0l;_TrPOM1`9FadZ&nS-MEUV z%EPI@ys=h2pT^eQjMt^E#hU?;rH*E$3g%{qv`Sd`h`JQB7_HSsMOdqJGa;&rs&Ist zq9Q^F8-q3146LVRyxnvA8}1kp;pK7$C`(AKHIyaGtLoFUy}kHt3pfo!(xc4ew1Ob( z3Uewo1h#dq3m+FbJ+B-NS-6z&5S184Z2&>fPh`WdH~Hq5*tgiXxL-*muZ&EF{f@H0 z4cm<$AMx-c>xtLZTVsHh62r2I0?W!G&_aEur>AHQsh@<~>kBdS<-y)wsn!^s&JPfU zaO|7+A!bNPERD-M&bT;C1EawRMU$}r2!2CUml3X&8QZDBNb|5jE27R`4OX|u095A@aYmZBCXTgm58ZmnDe?q3)=qfsi0*= z?+M(Ptyk_O8ieRAEz()?iZm(vpvVFf&tVh@)-tP7A|k&l$^j+~c7&s2Br!n~2EtiH z?A{w#LGS(r0wBNr?ssY4JMz{u1pVHYgaZ-E*`SCiCr<-*J~O7qakN&?Crkngg7cj^ zOT6OXa)`O7A&d_gh-13b2$*Q3_XtV~L7B72tH1%qW)(lavuTlz$f#j>sA%t<2r`LY z_C?XlhJO!D=>@kJA zsMzqlUa&}(RK169l40)#6#-KpRu>KPpp15ciJc}=RfOH0S;9h9vPzYRq^P@>QdGtI z0Mi%&f(ZNYDLOH!uxd#ebaYScMT8x}!W77OIuW^{RO!8IDeJo2?@d%-ZsE&PeYhnU z5b)HCiHT@EEq!mI(z+3aFc)PemLlEkywp`ii`;LGRlqaqE=5@+VY5HH+|HNf)8+Kj z=U0ft0g8(7QkH-C|NO^K|MvfM;XPj7NYrnQA5V5~G@OWKt-kFjHAK0MumR7Odm&-F z?ZPBQt-JRzEY%~CrH*}wGOPM<72zVS^<^ow*7VZl8;zBDf^8u}aOioBd0$DNK8<^4 z0SkNYWR|E{62a+I&ujnkriG)MeEk(b&8?F#X<;JoUCw7x@!oR;Gk1FW6mM^IS}_bNN!(%KxW3S` zPCG|E(RL*%QC5r=X1pv%%M(aaqX-gBGiVBT0YD3~29PCJ{Zy7o+$Rb!v4EM&g1M9P zh~Q<-f5;DQavP9by_}#A96m;edyFJ&vIB0|3V)n8_G zB@Tlfi@XO5)4x2GUrE9e%#4L8E3YWy@5ywQxnPVe@P02PoZ`!S!Z0_1Ec`xSf z%i_F=h0EZ$2zz>{32h8d>OaHX)3`!GqSD9US%Zr{x@d_bAJ0UYZ_Y;xF}1y=H&Jw^ z??i>bI?2~YL~co^AR_F&Lx@S-y{MQ45!Ir@5;_kzS5;LR-Ezamxww=xHThvtN0Ny^ zWHzS7Xl^X{=Oj**ZEFcavTn83T58{$6tyu*)hq@>K#CC4Xm&c){1T4ndnd*)Q)W)m zTcA|6VFYZgFUzv;9m2Kh)&>)mqT#RrF>kkCs=nOYd8r>BPv3ujP2_j(AhjqP|Nh_q z=imOn{(JfR|3b}ZUE=l5LXkwFQlfV{tuY47lBn#%;T|5=n`jC5QgZ5Xu3FaGu6x#A z+~2mMViC1!DQc0BPcBvAuFR!WYon}6L@7 zxa}a(%j)+#dYj1$7X9^aqmS{!54x_wOwS()!OI%;B(S)?;ps!PJw~Tmcv%VL+vcw? zvG24LyWNS)-2R!7dwLI07recaaI7Z^2Lq#X-w}?}Ir`wLvF&if!!r#h8^Kd_BqpkB zXra^DKL5b$g|rX?QUIp&Biuj)F2q7GSb};$0Lnr(FgoSAnS+}RfT+}Hjlu%NWg!rJ zkU0>f3kYaiB+!nDrKILbEQjW&eabQ!uq?T zE~{_n3@ZYj?R4)tMi3F}G-NOl3u(zT`OG3}kzfD%ucqOE(tiy(s!VdHmm)yk_lahy zT$buE^FE5|aCe}V(zaczUe2dLbnBdi6Cv_&H&U5zR}ODOYGKt7 zlI4_vwX&{Ui;rEFm5YWsm>JAPPzrhvI8(qzPR$u!&CXyuVp=gJSGWNVQ6dQdFqn;j_W z!LWfC#1aWvoJ4y{vf8_k8?|OCH|Oq{=b?a^>XJkb5PRx`%t)`R8-s{*=V{aHMnqB) zXC%M<%|CZfc$-fV;az%8BUqjjxd@8)pf`?ec7l9o!eu6~XWbm8CX0mK#dHn_%uGwk zX^4U{Pk~9Q%lSPH{d(H4hb1sO&kN27z|mzmg`F{snR2#O5f4uaNe<7-l2sQf(@T(* zDz%jKZ%kb3{378NqT=QR(o%AdIuRL#nHUkC@U?(sm)$TV6d8m`j?S3hEFeeT2>{7O zTt1wRcD^uYWq{l*+ideWntd^KDZS4!MrFa_eVOEkz*5V^T}E_sN?uxcOY<*iIj#G> zrKM4;+89ifIUPPs>sxCHmCmXG(d_J+jVJjHvxus;ZLhV8YCal$c!q%T2Q13(Fa5F< z$zroCdyG&Ow|IxmSunVX)7`Ca31#^-pAfZ1>myQ{(1vP z(EN;(I}65rcXy};AfmS2rAV|+wQ#8z#*4&#=ku9W<8~!sEEiEQGYS0r?@85rAKO-+ zpX}`#ePF3%9=A8t!Vizs8}4^Ho#}ExYkt4!<9Yn~&)%AzYVacG)A+|fuu6=M(dpw? zI9~!J50B(dDsj6*BrXpKCQ;NS_t>oB!|36O#)h`x@|0z)w489iayXV#gvSqm;>Sl~ z3J?ACmESg^K*@U64g{_*tZ9;Sh#-t8#*aUMh1WAIVQ)`pz z#>clil^Pk8fTL|xDyEK?A?6Y`D1egkJ-8Mm;;v{WqqA+6<`if|X^t*F_C4-3o7lce3&o-9u1UMbQ8v-ZMH5K9MyAVl=|GeygJPrTuM>#;6e5a>2 z4?HbNJ0l?u%eXc~~Cng{Mi*la-1Iq0YfHmq;1>P{{`9Ic^7G^qxKnCXPIlCf9pH{e8Ar zF%yqr?;Yn%;xN)c5_*=qEWN%wSNhg%StSrLf{RrT}+5W*AgLCloJ z&qtk@fhZqrDk{RNVy%<#z7I1SV}K&PdS=E#Ri*D;gn&>cc-Sx&vOZX)_p#mg%jFbD zk9(~u6mAZLd8|vdKFm#2nGhCBEpK~&JfEJ<%MU-jstOQp9`5CG{^5W5TmJ2LzxvhV z`29cfn&ou@(Xilkv8|I2Dmq@?`1v_Nala=Mq_-iD=XSeiIcNesWUV$V%%!jtY28%0 z6!kvBy{?M@S~wdTguqN%HCwdJhOX6H+h1Sn<-{yhikueoPQ-FK?OU^LCt^9T$;9Dx zVQ~HCD}Q;FTC6p1gX+TPlU?u0Mu-IJw#KI#B=m5K+r|$UzwdfEQ&H?4y~+9H_ZvSy zlU8guet3*+gAlJ5^g&f4NPqP$Zm(g+Paoyu$JlncoP&i&^FHM3Uw}34SB%aA_?YIW z(NGFh;hhlJcR4L^i@nkF2V%ngHsA3{|EU#yC&0~ktwewYF_SP_Ln&Am+V3;+Ru+E# zDs0elq0^Zda3ig;-62Y<5zdcK{`LwApM@`#-09&N8Itrg+X_8?gmvtV&W~Z8!of;q zNgGzyR1@6yJFgcwA`A!?MH-uaPLydG3QHD`X-MIG)wrkqzrp44Mnd z2^391ISpai`Q5jL@EOb6;19_K37s6ahq1`F-9s6Suvh#*`rL6n5SBj-=qzj z+3p}_YOUvy>24D#GJniO>hKtr&emLVrhTQ9GR9yP3KW%GPP#h_7gb6$M^P1?bl33N zxeoNan-hb=EmHorS!>|LdDFremiWR{)pZtkQF03{ItSd@N98k^7+}U| zBRD9Sm6%zJSob`|MA(LB{ciZY&@+guWD>;9g{3alhSw^k*4~B>k3gv#D#xTtLn zEEuh>OU~}H7HdYt8F6G0aC(v55jJdDYK)Eu3erNvq)g^!7R$NjcAQoE-gGTt&WL{7 z`Eml0@0~=si0>mJWGS(CQQ@WJItk`{UWveNws-8ioYwOEgzn{Xq5y?aR-&lTx^U`M z=QE8#-ZQpEK-_Np_{dLB{`y8m#%+`5$GGp#Z94sM{ssKDdU@hbZ6`!&%vuaAfZi8#m`ZMXr^YqLAg<&P`-3yOtgPGnpM@t5K*&%<2Ie-$!u;!R%#`&@DNT^ zbt>OSiQ*WR&!m~}U?w7!>Gn$)>5(RxzVErz`q`n5o~wMG5I!a5eo($Kb!#u(f@JLqzBO4Bx(g2eXYAMp&5T;?KB2t9HGo+qQ828BkN(km|!b>!SSmQ<*QsyH`9$s%QBD zs=wVSJlamniDJa{Mn#~EAPxhF)|GsaRxqLM^!Oa3VRUN^Q5cb18P;Xk7s2 zZ+_+b9^0LsJ}@($p78dD{S~^{l9%&JPG+G+IvRd+c{!A22#8N85s!mNje; zGs=>gY_3a|l;*ilT&O73N>bAeM@*B}Kh+YVRDiRGFb7W7oMcmI&YORP6U%gW5Wf$P z?@5tq%$@lGw{-z(iT* z7d!?=&(&XU#uB}hzL2CXA11gN_n7nlv}*9QGV`oB$u-d!#-y3jN!rn|a{RfMWSC`V zWAaBBU! ztPNxqce61_Wyba}7p>+tx-GS&|8M$vnN`&TQ@};)JV8vNk_pdL?rL4)VAlZ<5jP9Q z7#T~wzXP#3@?|{ zxOMVy2YC>qZ(SE{TL%+@`qpw20a4YgA7d@DK0U-RQMKXg^CfY2WnJukXXPx3oNV@C zM1-W(sX&;PMT>eH^6;RSQ|#S)0}}=7x`2i23U^lSKYig(AE^}F-$(^z<;w~C&Y~2K z5S-7@VsCdmJ;Z&B2!4DF=KkkD^Vh#(1St6Nna&qnUm=9PqptM$fr!w$Jf8h_r{_;` zeI+K;LZU1JbGn={28n`!VGyOd5E+JtH7bJJ6?Khmr)9yg=o_9svTAq`f~%r;F6trt zaK`5!=yHZTMk8fX;q${Ri0YX+G+GuaHJvbgd8FPU3>*IXg+#*)%(R|k+gOFdV1v&O z5Q#oOOl2Xh6cO!Cr4Wnv#^rsZsNo=DXvqgZg(JgNS!&7xa)99;tpfp(=#5HwpMQOf zc^2Cs780c-Ph{Q=fIDjz$P-1jV7z_w%W z{pHQ}CcnufAu^7flnI}MXvDGTn?c)YRgJ7`a!_#6{W<%ja~XcIL7=;NSckD zloQq+M*;R^*S$9b@I)r%HmuBE9K5IOGIN;ibCee5DsZSM%`O<^MzPkCW-68hi_WK@ zzeiM(N5P!ch}qj>!;+qnjWdLddQY|rrM3=a?RP07>TG(nJt}yE~>zU zl){_|dmDM@*!Ff>il{cTd`(X#JNL~mRB`om)t{^{kd7D@G9Pp2>c z@WXvM{j>k$U;5wwA+`Y_uwYn#Y;Wc1!SDA7*3)9QEz4i6C5w`{ma(;3we3Bdt1gq zdZSvYuH*ym6cI(DyDmCDf8naUuC(oZzJNHkCLf;dhaU*8&rfl`6EoEn0D16|8$58S zXgA!hh=^??)zIQEKcYAO^mUNZz9AER6d<9t!;P03+YS-eO7Pe=S`@na-l%X^Ry!9u zJkMPtU%!*lu)rq9hZo=eSiM z9a6#QgdkDS1d7J!BnlB^PsQkY#3SF;>Ge*fR}@)=#d;(PDZ(Qi8VPbcf-OX|KF!}D+e>II6M__TT)?=EE^CZPV@t;v^ zOd7;QuphOyd??Xb$(SgkV`)w4wVK=vkW38Ilu~jY{U@YTa<@auiP1JDpU&%SHO;q6`ioWE zE#u}#uWv}cljc~A(|I*>3XEat5Xqh6+$(_0BMUB5z7Y{&wm#~j)=f2^zL6Wq_a2!v zc&LgNkw7WR!rsR;kGtp2lbE!WaA(YivWWE7RcNWTw^7#e`gY6vWvQBK7?}30EsF*r zTla`ii@sdtZ(R#fu|q3imm|M<7{*T4S7^Wxd}aotE5dk?0_65i7~tJGA{ z8v?^ZPt}LH85b4R-g-KYR7C_79>D4G6us+O(_`dd_z)3h3SwRAXe~XhAyv=o*t;l& zb(phC|NO$2GZ&rBLHovQwSDkqr9}A%`Sr>oAu7+0QVM#@5ILVt$cOiO9^d~7mkZo! zbPwPtoc7z5pPmSWm(RQ`R3-KgnQiQZ^g`E)@<^aeA& zJVa|#tSHsDJFiRF2oi)*DQ=#ZOR6Q@;SMPfg%9Lm9RWAE&-21aNbhb%!w=7)*JeK0EsNHhnDPjFp<2gZnP!}X5Vaoe!(=qNT zlEmZzDdNMEPfy@?dvXR_!o$qg(-Izg@07nCLaoJYBnxz+ssj0R5Sl?!Icn6C<8dGa z4xg7t_GYp27`eeSq+6yNF|X^=w-$+(Iv617waC=I7S3I#MJ!7leFQNvwXI7L5$U}P zaf085d22(9CYhz1*)T2AyCZ{FL|{3s3p4h;tBR`Lx1EHQRfV^$L1@u3hINbkK7RSt zGb4U_yZw^@%li9&|DV74H~;!+`zhMs#|Ksk52=NP(^+VHPl?mImCMPu-TH`N76Q<_ z5kf@TZ3p>ieZ4HY)cy6!ioUm^BBv9rWnAy|vO=_6*63Dh@o=dNDUY|iK0l7FX{jv4 zrAiTR4Z2WKSfh2Z+olia(R#G5=d<0fv{ZmtB;Kxcxx^UdSKkCA_ItG5?{|9sK#4=C z#kQRlLpUoFBCa?7?l*qDQQs+?tH%AxBK-IiZ3iNB;l~TER}>8vUKe}2 zVp-|ok-cHtsS5T+D)IJ0OjMV+Z=#BqFO(<7<5O(+umK;ww_F2KsaVep4-ZQ3lRJur zl-PGZpNWN*GX=tWu!5X=x9c0cb19*fms7NcQeh)C1h}~kfOtI!dW>Ek6>N`hIz@1b}GZ>Dnf)9MofF_!X(Uj6oy*>Zc&6sA6m7j zhP$w6VKWy>G6}N$#kvuN4;SL13}PFeT&Plb-+C>YZ2-bV5v9x)11-|`F|pUe>srU~ z47(6;zqe(r)+Y~7L~2!pXMI#}Js+!m8-NS*WnBS`W~GRYYy-%@i@sd9uRlEc=x^68 z=j#kg{KMb<<6r*Y{_A@G$-#c#WmW6OkC)i?7=ufRwp;7vRK17wAyrA$+K^fR-0vFz zE+R$CT6|a$7Nv4p$JV?LAkb{NoWq?}V;Go8nT7>H2&C+{P1Xgq<{-?fcH8*0g!LFh zo}TUHW!(3&6saXVV3D|95sji4joKc4FmsfZPA6|WYmv(X5;XDnM5-)>wA$@TLgOyp-|wCXY|CZ+tq%KB%zWTlB%_ zQ|x!HB|NC_v_8=DbM%hog!DP6$mIc|R4M>o*VvogH-31G+Z}5mX1~7C`GJI~-Q)TK zYbXn;AR$v+`0*huqO7zO`|=|diQAP5`|B69otUU9dI!nu4=f9`qVI^H zbh0v(+|qUf2i&1$w#Q zWKMHiM#e>ng}^i%w>w?$v^8!W+dXb~w1#aTw|l>D)<=Te+TLuKne{g0*T4F!!+D&` z`%IEYs>JWd;^EmiZq_&eNb^`fwtzgr$M4#OPwW;S&b+y5%oQY8S0v!=^IBr0}zw3!$@gOB-L@a(8Hngy?(^v+x$&L7Kz zcv>k~GC6|m6UmmGuy_}vbBkq`1x2aM6? zEu$Nb=W+fn0)8EoYn-E;ag2r!)4(3|iN$Yl_z5@l&W!+esKH^j)vC zuHlY-&vIe7SvNjk?>3Jl$)9K>d8?O&A2U9YSPzw5n2wKj$x&$i;MP5Kt#88bfD4kkq z%*ecEDa3m4CmiOa3q;*I0yvOfxsFSlT}-AJBae?fDu=TuEeo+=?{U51w$a|HwYcAX z-($bS%vvAU>wdkOhjp_yMjvir-FokP>+;>Nf0ti_f2PXuTn5&ljX7a8eDu**W zaO8TzLvpW81kSGR?9f6f5_Z%=zK6(}dmp7J6T7AND^vyHdE94XVVJ9?UwncA+CHNn zJlh^6dw$>5r==D%AKgS{sfCH$VV>Sctf#ei%TrjG%%Z4rzWyS;bxbd~C^ z-5?TUkPw|Oh#(j}exT93cRrt!lEL0_ztVbQ6}Z#+6x&<$#veZL2!h4+8m9|AK1SO} z3!TpCwBxmSJ&_GwPvMUJ4iP?`s4Vcw(3qs35PT5B-mY{$GYifSxZgz) zq+Ha^v7WKtNd<5WqsLDeokqv9hG?{n1tAjqjtG*%PanA!0DO7~>-_PTv0YIYyT4MH zT%O<-y%7_npzSfbJb#U`!)z9^3?nTO1|mvK1%gB~%g3dVmKcMy%*(bp-g!L{d02Dm z&NH$xZ~i#!a?na;i7?7MSyo-=*v>o)!ds8mE8gyCJ*5n6TWq_xHg30ZyN|uM`#!qa z@V@und+(!ddmm=Q-CVx=^)W_aaKwOpSA% z=6JuE8!S{yp@8*)X^14z z`LsxXl8EG5l7k}0mom9pnaZlF@bEk=iHmS4xuuvzmu(*+tffrvn-5n~e-%p3kS>|4;wv!@vHU57lDZ)6?ff7=tOG1!e%3%gM*cb|onaqEZB77@UQD zAEj!5%evUTfkTQ!8>On}C~mGxVP#rRaz0^=^C{ z1k3p}l}Ym0H!4f?MsR=qkzphPYrLF5iUdCp!;H>P=v%ZK5!1RN0BfWP@~4VD9rukm0quO z-?2ApjrKimH&1)Uee3()_ulu`_U6NFj4_4{AKlE{EX=~(<=5Z-)w|>xhq;1?BjE1f zDM!RH3uk$F{uk5g?9FH|%w=Ku>A6)8Ge7u89R!Yp7XCdM=Z z+VI1}&XPHW!WO>o1E5mX+F+Fwk(hNU3J)P>p`{7H(`L!$u zHxDhGdYHRs(#L|ncV4?G@Qm;f}UL1S+IL z%Yw@zjUL_j@rlV0zB zF=M->(HL<5;m`clH-5d67JB-Km(TS0g!2XW8(97Ffa@CoI$!*Dlgk=Z=;6t)FQE&C z+m|o;^*1prMk7^l!Dz6K)}aa;Axf7=YCFQv_7LUuiPjZ;=ZB}b-$4OZ8au5Qh`>5s zK7a`x@WGcSK%gc14k;+7*xzVbshkK##Nh}L0$~h@gc+ppvc|q;aS22T0kag%lX1+Y z!kr}X8%!(^NrM&x&;kJN5tb5ZR>uNCnG|P6Vjfh973Ti>7N1{fYcwp(u9OVaRn^%#d9k-yWX(F+Ms{ zbG~Ncm}ArQ#wEP;y|Z;@rjI@0?0Bb0h(|*7kMnohxka>(E-XYb4S6D2=b8>#0(p2G zHM*0|JfnS6-Z-{&^zO)>|MWA6kBHcZrvYPT+=U58k~0AydN}Yh))}#^m8Z*)-GW5` z3Y$L(Gn25J^E`;@=lg_kT8Z=f5Ea4<`p0Z^MR@pdq9`geZjxJAKvZZt)+r2@MGTR> zpc=rw53L#wDdILtDdv7yfLxSkGAIrUAd{+^nGFjwF5LSd=CE`zd*YwmO@!S{B>%kU zI?e*B%uXx-p|_DwvaDh1qmxJL0~9_ie{1i~JSj3UW<;m8TDN7b00~oX&BF3Blf>7g zwbZq^yPNgimr_9Jdn009GPp&o4ApPGe*W^~OIp~W+TZq{KEM6tfBiS{r$2_n?wgx2 z3&Ju?C`FyfdYf_AA~sA_ty?`W){U9>>$aSWBUnUBp`u)bMQq#oyb^+h)2l#Q5rdEe z&fxQj45E|s&gV5=zK{Z&kq|MtjUZt3&_$olXx&CYN(|%2XWuq?JmYrbBG8J_X<5)) zn3F_=QxHanH9B9w%1;z7kV>l9H{Y&& zzF^-Y!0UO!E@EKj1}xeRH#(g`l;bdLzy|pMF)bxVBPP;1lP1hyjlLx_cILv9?qDOb z&pSNrWs`I-O0`nJZ?|~8;q^w_j^6!#!`^(``t|C)_igXnZoT(mqmR*s4Ik##N9&{Y zk**7Om`8xjA;0;}KPS-pM3b6 zQ|zX2qQt-jKs3S#oPa%^-D3hU3kRqa?X8=6maPbpYQ}IgaTs}fu;H0sEGqdU!-Hi0 z3Rr}*LDgJ~;y7&qs;V}utaY?5L{fwhZSQ`7T}qKsYZ4&aK9;30@%6T`QYONMCF!s4 zdsks@)}9WO7W#kV0j zv>tuu=TGtS6D=o%g9A4Bv?3VRv79L!l$@;q0riA-v$t3I_z9%sgCek85`V*|HMT3M zQmJu$;q^p5!kjOU#Ei`1rZub-LQvw0!bl55&>OF3S|30R?^C7mj_@pmJh%}=8S=IP zcOv0sd3Qw6FaluHt1&5_rA|h@Ox^>sMl0JqL|`_P(!`{Nz<9gH%Nwp6?t5%4wtH-M ze|z(7v%T52TkHGQTI-_^_h@bOVc{``o4F0o{=h!GnGbghG7tIg+rRcXXU&k`ah~d7 zJBt()!+c7=v&Ng_osMx3<#PmW)EgEpKR!q2=Wi4;~xMs{P^^yc0x13m#3%E zY!8`(RgUm^p+|O89r9I70!F;soD*F{l7Ryp5y#*;#Td`N#57wwi%2F=GVZ~WTeAF> z)TLxzbbb%aOyuUnJPWqxu|9Lul+Tr8!9Zs2W-OA8U-=@qhbG2F$Y-(;qLQjzs`_*E z4zMQGh-Z3}cse4owY60pI;tmCNYn=IQ=c;W5VCGxLFur8G6d zhLxhFNcwZ?sqD8_RA^qW5_~GX^}3X7H48vgKR#W)|KYO;`>=94{qcYK{qtY_=0hca z+oY5LaV`D6<>i!A!XuG;-VO6%tnJ>?{kfj1H48JTN=5B{r}LT5r}pJd7M1gg)*O(B zhuAl{oTOH=L8ldCP$~Lwfk(L6=O5+kUoZi^dtIkjsSiFqK$)zg6r`;vjAQWm0i{r_ zc=;07SC)d)na(Tijo{z{ceE`^@$PzAv2V~4;Z#>xqx)NIH+udU+eR4l@QJL&^@Yx7 zN?wB-ogS&KVV&5+3^wR;ru8ArXkB~^KAo_fA?#m%#8066HkE(piyHMfAoZU=Ct>`icl(f>ba?lB1KK((Z}JPZwE=tJ$&1rN(HabsESh zxn}cRLRPfI>?Dkor-`w3yu8ud6}@BIjnVh>&l8^_PjMpC3eoN+Ckn;dh=)exJ;vpK~a4*GChaM#skPX9rvI z(DH+XS>Z9sd+Db>R(+9CJWA056DP$kL3esEu*-FSWCKw1&$wsoAog+e+wtdSSW)dp0>mWn1#l`!Xtk+ym_Ka49@s@k+LPNsKmL>Rq2K&R7U z!`vqNt}co|5$(O7*Y)}7^!@igF|nCRsek{!{{3(Mum8G!`JR|<+sFORgdon{^4Oc4 zRuCi1Ea|_bC7T790bL6*at$Qwl0%$6Tp|YcovQ{D^)8FXw)xwgF00?}u{U0ezrMga zfBg+~$&YT9kkRR4_k@DpsEFTh;f`8~m_K|(?_h>G*^bjAEel&i-{QW}at1k%0isyX zysYu%$6Ov_J^Oa0y1)$9p~Txw&a1y&X<2AlA@Co*r}dIRxafwaqAEs#}U5gz>T zknA9MU|Hh6VO4lwyVL0u-3SrGNEk#(^@NXTjn)&qW3*|2i}3Bv+4{kR-m|jU+m?35 zSq1Km=MRGCzHIIF1cwFl9A4rGXk0Zd59X#Mbcn z3%y*?2Kw;ZE%wdcuDv zauTx~hcwj#;Y_a()4sQ5sQ^YdBJ6#nK;#Kf70OUzI3*k^JGOf_dZ)8yeLGPa97PLF zccU|>!k}j69%U)5H5Kt;mW~?CXJOjIK`_rN7qbu?wahL8mYj$wJVugF00tbP!pc&r zYtQ8D5XRSD&Z0qvj(Leu0>xtJjZa0SWvS9Q? z2lBE+J*Qn4%orYR=dZr*KYr#G`Eto# zQF73-AcBj4H3PG}ta$wq`%aA5Hz06&$Y&CL_$G;ET-5d^YxOTb(dirkDg~ezd-P80 zg_b3*FMvl`xs+@{ggLy^!-u$EgPF<#8}Q&#Y;Sxz$Muz&z;(vnw2&~ui5O+^?M};y zm?$z`xgY@RAOSPl9i^h4K@`0~gysoeh!qI$jVHJ+H%ue1)NY-$kZ5c>UGKQwuT;qSBkg$epntdY8IO)@jjVpdcW%0q|H zG-K)+*kK&7LswN%CU7Y#%py9*VCGUZA1dSE8Rih0Qb}6iyr4M zG+j5)!fxrLfU4B0F(4|(&51H=&P-(~2nzR7WxKU}wpq8C%}J%#U5eVUyf@4STL28} z9`2{p!X()xp0P$POaM#r!lAhlIo6x4k7OwJK591RA<|hWBCU<5^Vxg9-S;A*r{$+V zejm%}H^2RA|I?pkIeF_oMt1$Eme~7pIdPdhBN861mm(ZqmIVkO1I+yCE0{@M1WJKB z3&rqkhm7l$&!=e3uU8V~#|Iwn`$lD|v^$GZU1ubkCAN(oKQJPCi=wgL zQI-fo7?p*VQ=}g*&3__DXhweeMLAnk+D2vp@Nyw*c(2 z_q}!Z=)Hs3%=3Rl6pk_69OhvbK++YnH8au?C*S_+FL5CAvm_;nNO{6e-5`+mEN1qJ zEJ{gzKCx*Mg_0P`sW0R$J@XS|7_-i)l{uqy;K@sxgb$>cH7olL&v>MNBjz!o$48V^ z)Z_g$NmR*{K&K~>2_CarJHyq}d6NCy)81@mA_rG<=FB{g^;FD@s;K6fn}3%uXZ>qX z_N9fW908x04aZCw1x%IW9f(1I4HxERsl6LB9Z?q?_|p8LB2Ro)o>paMQ0#4=)1uj2 z>jsuO(TJ)dB4Z2{Vc}WdKF9rR;G5RN^sVoEe_xic&g+OfY>a^SkxveBGvX|@A{HKP zh>!y_)94O&Vsc>H`npzkFGaX0u~4|Ev^GkuMdf~PT9g5)wfA0%!co_vst9s3cSzB` zcde30$cUf>tNN^&7Z5X-st8!?^|ZA6empT*RYfHtuD5+X*WTM0Lx^=<|M74C@cb`+ z_thiW>syczNS3<2-TA1;*shzq*UJUrT8p(FLFK##F`d@f8ZVV>P!S)d=VjdP?x9QJ zs-Wgxf8)H=LhHh;A=fAL#l*&(CqcA)EcSjh>!SP9Tr_ zjXr!u>p8sBdS*AjUg^^}_T!(4K~ljC72I#(9S{ocl?aCj%A}uhJ(@LcV3+dwbSGqj;=O5^F=Eo1%H!KfS>iirp=S-9P zmme8Ubp=Vl$$CO0NR^nP@34X1sZ^XV-~#K>_sMG>11w0~XsJ{d7A7sxTWnV@YsOND z1=fRv^I;&Gd6pbDsVoqoApi1)&o8)bwC~t=-0yyUjeYa2jr+D;uiI@KedH!E^<^K? z`xs-^EB9d$E-IAZ9L)n7U@ z*vH(An9s93Cnurws0j~?n59%S|4leg{t^dZX8iM)#k@qvaoNX_2L@(2G=hlz=$v&= zi%7f^`R3uC!YZ*J-0Iv3yL&P&j=zegOC-}V^0O6h>L*O24NUfC!ZK$bl7O^<_dYN+ zWzQdz4(i`5IOnO~rYozDp6762cCpVrEzh3UJX!t7e9yW`LUKF#_`#=HIiHF$JJN@Z zQp>(K(W#6}k~I2|!<3Su({frc?Yl^8&-*4e-O5v8&?%gE&u4rbgvBgQoQNhLUdoIG zna8>=ZSNjnp0+`aWtRe;2sSp-DY z`iMXfPRn9rfKY1L?srl3F-GfJb+|u1JdEDk-V3v8`ThU;`)~i%U(=ud0VF=$_nuo^ zEd>sW0F%_hAgwh5!XQ;v+21z3TqH+)kqGkJEzS?|@hiT)%IOsMD=rs3U$EUlgf>9T zpFYKK=z`SLN0(Yqma(^Zebw{XK7UW=2cjTW%7U+bBO<fERlGiBFa4 z!nJ~^U$6YX`xo)~dm0^O$;)pz$OqR2&!6J;vk}XuZ}9db9v%@)k59hu>^;nJz5q90_ zr>C&s$7l2#i238UVI6%(S*UNcp4^D5W)9Zg-sJfcg~#?r+uKwP?{Bo8SPHIRs4lY! za2T`%K>IC(xh#Z$1+)+g%1T;LYVt>}Mvz~B`&YS&rP)rBR=Lb=^PEbL)b&5{Kunk%;hl^$ zksb-^bT^)O51t%3n%$szcus)lVM(5+0KHoyDB+(GVPP3*OD+`4paJEE>E*D!hCkVWA=lI%(yud@o{OGtpbO7liN;_X&jz;(03^Su<7$cp2J5N2gKrV z!URLxu%naAg~i-*aZ1D%PC=!VanvIafiWSlAZRHu{cIqjk_>)kRW{44WugJy&1HID zrZP@3i@gz%+m{5(Wx=Bq-R^sygY!F&$aXFt#>&Hdm}eBwfl_3QiESg$dS1ibJhZA1 z69s2e4iR{E-6qt(x88bZ;qAH+vliXAemYgkEhW*iR$>|>+?)jIB+e?^$sHsHvAGAK zl%#UIsEj^rSbFmE*<-m%6?S*G2$m1ePy6*chLxiGZTs_=x4-&7|4V%TkFu=3H>e;8 z%)^E(D#A3n6ai3AD_E$A?OVN^^i;=nliX6)BF|6$dX?~uIPmF=eWQINCEq%qDuzXG zX=))6)XI;K_Ty(#VHK!G+xfIc7%`)5e&0w4BJucvPiGqUwsrdEH@LsC4=gJL);y(N zoL2k8-|E8|;da{~9KF+NojdpKE)P%g_z-E1%ER}5BN2e9a$H|2YXp>Wf5q*F^EtK~ zKRovzzh`EBddAz2!G%=(`bG~=)NZ)Gk{g8Z@KIn+%C%6t(sD-If(h>Ew`dz@DMKyj zJ1r-S9n8LO{OK3|?|pcHj3c?ah(%ggb^=CQyc@>jP$R&-892%Q)>C^Kwt*!6TBu zg9+IG)i3`tkKkE|a1b^Q@DNQ3(h;eiGC;y7(uPYki)#`8#OX1YUhhrMbO7e&LW$>K zn_kxpvv3kKl7X8Rf?=wX5p#Mc%q1tN*2K}J^I>l7K>4_$V}F>zpX6lFJE9sH4)UX; zAf~)q`W|2~BT~zkHX@09bPgP;cP^Ej6!Wks$ zZPZ%Ae6*oeY*Z z@$s-RSUJpV)!gV3!N+uzyE|DjZLGC^TC0kVVWkLzGe{EVHcVASH1`6d_w)JW7D@hA z;i5&AiC~WBr_0;Ri<{Te^5u^|@WazDKRu2gf55r|G+LL!ecOYHKwMNG&i(Di2y2~B ztE$@GNQ7%iJr2TIEt*6jR*Cat22STZTNl(q0W2kmy)^*#m&OJ1hu<<{fhv|hHqCnooRFykXF?YBIP`1u<;J(3VnAr3bqpUk=FJIcfxIZbk2Y&VpJnB(n>oX^QTV3imRxwsM` z(TvNXHHgv-4;whi|3;z#!e~;L3Hr`Ss;+SHKmH?r_=)yLy<^{EZ*jZ&b|1HU-*#(b zzwP^dcgOv{+X!>F?h(|@`mp!&cZ}#6j0Sb{X2zUVcI}6)mvq&&N?oL^MJ+B3nkgya+v$~L(g~g^V`oKkH<)DWjGjAywZYB_`NO*uERdRES zgTBfv79q@TPANzy0!1WE$IR5nVME5;;;OK_+b|XJn7>J_k|(>nfmuatj9}_}pW*Tu ziJbO~T1#(TiyTl6Zml6gi!P^{AnA8nbfTJwd5n=B=hTJ9FlHLvmbKouCPc!NxoynW zm1K+1vT9gRVIacXQ#i6|awg2=eax0M(OSHZgf5vy%1*TuGs_0{qJtEUT;)y}^OrNDEnz^*EhqJ*BZVL|EkiN7bKxNs=T-o*)sk zh^m^IyGP_vM^>R54HTx2oonaqzPJ1T{}Fr4PWNkc7m!tzSA@HpnW~B~^L}7aiyV9a z0vQn=?&fZyEW|`VifJJc-Z;-u&U!upu&u@QRh~ciFF)$}tlxZ_TAPG3Y?O05%6V~v zoKBpU1jxww5(v&`+;6PK_bqgiTBz#R8)X<`t+yv7ed%KHubhP}ml@2_vM?bcemueLYqeO>o` z?WtxU&^=m9TuqjSj8$6iW&s~fu51>G-t%#Sg#|3~?e-;DX8{NGyWjuA!0j>5RzdP5 z$Wp7DMY!aDKaS}vrgG0xuR{qr%IgMmLnNKKV~=+@lfMWiNtcd6G#ychqf~I5^>VBg zDG7ud710M7V;H!{L@W8YIt;Z#cu6bXNOn^}`eRW>9(iygTvbM1eb5;ng)%P6qig^c zE|g?6kCw0DQ^vY&Ihm$|&CEb1KD5-)F_<4#i<)`WqD6CKAf8E45p%-Ugum#U+@Q5BJBKo>-rvrZdAic(7L)~!XEw5_d9 zMMSoHvbNEiH8U-;Z+*VZk@N!dWPj%!GRSF}dh4mf_r2Ar&=G^w8x+E^oKA0_KIbCf zZ-4#e@Bf$o;Q#t(ouL z&e*QVv3ja#U0*)3Z*gDI8?8|o6saY4i>XGlQq}J_-*>&7C3>`u`Q+DEuD9qNr>CUR zkCr5zd0FuG87e$q#wO%+;kE%0w)1r6G{<`9RADC3qs&3WzCjD>!t=Ag{uKL)a?-E9 z@%uaWJJxr3{*YuM7U904%#eVH%nRBM1nZ3X0^3jXD0PpB39J^x=M79%XBGnj>E ztyB;Zxxv!{Z&>g0{4sjZ6iH;mRs>5$snnV(e}2kdoBQ5>wCA}Exo8_*3Ch7w`LxZTmuAP zK|97|cLeF#WONXY#t<3-ibv>ozxxLsUCVha<`J%?=GpXR#Kp)`^T624Bj)#Im4^o) zDUNe?1Xi*E006?gAE;+f`8L@-qn&nGl+%Wh=i(5-%{^6RbI*>!u}pJM7eofu<*4tA z$Cj^~mO^s0x&S(Mo`JD6&7t1|$fA>sdtl_kk+uzHL>=%*Mnn?((iRf6mOS{&Dvg*5QEp6`vOV!pa&DR-0Ovq=qewZ9# z)~8z9-b>N@wrSOUYe4Ae|Bhg!9;Q{Jd#$3(a`e2}Fau3e1Q{|wL>z&jOo3MS_x2@k=*m; zY^{^%t=zyk|Hn4X@)Lxp5bY#pJTn_azUxsb}SdF!a7vYy9B+PoM!g!(WzuY z+NK%3g_d;aLlxFV1+}7gp3e-6b=A{}^9d?(dza;$#T7W6;gKa8QO~HA`<)@23IKli z76|M+FVA6(eMdcET4a90{Z*cxy>}8kz3BN8`wF^Db6O|E0qZ=S5yWx^8P|6?J;4m# z!#cKi++N`(^C{ZS);TZC#v&<1pl`IsX$k6B3r#1<>h6pMs)Tj)4XSWwogf8y^1OVF zQnQ1JWV8kFdsUSD{4TSt{of)*V0mgdcywzJrSO?r*QL-qBjS-o5#HTkp3u zLR#zl)_XIva7XLjyZPaK3k=v?&kHxkbWD+MSxLkM{P)p+EyosbjE{ii7^#2!?oSzb zOA|(LB(wAVc*w>`qa6=38i%DRZCm*+J!-N@aM+)LQH7P!711GS3sptTO{fV(K5W{f zjVa9utu<9u6&t5m>Lr8O>tPQ!pBzF)K8TKfF!FHMjRy3bI#YE4gklj=>qAM;r-4TZ zM2mPrl#c%yyd+{!GGrX1z(H*mwQdoKNy0o&*2hfu2%z|2n^068`7)2!I7Zp_aE_DN z{za1ezL#2x3}HGsB!kKyOxztsGKM5Ya=n($`k=5?yQ_dqCRROIA3tdgFfw(Y6CFn`j4X_?piO38h9m>CF&^u1{j5Z;}_ zeO;fPp4N5Sw(WGe{PLF{&VToZ-#$;sSHf5)?t1|8e93TE zn4=c7p0uEGdEHiCo`{Isn=BK$#eUQC1^Zo2r>IM~q3vjEynjNeloHnUeDdo%=M%&I ze&gu{eZ}oHEco>7Km03BA2=@z#BvdOTwj@lmgt;nSa$e1&(Bd#2$RdlNUy~*$L*Ej z&`O4z%d#LMs1&6L&FOtuMwd%EaOqtxA7y!hhxd)sLY){E`!+gGJ)lLZ!kyC`+dYIV zRqBLHh)gF=XIS9VyZ`M+UT@fUyIx`4K7Y3BwQsv^y>0t?Tix8c-|ySL_uj2_2e~&h zkHe6b@&;PB82Gsq10)sGZUKxL4kFF6<#6DMMpgwFX$GNw_uZe4IV~u9-1i^vBj*q< za@0Ls3^J;!#?Xu*7kk(dw#f0&GC&bW6TvWkjQ=$ZW)EoRas2c6MLO{GSBb%cUwW7> z7>5f`wP&Br@RE%Bm4H8H{Jdwld#Po7&4{eC%fC}f$vgLmr9L_cD9IEB<1x9biWbc& zc$%jmTI*TYorI$!QlCyX4hFLzJZwxmBUGKf#GGEUy(Lwl-YvVGv?wWUZ#qE9!l9(2 z9PZ|%nADmOl{jqId2tGorD)YM5V2sbWq|sMW)eO1jZ7p`IjnCKEu$tp0e~}(4@DP#G*QldY5qDt>4%Ed|m?4x0bsBw@^(uROVk?icZs1)98}wHYgd) z&*z*6V#u}(^wz=1LIV-agr}zvNitXl^-0>kKYuviuD9^0)AYl?{Pj2g;^W`ni&r}T$scKu3(~0*LDp95=8uvTr6Soz0rXsE@YUNb@ z=b!QN9MehAIbUcErwhxZ%M_n~&aGNp-=PJ4i`%LUU{mFOlPzeclo{)VOS(_iI$ zaS&2?x`2%LPxA4%Y^&F^T%Kcn$K_+XeQb7-=G)47 z0TJsB*(@23Qn7DRCx~F%QYRpVwZe9EgG!L3`lr|U*S~Pvx$kkkq3w3Pa&PmNzDVJ#^I|Tp}r&ek;a$sUt#UyIAG~E5+ri3}U+Hilx0JUI`2T8Qv)Geg!b zj^*gFr)1>VE;=HGA~GV{mIK-#N9D{;P^k_N53QQ0egI`E0a9S*?p~+5u3Kv68KBKf z9Z2goO*I9qBgj`$XLrDR2tg6EFzW+q>Y+tQdUq}A*;p0mDPfb=MjL?YC?ZHHrW94t z40DTe+gd4uSRI4^H*%a>awm}s#tgTQ3_1w6n3kF?ar?H{xyoph(W)A5!$3WL3|v*` zX(FR)v2Icn!BR`AGWm-PEtOh(>#8b6-F%=*!_B<4zMN(bj+k0=FsM?L>5U2V);kd1 zEh%(;Zxpt5BdMjV_x1VZa=qRt60Lo@uK(~q{geItm%RlUuzMTZ~sn&yuV|bY0hN{b9m>=3vTbK8gwbu1Dq>Lft#G4SZk~ey~TD1C0Y|D zz)*6Y#XCHb$QzfJc>SD(Sa|-J%2@0>!eBdFMFNOnm<6_xC*S5Qg)! z|NIv&7pcX6`I~5w^D~z-Zdabp=#6#aa>07nd7&0a^=;$ji7Zg15c|&48Sk%}bu9?g z67F)oV7dT;ta|9XET`m`!%db`c-B|v2^@rhI?TJ|XqeTShXzkCBqKU{r{ww3L8TVB zMV(pCc)RhhKk)M#Zg<~S**Cvk?SA*(ZQJ(yy6$+|^1tCxK9?(Dh{vU;p^E=!PUxbW!9DW>2 zxnqVs{z|3^)0;7z#REoqc%x&WATdUH$b$qFUq)RyHg!X}_V_Yp9~KCJfupwI@!xV) z_^XQB9Jj|DeH`R@xpvFe=|KV^Fx9GBx|yn|YAV8RX6Dm0MWkdc4{+VK?kUjZiC22 zdVjmk=Y^y6Zs^7~Duiun@z zT`o^DOrVZ!Q{?2)c^~QNlj}n$A zE=&CIJ&R&lqBohR`22%By6lF?d5l=JRMRYJl$N|n<&dLJQm zYpgYS0wj@TuihC>A*Y2P1ayzyVwzB9Y{nmcmQSAsP5*Ysw%hgEZ?|?|qgmhDw(i^3 z{K#Rq*3Gc3d$aJv+7K8WEHMrQ&oeh0v^48$0f^*?jkH&gs@g0uoSGX&Q0%B2ITFC4 z-~Ij{AO7M|q61II_IaLy54(krh)0IvIL^NDMwcwSgt-fq9N>!qm&@EX z26&SQNm(&4Ko*gqb!P&zlSx6sQy-9r|1ToaDcrjOp+ymp1L2g`ilbaHPj*kFa~2`x zZ=@+(HNi(ZG=y@5*X7W(ah&q09t||8<+Pr*!lDC_%yD-aJ2zEH?}23Ie^|4F!k$bP z(9DamS`$~Jmy zB4w&2B8sMMk_Va&rD!+HuHI}?L?(#ijBL#OkW(O4p8O^hk#cfzZ z&Eom_saE~@m(TOEeE-ki|L#Bh<3}~$TFRXeg-4mI-?#dkZ_%AqK!sMBH14-(4g1b% z3Q?ICl)|ZU+o_CcMph>64a+QU-1lgW*062y^dZ)b;vNDwlI(k&F0?M^m)KXC7pXP& z9SZDso?r0co3O?@qwN%peItS1qV2wJ^6@Jo{Oy&e6SYP+l)_T6?PSE|18M~@_M8;^ z`A1#O=&QKFI;JJegP_@n-kqKTWYUubvE5@n#ozvgBri{-$cJyy)?_oFtys>Y;`^3d z0@jMYV|n5gRiT=IG^Hnr7HT)X@-d3ujo5fbzb8326vvGsTG+|k;3JKy@i?R z6jZo*Yg|sTt>`;c;32}W#(9a>01q>g5CupjoN3q?L!qeX5V4 z95F2mn2>d zZYM&NNzhW2-ZL5_-mSHk#_c*4YYk-E8%0#LmXd~<)_PG1LWKi3b-=$pzli~zO56Lo zZp$3I+kHP>CTo_@J!>}2)w`EkI3#;d*{3Lb^9UE!1nsZ) zy;Q1bZymt4Z9=KFB0_7iy-#zsZdw%Xy){u467US#SdV>cFE3A9Yj3xkP=5H^&wu)# z|A%t@>=EH!zWx9+zux3Dg9Q?t=IF+GioHoGJU@X%;^=Hgo#XXQ=7Lga?#l(Aevr#^ zv^^s^WiHNHK7Mo;o}Z);X7s@66b?Q;iE8wn(}LbYCepbq!ie`z-a4p!x=7!tmFVz} zQxKNu%vNU1D3g-z5S{SV$4vuMA^21q{>Yj}gGJe%aVNI7M5Mbf+$GCn5gH9|LnV0CDS_#K| z2{(qJEGU)pIn1!%9nR@Ytq?(5u{^tX^c~?TDCYndy#ZjU31iEqMuJdg2`sfx3q7C` z=ByPFVTSp{`Hau+@z`XkZV4d@ z0PGg6C$$;P^2}uSAfT#w2hHgJ>uC(rJv%9`z4@_$TCC zJd$t0BZPu6*p3xf9(ad0c|`S&KlNoY9h{Bh_MK*fICg|8@Hd!$?PGXJf1XoaQ$^m@hZpOa%eEauRUZD9G7(ucP#=Ckt1qJ1t1WE0~n)i3x^xz z$XL?d2Te)7=sBYwHjw-_5x7UGnzwb2kxa?CHUiXPUC0NOhyqcIswgGNe2DObLnUu1 zLyU(*hWQWGQu4Dc0}z;N$k$SKpt7Mkggs##iGiuPuMFzYBv2(8&XPv+*`rnzqS zz0{JhL)-UKG=J2mTH%p)8C4;rOxiXRAv{XeT575pgm~}S#1EMUo zZujl0kI$d4Zz0^@?p^f{zj?X;?T6{Zv%OuVR$XS)1tPKC0+o|8AhiV5rD`p1&~LuA z>w7$XfCRz`cl038%0w7{I-{swQKSf<>-qEs#mPAC7% ze~+iHP$t=~QZ?S+SSsf^uJ1TqLN)Gp&0!fH^+dPWSGrNip#M@f$2BfUGm# zKjD0lT4UY$bn%~m42XR7jbC5mewXDe&(E+PuRrsv-^%kh*jAh_2C1@sSp_$LyQYlUBl(T}a03_?)O zhLH~vp^nn07=Qk7Kp&!IdY=zI8`8Nv#$F)MZ6DaYq23HnOaROk^RWku2S1NcN=C{K zSJ@H%$&>C&7uT1OnFp~bxtDp+T8zdT5F*m{J)5Y2k*>}MNb;Ql^eikd>e-rjJX#Jg z$dO-9*e}0>tRz?&-J7Ap7f}Mm-g23pSJGl>qy5%T4-W~64lLQ z&{+}L&|0PSp5n8n`0uf`CYlD(#G;zH6H#gvVPFj1wU!JePIL9$>s*G@*^t6T=EZw= z9gtECQfz8zRopQEV9E&BB4MC1zg=Qg77ytY&=LrxNjr%PwAH`Pt{7GKE61V6(Al^+bmw0<6%zNY0i{Dp$dWIDF z_BZkNGs_&J*zZc{X@ViLy}l?~V-eH|WhNq~h5HVTkRsdFb>cK*Im12njbD8o*DL2L zU;Pemzfj2Yg<0l&zDVx~kNX>fF-=ky>^FcoJ@fVp1DMYQs0F>thu_8Z7uY_8g?T~S zNb`LU7QH-0+gK`kLs7JjsZzwRZ@fHVyHSMGDH{gpMlD$zIN-!uICKNc-{m~TzA-vW z1tNMMbLzCfU8rzl(J)6qPEVxbr%&-Of6=$Mcz^fxj(zX9d-T3tuls!s$GYyVTWft? z`+aYDLdIxdqghy>Tl5@O9L;^~y&@B%c@WVPH1SY+DI+WBksb<11Zg(H?WtS%v7Sk~ zdQy`TnWVq}{Xah1PY8L?-NxxAqu}ERn{XWakS_v43>$+y7JBgj!U;0}ruxNa0VGl< zUxh53&NxXNBRPg}i%bvtuZ<_eN0Pbrqnw+xF<&2_{xDc1EH&eQ)SR`Ch|M_cvWtZB ztEkEluOku9N#!rvx0bC1#||`2dqcEIzumCpQSx7m^n1AjDQyqJ9}($XjDVRX(MCjD zYxzO+=%P)OO1LAtua4#CP)-gMk`1c^+{o$9-|Mb88NBr$C78F1LoonnHwI+g$=My}_ zoKua~#5?;YUwzFiTdoU9KV1gx@p|RU6RBC=D$g$wD&D9P=5jvczVWNyNN*tc^dY+G z%TwI0vXod}_~C2(^4Id!H?YQ1qHPhBm#?H2dwb)D4_J53HHrd`Qc*OXzm9EXoe(B9 zZ>y^3v)}K0`MUk^FZ$JQ5zgx;^ajtYT|si_MZCG3s{im8Jb!R=ynm9Zpc{L`G$rE` z$rcxl`zz`cZI!1F2rwGv3&XJ8<#dYsCQl!6zSu8+%_kO00jnrs&Z(kIu%J64=yrtt ziE`q6L4dWyx}vS3N`+tF_4Je;3`rz(ca;NX8+Z|8UZ`N$nFsoKl~y3hd=&PPQ1ekoyx8cw8Y^|7`}o- zV8-~I^Rztj)|tf+>cec8C*?SuAC|wIxMOscrr>=%i@q4$#^Uer{H1IHNuxqa8Ia?w z9EC5D82uqixVsMXBNHhH&|}c0BN4_Cz1uX;W)4bbG*f3l{)-upKdQ=A%g~)OVVP{z zLk%C>sUVfDcP#}2Ly;=Uqd;cN_@(67oz*1iEo%uzO%BHZ((ijZH}bih;&v{A^Po)I zk(NRrpC~vW9;FsDSCOJBQp}9X-YjcVRHT$D${e2)468!3FQ+^N=AQBW)-AtrJ)Z}0 zK&$o3_PADM(9C=KC_J>3T2xd-=w@}QX}lAaVaUys*g{oW>q*qhABZv)J37G3dx~yJ zqgB;XHQmsoMm=?v?hh7u8cWJtU^a?)x??;e^DYwW+FmYa58qnbZ|mLqzx&7U{QX@| z%h>s3^KOJ$=*tU2SQ)n)=ZPxl?q%Y3PpvEJi4y+ynIFE@aBR2!{>l#@&I4fa5Qym#*UysixqXf0jHef=C%pbdsJ=X5T>)^KV?N__ zfg8`4u*Pi@29&XPsxT~i=gV`f*K&D@wsT)&T`|q_&2O-7WME%eYiyf5onvbhgwC>@ zIG<5wQ25n1=p7F9T}q9paM)(m^Mo|!<4)}y%_WbZEI z-Uli(Cxd3LlKH?ygL2%fGBl_1(4aH5Y@m5`ho!+Iz@)ApCrKq zXBsxT5C>?q7I!bIBS4goMiBz4<62Qlwos!^CBb#7qx(iA`-?{{ckjKm4oaP>Qo_8{ zl75d;OYcq~!(1fh)0`=4^PVLHz4r)|Dh$-A4#_HU9~OiEr|>?QA(m~;9F95}nQ~dq zbMIZ1-7Ndb%&ksS?|t7}naVIn7cHuNv|+)+wWzgTiq6ZF_2FSIs@}R*)uPezh6!sn z*3?N!EfVf^so{QJ>f7}W)gQnA;py+b`^~x7^$tqkcUdM+m1Q{tAp-0@FL7|g`8ju1 zShuwF6UL_(`{l~DXSfAja=p%SnTf_36F#q~4Y zGQc9IQ@C?|gLmjG6<8aKj9ISE^v3la)^UA>7HDNU(6wY;hg_bq?d&`5H#uMU)wdwg zHW0CHQmaSE(^I^C0s_;_zF}W8ss}*4qqUf-oSuBUq0X`0d3uTM7Ud+%Q}m4)pQ$t4 zD2nM!EwSF@=>@IR_i*P@**e@fO<^HL4u~ehoMH5xrOGL};=; z3gg6<@Q`Dz`4DZ8&FZ<$8$GS*ZF~?Wh(OSj76ir@+sQ%uRMj}zPsV>qt99b_AK20Z z+yPIigd&4*Wp1Su>-`J+Kh?lmd@pQ! zm!brF@1mJo@#8v_wX=o@PadpDx@!{qsiMqSIZ&zu(OXxQ;SDM*BFVQ?P?Y!EK8UrN zS@yhtjBit-qf(Ti$XrX?n~2mzt%IUu&{~%w0K$Bpig}38yL;;)Axr_MMTBVbuf-YcWfBe&wG3)&4ECuJMXbs+@%(N}e z&oU_io?ra^Gu=^$^e39Th2~zaZSatN&7VOC;`+{~XOx1rLqwmRV{aNgLU?(C8zZ81 zsInBQ0Gc9IK%;H)^n&{>`UWM|7W<8ZJL9pgFv!ab=85Z_fbE-16X!W@Z`kg>cYOOD z?r-P~eT&v(zX_dB9{~9KD@x_GU|Ldnz|)6#`x-lJ`Pz5DInZukBD*4EWL*4w(T4P-ap zduoVm-QD5AOmAn1*8>o7JGxvk-o<^8-BMQ*ABJ5qcM%OFFeafKd`zxE>BHGIfbS}) zoRGrqo|E`cMGljje)ru!K8}T>1$HRKhq-Q?@Emj@xZ4-g8;}2fcsNG$Y&Z_=+&KC( zmpBXnY2i2^^m%OMyqqI&Ml1qxn9TTKCIz%qw@mRKV(~FGAMuqmli-jQnUxVzxVJ{I)Us`z zMG%9souEF4Tkp#Y1Dzu#plp)=})I^p#* z#bkNHzQ%Tw%SZp^FZ#`Qv8}WQp_Cb_q{n)N7Qg{#+n^i_yz{^f7HUSr$+?bYvhyWPDt z@3vmA*1flWyKZJ4;I{R>d+%;G=H+fCQnD!14!-7|Ml8TA4jd9dHuD&kjB%45ZU<3^ z1Yk*s%bejs zjx+giaK!=2_*IKw40L@eEY<<2Xt*;DrqiL8@E9>u@EZ@PbZf-1yc@@NHlbww`cc#( zJZyPzvjptsBO$9%;=qRj!=((5-YvVbGkoIXCK020%3Vg=(c z1qIAsYtbYlCA3^Jk7Hho2B0apZ%xy|(p-y($bR3lSOoPBy>)B7RLP9#e4eda#_n71`?i-UEgZJ5+j2f7!4-_QTbbClo;IZC%X!=O z&HVMRzg&Lv@!O|rTh}5eD$9)4xSTmxc0*B}Ya#;Vym)K6oZ{ne^t{-0!-o%kTe0sv z6@t(zMIajIGjtLXrNRIULG-@2c)g;kynI00U8u6)zuQ4R5dd)z`6ip(70UD^BH028+4NC6s=RMLx7O;Q{1nBbGe}H0i%isSSNbV;=D-wdJX@}1^k9tXceGHYU#qC< zAOG-AIOOHbCPus|wSg7(Lj31uA z;nF3U8IW=6V{G9@Nfi}&7RVUbF+R`3*mnfgm7*hKO)1)~JqCFI)bXk2b0`;+Hgct- z$~t4&9FM^VAPHHZn_QtxHk0h`!^W20xqw?g%pO99sE*6^5L<>E(YwV_p6Kr8t|~<} z%yZ%&t%8U;)j)8R(q@J!-wVY(yj%MAv}oIVt~LiF+e4H+V~tq>Q}nO{qG++!lGvHZ z4pp)2Qe)`RXl6cc1EuJA{exzGs#E&#Ypq4Zx`)u+r&_aB+1*>SI@cmcA6SG|ty*7_ zP?nSLU7jxZ{7I@9r}~@UV0}Z-S}*5wbYm@OECtrOUe2*Kc{)ck25@@T=Z{*a=sTy` zN3Es%+m#=`;@+Ub)03D%C9F%hn_*rA4k=XexAzdy=MUI!zU}h+zmK-jx;{OjH`FO$ z+&6yv$B0m2xXI<&*Nsmf7{&;CV=XYleCB+L*Prq9B4KfTlhXz34bNZ4dS?V{iT6)D zpQuy3{*CjAZdAc^h8Z&*+Z)m8j{Tj_U(;GZSe5PwL%7rm_oy?yhxarqMQ^Ys^9foa zoOMp2IjrR;2@$M!M281z<>?vC{m=i(zy2*=U!!%ut^M__U9Z-=ieBGu5wY*RHAm2# zd$Vq_cZW#t?!lt~#YL1N79;hSQ=ttgFB5Xq{OD=I15GmyIwLXw=Mg#f`LP_zonepY z5h5jzK_9sclF~EMK~gjhX*JSj$C2n03c@{CbB9vLgYbVGwaCHA4>>huy>T)nMgoDr z=&>FjIpyqeoWm?~b#{0` z{G-3UW1gu>w;`KN#XrAUomeFI^oY>8;Chv!&>|}F{+Z^SW-8&vW$}QtySHARpZ)b! zzWLU#Yq^~Keq&W%SGL{nH`W?t>`msgo@W2cpE*saQ>=GiH@Q5?x^kZJ;ahHZB75$P zlVH8ay6W?D1nm72w>y`G%NbgDdBW>2Q6^Gw{mfEhI>r4>UcQa(N&zv1Z{w2fhq!go+<1fVF%@)eXC`vy^HW#2&MTp1#@h8dUhNFP6a6kXz{pW?s&8J}OHb^H9b zzrNePwcCBat!-<)_1krA%}BZ4cT)ClM}=rIWZVzbTGA}KMM_6wPnhz>K_C(lJ2JE( z^kJzA2P0?>8&IXT^wWf zU`F8p9*tE5f{|8|qb({%ggV^>W6LDR&JW{sf7GD(;ham&BCq`iYFx6eJD&_f5mj^7 zJL8+D@^HU_3lesI)(?3tT1ayc< za;y@TL{e3HZ-A?mh%h(zj8@07LX<&<6p@mOZ4@mj1c#4C?d<&aOo9Lz9Wpb~i~i$vBPCc)&C`lyV|y0fEvz36eNLTmOb&%5Wcw3O1#BQnw{ zrD{6uv`G4th3wsZptgfRol0+(T%|ayFDjbeqg-=nN%%8W6oA$Ok6Lv+fmJx5GZ6u0 zbaxdov({aOMYX8bs$l>^tJ-cXB3e}7=D8$1+GAZyF|%ozQuuPq-r=arWaeQu&BdCF zK#Pj1d0b8>1}x(9_dh@V;oGk-r8kqJ(HiF&&(GKzgeZy-@!(t-7{yV@>c$E8cX@gW zop`!nyT^LvSsHC0w4Klnf;rl+-HLm8U2F@dsX>#2YTp zAgMF<6$oCwLUf8?T`5FJw2jYS@%9PPF+a(E<#hJrjQ(!3)z89x_#1SJp}?WW>7i`D-i}xv%l( zKjY`0a9iQtZ>zn%!|nFz?Rs5BxZbzheQO!Z9^MSJ<{&N5%)5_}v>|%Hf*B9i42c}| zPh(D`dV~!J7lRD2Su{@_RZ?-z-5DtPrbsO!rHp_;=$_QCqeGSd=kDwy^=3!c^J7U! z28ZZ~(OP|2H^w!4SePF^yhpnG_!*NdSaC)2cpq--G2~_wuKQ7cl_F|Z4UnQE!beTFkPJUwk~ap; zy_*&-MUSe(VUU-!G=z=eIzmgye9cHwW*o7%eE+A>BK!Ta25FQHAEhw41J4H(#zC*c z6Ek{B5X8RsWUeJGBwcrzFLxV(dpA#%O5&lB;frH2$nmnoJl8Z%ixBRj;*NnGAw;D} z!7x5b97b$Jl&T2>N!w5)|0;KPN#H7K-QY=s^3!?Qx4rE>Kh8W)x$U$-DM~WTlTx&= zO_f@zl;Yh>k%+vh)Z6u*4As0|q)|tOt@Tn%lDQtH_}t6XQml6o(W>>ZOLfPI&x^lih}zwsY` z&<`(=3axTJ$Niqm=;%E;@llGXM(?Z|PapmDv#20TU4zgPw)y)_CKYQuU*hyE3hY~e zGEIamX_55H}))Yo zQfhP~3>5kv+l_*#PuOp$ioM5jA_aZ(`%RX!2x8kQ%4rRPWb*` z`2By6Prt-%>#uL^_04bV{`R(A*W6d{yWQ4imaTx&ngy`;@eb{lrP`Smp+|I&o|7Ed zlWLIF&tUgRTW^N{l0q}4-4OF+YaB3zbTK=EY`Oa*Eg}*I{m8APaGH}Tvw!X&6y@nE zntg`)hx9=oC-adJ;X`iDlD&~X1cDFWg^Y59G3zFn0*`o$jDwjt)|c)!*n8w7xp)k| zhm~ZUdU-B=K`*5hfrx@G$EGZM3q4Q#q#mb}FNd$gUzO>Yi10d9bIYzK_-Lbg zv}7^Ic^SPbmMLoG-g=gbrwe81>|-Aw=~qiNCau%M42LZ1%F7O7xY_aK8x;eS094sO#0*T%o@RAVTqMiWO^pYeZV7F zM7mi7rlR4YBH29a0U^6tt;NgwTM-N7s^<97S zdqj+zYJw_#N0~66p%ZP#a>m;)RORWy-g$X}%$(-<;d?$m<(tAm%xBzRF+Isw-^SZ7 z+}`ATi72d#Xo>a8^M&;s-qALC3$5seTB9|VKv3ox_Z#Mk5wYE2JIr)BMeE@hnHQbl z9k!!4h;Vtf?Z&F!I;V*MwDRR!{QL|5{LlRSDcn1fqVLr@#H(ztbZm!hw?DV@l=`+8)C%@ksHJ zh_G}`Jal0h0MCKY9`eF)>}AF;kIO_QjWBn5Ksb82J>`gT)*s5t*hwZ)nj=#_zA`d3 zqkK_vW;e51>iA>AqZ=hpBUjbdTK<9LTc#m;7`-*shTO@T4Ex2Qy^bEC2>fdGXW8|a zJIsg!xt(954ePNCN(xbG4fg|qgm6d^H$N!Gc>=~^FU)}DfOte?lYr*(0)%Ie_>rxb zBBhKif)?$qXK^hLVD}NyM>e)lvYToYhGp}tjj~|2-i0`*)~QypcN@;}v=xV^=87p-=?%2c!Q z1$Aaoum}nn<&Hyq@f`1BKD)Pnua55GaXQwnT{ZE?SbIbS~b`zzbddExmP>pQgK z^b+e$o7wv}`BS+RW#l`W*A`k+S`upGiBZt*3H;u=p@90r_;QWVfJ@zEW zN^eB8mv7Dnf zhRJCT2WyG@yS)5{s+53~xV=FIRJ3=7b3PLR0m~&y1v)*Vuha?4GkTY*XnUwq3VPGa zb958m5ahIQs`lvzObh07Y*!$th1OBTDN>4*0v(mxI!&+^Qu*`^U*GY6{O|F@U&GLT z{>9$k+I8LD-}kNWd%LfD=1Q*jjjEs|QX@e{5$H#kbOaI^*(|yrwN8m(O9>J1VDs=% zpG|v+(PZ<%DSpJK-5(x@jG2H)p>LF@*dY`9(Loi%ntWi(WtGT;bM%&%9O8W?0KT>o_?Z99zspwEw z$6Pspc4i4$8oe<2JWNh<8l_W(@T9UGAs|s@MAmOb3L1gd`;e0e86V_$9WpSIiSx8; zWcf`Pj@RW;L6Um86t&(-af@l5?)NpxM~8x$lGQAu4U}Zn56C zdohQx^DaW`7KVTfAgRIv48rBbB)%cHGO`P zQc-7~PPpD6kdI#r5cfAEM9mDB7g%q9{fqwYJDA6O=JOMK7bQelCR?vT(p#n;csj!k z_pAK=?_=K(E-xRvcP=xVrI`c_5>h9S*j7#xx}i+$yJ(;_%un(5qxen_mLdT99#qo9 zouZ(4TX{O+{!R*2<9_4i$*%Wuc>*lD#d?$F6hfRo#JXe|1I9${Qc_d>i7Hh`o7)nt#|9TH`|+a_uXP|?t#|Q2qoQv5Gj)Q<~(P*c~%If z6(NXht2$Ix=3!lmVxVsTk?g6;V;W#4sVzcO6VM?CLnxJNi?A3*j(p2M_F;$jj)xR+ ztm%-a;y7H#`}(`@{*+_vaMuoXVz8bwB5@c406Ftz`&ai}tmVN8v6I z9(lNHZbX$rYLN)`W*H!g>_T*(=ZRD)T;Ep~ib&gbM^w>Lb!+YQ&)@&%KmGfQuhDj? z1=ECXAQ+he)^- zS)SvkPx#AU;)fso^|jwtU$@(jzg&NQ+xOOb-!|KJ+xBjNJ6dyyM4($Z(7LyT9C&u@ zh$Xq0G)Fh+V0F=N6#J3gY`^8q+4E!W#kWHB(f+60WA{gY%5ClS~R185@6Bn&?`mF z2C0LHQj4|j=A~-en~IjI1g*OkJ)M^AzPkX3w%c-=0qlDNbvaGzeKU(;%Tr159U^Ys zhq)g4sshMXuv%+`GnwG#2vL>wb{Az4W-oh~_txEAfPi<4S_-n;(Ea84e7)Um-`}p= zzx%)a&;I#ms)`m)C$AHynS?JdIu%(?aXNF~Q73q3Z*12H>hm+tFMj=uw#nrw+J=3X z=NCGl6VlGS-=r2Pg1TVevEEpxAhi~|u5z0EdgnZc&b&M`Jj_rlry16G{=l~DhmS#J zsT^%eh&pjzyzMg2*sk*OO>8?E@J1i^SI#GT=X?qlNM$LYqNps-(Kc9PnXx>@+b0Mx zo#2T38^8J^+R8eE1O4dtcb+bOeG_YPd1hGbcLenEA=;Gzl!7ud{dRr5g4j3f7JKu(TcYONqxFC!H!gCMk;zzd z*r=4tQW>T}am!PGOy6Gs8hSh%Qli$OPGn^Ku$Sb2&(9#@@%)JZN-4wjlB&mO;Ub58J0|hG zBR`TY$NvovJ60#-A9#or?xtES$s0ISkX*~ArEgd@L{*RY{7~&PBa`p&w1w!A6h9t+ zR!SKeA0+fHAAV$H7p;;~s_v};ks%s5#sNR9=K_qz>xA3oqbP4Y)*~MYQY0gLqu5YJ z;804DV{1I3rL~NZUeO4cxrYx#Uxb;Lsy?)G1~E-#yg!7N`$lBzMepvd83CPY?>59L z$(@Af4^|cxf;9Ja-Orb0U-#a7U9{~cqVrtxZc`^!Wuou$2Q)XXLN@nW3$sqaLRIos zDOE?+kXwE?RHRN*cDa|TwNBo=cu=0fwZ&gH@?{(cp2vF}LFRGl$R0&<$;{vPg}W_X7iyW{DM*UwO8tr12B z>q4ckn<$|ZrwI&SH!K&K=4c&#i{%9OSl`h$o}W=_w9VHQwX&YrcVWOCy~7$_zQVdf zDwVju%d$i$qeY!WG+?YVK%75N3q>f(y3jfmaeG6*%W{EvdKR?^qym9jrOepgU>zc) z$Ndg#s8ihE^yv%_xQ%nWDiE?f=OG?$s59EeqQL@{XvTR?LwBrq+wVMoh3gjodjb zcaSlJSvWCO$>yD&_^$MiKAK^NR~VE-Sq5?J)d$=n52eg@k1kk%Ssk0nh(Uf!`sfjz zOPw|&f`Wkl;Sc}*n6baWG!89=M}#7)p~ebsWI+b6KJlBGXc_K6&uJx3E78m`qzv+i ztHjZ_65-iha%i>qRaRmkmImHIgejYhQ&K!q5WpcQQ4jYB5CaN&q#R>V^ac>{i%!Dv zWbw#~ZX9aFwfK?h4WHx*EK<2 z>4Ws{1g5!e>t2*W_T7hfJff(GNU}=sW%~i^Jsi_i#@bKktcOkSSXb`2Qo1Q6`*HU`#t;MH*{mZ}o-~KO?xL(fv^QR~UN^f^= zn-(AGZJk7wZKv;CP6-7Q3AoF0j{B7=loBGiT)1s$o8R8$azR}%+G7m+9$S}>A7#?G zT><0i0`Jkg6!mVgzUK~{U;VzneyW!X5HJG_b%}Q4x}qp-<8&g$_f6&r`%W_U9`niH zuS&$W%IS=@xfv&k-oh-F3+n7|zaR^6y#W%|IiJ~ADoK@6eBV%JSxynabkWO)c>4qj z<_Y@-0Mo^9?^I=}vEB2Kg0;*Z#PwCrC%5s!wJ7_8AerjM}qA$J_5;=@;WfQtAi^Byp!fu`icI5++ZBK%1G9i6j-EHNtn z(jfKVG<_MZAp_hMAir?wFw~sE3#PDqfFXc*P*UMRj8Wdr^M75}fMP8YTh+CLj z)%+_%($8zkW2@1dl}RH|i}co0F83tRhA7KQ@55F#;_t6=S}>n! z-J6fZzY6v(!o!?x1r&Wlolxie0ir5fV=YwDceb7L?Dt!coKEPwet5xpMRz$ZvG@4R z-^KfzEEPeig)FEQ^$c%iIc0YgRM8rWvP^C}OC?LRE%uF!xZNTQ?mR7Vzvr8R=VwX@ z1kV?L{VY!}tW!9{z~$uIJ)kn52^%}^Iz`)AC-#O?SQTxPW#Rfcwi{JBpW}W7E!?CC z=QC`N`;~QqM>>`;UjjjQpvYV!JWd~E>;9kr3;+7p_VX)xzy0OMep{n^+uOdkzMHvc z>7NC+y$4{=tQp%hr;^ zX2&ESJr->_k&e?JVIItjwU5J^lw84y2!eLd9Wuk660yZ`cplLZFmJ{~nAY#U`_m(~ zfJ0s!$Dbt*$1j9r@5-x z5v8Juwakb|mQ|1EM0j8b$C<}TeW06pxN33h`83RK@W%|F4OM|WG+l>#D^+i%h}`y` z*5gdy)TyTV{&3A8Gbz@MI-HofLCsr!ooY8nL~G`OqMG9^DXQ99pN7Q2G#y5uR_%S* zB1PHvuBua2>s=_BFVCe%CZ#B}?#E9E=4INpX5FV+%!j?7AUvkIiimf&ah-`Ay2E_y zt(%ug(_WHJ?Xa+J5L(aQ3RUTQ3!+=6$8wtTCrwD}Ub4E-EV6a1d)|1a41gz2KtK_NIkFW%yLf`?g>Ix!pmS|@;7qxYz1K3)9nUCyTngDCbbdJ~<5Jwmd&QRd>; zcP=LcWSYW*%Zcy51cfkCf*RApcE{5T-J&;KUa;Qf{1hZl7tRYnNMSfjwKUVH!W(+y zJSUL~eMjsLg@`=AaJzBeaJl$;i{xwf=DktH*SmW zcAhQ_LvLYOFsR|popSzwpMLiL@?Y@zbHDAe-nUQh+v~N=_4Swc>ves2l$5Bg}@5Ate$h|wcng5j6rgIp@2WAcSO249O175&}s{$Uh#k0!z3u_+t6 z^AX+Vabl+xBxi?%Oe9BU`iQ6ngd~ zzD3NX?0Y-5!Kn_boAskIyoi*!Zfi?d%qY={P)eC9ReCdbtF=zc=YDOom8N>5p;k}=hdGFTm_w{=Fn3ar`ZH%^n3iqi>FS!b-P z%!LkIPS_g-EEA@aSce1WC*EF}<{S{Wcg&Oj{F8{eyG}FLE2ky4yL|jA9N2E0YM7x^ z$cUJSLB#R?iM29^6)6BjdgzJa@Ximvp*O4>YK<__^nZ1FLz%)&KYZ<Q=ALgl^Jr${ zT^V=~+;H&a9}-G#?r>nE)0FJTrf-NF$0{$)dFgA=-~av}kK=62k_V;(kM$Zn@?w6- zBV!ZwI4(z>_7MIg_fP|Lnfc`;!wvnA?yisa?6JRtNJ4A|$S>n0d6bW%`Z!Kf2_2^C z9N$$uTvhc5!kG13P`aC0&j~%3hI`v{DVk1_Bfuu56t~f)mBV~m)fndKJ>r+WcOILDTrlSaB|rXD zOIkR>ykxaXgcdno0Z$7ZK~XWczW1aurF5R=_rr2?5c0uN)ZEf+ZU-2a#HK}r#mn<$ZT-`~{N?Gp-+uGEuWa9AYx;7Dy_L&_>ov9;ixMG4 z(&V7FMO^^m_Ab*Ls0D{b>0tc3z-r4~MYC13qE-hPo%FfUYvDxnaDsHnpB1Sj~u zQzF)t(}X%>UxPw3&eir^p1zLvpPUu$algsOuOoy-%>qKHi>-H^=YWQroS!f~MNy%k zHI%|KW8YX5ZIjD$i2B_-N z;dfvRxpWA}F<3|_T0C$Bb+gYKhwCCG5yo-a^l|N#aRYeVqz~@L@T7hB-9M$1FAtQ% zQ#@Q&su~C%iY@rasz26iV~sYpUJvha0y0EYMUI-+AyXuZ=s3%U_Wh+!Bt{!~>g2yV zsSnCu1_*P(i1_l+5X3lA$4c|?R}WjvkuXeK$WeK9@cRZE3fW$gnE7G0{vtsmz0wHi zIFTVG%ephG;(3B6hdu##nT;ML?I}`+%aDLTVG$kFI;M%xvLn};QDA2Du`83~nK2&t z?x_ImdrPkPu)b-U9T|bWwP~8tSO{TSPe@vLt;K=fy;MD%i(_Lj&$G482W62&M4hB} z>&>&6HQdQC>nfr}S~CIbRKrt(@FdCQ>)N|fsHKE?-kI_xqM(@tqt+@QDb`G@)~WPv z+t#Y;v`qWj@^hY-Y2SLSnv)9%J>>X07c*O?+M5BWbA?AOlK(CuYSA1TAX4%Ys@hx6 zM1HMm-KB`PPK23rV7{e@%$Iq;?^LO!Ow07k>-FdF|Mu_xpZ^;_{S|IhWjIRJQW%bT zM%ze@x^OBeGnLpk&Zh{(a)P-a;`B1~+~v#=xt!5D+g+v_RL}v5ZO1Z&2-^-(E@uWo z$G%2uoD0q`s&p+V8rwbQnd=SH68kR8iB$2;_s01QGwGY0o-m!XNZhZO7FlNh{1a)U zgf1ZC_9n0>8r`Wx7*n&}8}dP3DQ@> zUFZ&|%#*uGJ^MfZH@?5yk3aVxe(LMK{qmY|r*5&{HtW#?_jPwi>xN;O$VM#zvKx>T z9?KrM=)IW*R3z2m)}4oroJl_qI>K{*k^#D5Z-6;^ii{7$C`Pqw8u*ZGA#lKt9&<4< z3JEcCYIs~?Gm|W0ffNGAL^?3UGK?3*v92SAt}C9MgvU(xa4_Rn@5-Y{;L8Pm{1d;5 zdk-apqC?|21X&)==vWLs`b-(vW5n4@ZO~dg6EpV9-Ys9T#9$%{s>#!4Lo8(erZ9_426g zy{W3XXFNRnpN1zf=;5vRnq1JHYtnQQ2jC)7N`a5{uXh{D*LYsq0EgzyO=Vn?@<&El zY5|z`?ex-&C3n_za+oDNJkdy6G+S$X@2W*KSFEBcDK2L$MT>N^VVzTnfSbFd<(%DI zifGXS35#j2iC}GRnJ7UFo_HX-Mein&fv$i=5$Vlp)qEQzYbX_V>mf=yrlnf90)a>X z`G*s3Cq*OmD2Tah(G=u+x4hETqPO>TKFyKVK+=7*xo11LsF($Td8*#MOd35}+e0MW z7nPm(;a`8y^Xc#Y{@y^GobI!+pL)DeR3;FY*4$rw_Q_MHnE8M442;cBh-) zukJmp(N?ZEqQ!iI6fP$;BZAK#Ac}p5buLe+vt8e49n&NqzK+&Vs;@Ve66-Es{~iXk zmD561zg}4dS-LlAcyL)_y^AWQ6Q()#E#5xM=?Q6_o+i2@9NxkVmyZD0JG4S3QJ{5B z3lP-GAfLZRI7Nkoc@(7KbaY}=5d?s#}K(Pl7lmojkLod zmR!>qvFF2Gmn6`%_Ui9`_YY$*Hkl z-$pWV=sg)J43wh#zVmoc_THJElkBL?vd8cuk`r|Ay+~^Qna)h7^XO0Ek(JDEr&)f< zk-Jbme1Xa32v0zYt4it;%W26LT1SlVQ8<9SA4EV!wyl+_AXOw73{15|#J;y_slA(r zCq>W9Bd~3)6lwdefRNK%i%6+Mn3_s0iB(A#PnuL!_N{4=-hG-hd^}a^RHs_3SuRy;3zeE3zG9m_BB+oJo$E)WkwH1$Tz?9+k5O? z&KHE^{gu7}^xkp5%2cW9`-c54g_zGlg(!MsU)h`AuloGat&6R?oYA}8uX4GhpC46& z6Hd>Ng8hmzk>Q_y*5|MJ;cM)-xP8X+H_;l%EUAieq85Msk#&+%U>25WrwL*7=KD7K zKBqa>cc~Rp0gJZ7JLeM+?7Ocw^ahYxu&+2h^ZYe__#ys}|Li~f6zjTv|9x!RdfV>r zZ|?E-cHj2#4%&OHd*7Och*G+FHv`23t@lSZT10K!wqc%@fn4V>IC9&Ae;}ktx9sy9 zhPgo!HV5IL*+AS6vw_fpmd4?u>m+4c@R68P93i?m1mc8n`Iw41{<%Z`c$||*v?T4- z=>r+_d)~bDyYK$QL*CA%)o9rAF#wNoIW+{y4#b1_e7yB>$ggqGIgW6k@FAui1+RDn z3o+=)5uruDED5t=D^ZYPJaXi}I^Z5-{utOr)E}RfFcoS!tRVitC;9LZ9`8RMi&l9& zG{(`BVsUsW8i5>CkAE#5%f?}#HW#IjR_ttH9nVY=_SQrso|%Xc=-Fj?o5Qb(G;tk`6!74;VSWnr+1E{4uzH$5B(oKj_ ziJbIw2lWpBoIP2k> zJT8wBF?HK}Omhiy>s>R-sj_WNRFLn(skODI3;LeEI=Hbc6!`reAAXDb4I;6wC{->` zpv1mK1fRc!i~WE8-~QkJnQOcM?Z@@?ZGXS5_dO!kb*D&c*31BA6_I%|!PvWjq)O{% z0aYm?wP+5ck3W{eg^ZaGW?n?AesQh2NB3cr;4$T8yf>KD97A&$0lzWps708MlpXWR z7)Jzm%vpo820}SnL~;#d9z9F6bLANMHg^tf1P^<4I^sx{M3n;i```Zqj)U*8Ta0s0 zM)%!l{5;O}5oVF&q>(hq;!6UNLv4vXC>>wwN5|?h!+2msWQyT97d&8=Gpm^ni;3o?=L$vblM2qb(JSv4zq7)6Hp#@vplUa~;x z76)^5JZ#J?mw}~}eQz0ewr(JLvmEmyd88u{H7;NCJh|DrZpoM)TApRm;+PKNAW!9! z1B9FX+TN2V^~ecTH48ej0Tx2*R#a5WGRAhQbRf=#*o!YGRKFw2f&nTRjpqu6A+4eR~ zx-2!!hb5_qh$43YeLScD6cri!UYSbil^zoigjKd1zl<0tm7fch|4Sfd;-sAMa?`shte@YS2 zo#zY6L^4{JTG2PEfExQs3Qx~|f1{a0q}1?$)~vUa)5Lv;147mb&)?$bxA_15zvAa# zw!i)K{{4?Z-aoy0H!bD*cJCI~+aAoGz_6?x$$Yj)DUxuE+@%ll47hiK`a}_fswQ89uG&a$M9pMBMV7|9_4dK zM%<6S)W{q|I*AKLE>W645J?Xgvbcx-;k*9o2$cAbpjJa=oE13Xrqf&p$y+nkK(;E`h__ zS;a@VB$t*xl%R|w0?6#*Avyp_#wZV4p^n|=XfOu9+#b@m;b!14=V3r#NtLY79_MIY zO0ww8eW*HqxI4lLNRbG%y$zELlaU{ZkRR|Sp=$C0La97*`^j?6$YQq+hlq$W9dBs| z94#=a;a*A-U^v41q!2|kkE&cSW@a);EQhu=zVM=2YH7{JdJ)+f=kn#YLj|^ULM! z?fvb?pZ@Ou{=eY$hp=ERF)cX1V7I$muGtC=^X3Md4czs zF7nmaoG&aIKmP;)E-&njh`3+na-nyr+Fn2T{3KQJ`U9W8k>!Hcbg8keXg9mQ>S-o5 zwi_?cY3@yu5DR17<>lMBzeAPN1WUzvXNb?w2t-JN zx^K-|GpTytnpw1rN|D>v9ZCN$-Ga()o`{tmu{Q_d4$2t#+(U|*MTSs%ga>W7DFjD? zc*OQRh2xY;QeKurjT|ZM!xA@$NV(ODqt+0lW5i1yMuU6L$Vzo3X#!|7_>h>&iC;!j(i%~z_a=wCNq83*<*-^q%9M}wK6I~NCt`& zGOXvafmz=Na7gM$=?PWxyQ4+-t>s*t5vO!*Q^dUslRZy2&p=>4&pe>DVl*_Cqee^i zz0b7}uuKw-P>-wV@o-A^Q)nsfmci(V*ar#@2rQ>LIo%Y_^R#VS#_)pq%0@S*pf{^U zQ?nan?SO}w$23jd9G1Dj44xo+1?gCp(srAcnoo#np8MW7;_PT!%akL5NlTWRPjmI& zduyeT4(}ER^&G;s_vN(Is&H?uYc1&_FOz2QHnB~$)kA-(Ym#r?H(xd)i=1`5svvpQM8U)0YxpW zCq~D11q|~k-an(v{O}F8dxWzp_8YZIohTB$kwD*YdO}$wEXstwgOK^;>z&Wvpe(}X z_dCoO?)xTXqHq+%goOuQzTp=BzyBZozyG=Y?dQ1PZ$E!}`}Cep3wJk*)_V7F!UAo# zI+s$*7xNiZgsoY=mE4h}p)4h-MJ)m%ZkgX3Q)I>;j)m9QFmVvmV3C^vCeiCx(duDR z$jMG6s|D$Y5ds)^@v+d$EnkWi0T7N5QOVxeF%G6}1Dg^rtP_Hv%4a1TZ;Du-Z~EH4|T4jw|hrkY$- zc|?V#d2X#`s4}Gt1iZ&sTZ`=Lb~;U?@hx#k6cDX-6$S=)5k%`&i`1go_R@L}WP3Nl z#v%a`%RINO)w%3jFJ$gvgqW&kzi^o%w^s4_bp% z%xB!*sN}qa&Rkx2diL9!2%}5^dRf2_slM-EpzS=r_}edh{<{D8XPHlVFGQWNU8T!S9x8XP@83*Y=88k!08oe*njQM(^`Z7I5n=<_mr+xGAf+N{(@_Wke(`j+X z6xSul7Ev$*I-zQWHNauE(o(QEZds-rRqj)@WkN+gs+T{>$^{#$dTFhdjXcUVX@RH$ z`|R`Y*Ork}I8&dir_ObrWKF^gk6sKnh(N7*%%T8!K!(5XNzoJ|V+E$vj8Ih@QB4_u zWm|EA&RIoR9;-}%ZtSO=axJnH3_sO!;mODZd0;2q;`H=b=~KDpg5?uktHA;>SB5Od zyVg%;pDv!EEy0U&OB;zyTXEf0V{`fStocCoq^2lpxq-}Vy+x$2IG7BAX!J8FRH64W zvLJ7PsLApNErN&7;8LrI;UbCbt<`!II2ko$CSVOKX6n<4%AXfpRn%>!ZH-3U*s*3QS?dmZmNsA`Zw+7GFS_$G+GZB$u zFcbIKuKS!Wk=9#^_;l*~p~BNhAwEZ{9LF5@ak*@|^*2BKk-fft`%iu^-+t*Yx9k^5 z*R99xCX#4fkl7k8ALY9*rCX-zC*Oj*G>i9NrHXDF#6qCq=z9YB>YK8^()5fW+f`aq z(>MNf^bOYxM6>iw#Bw3C*@k`zU|& z2lD&>`uOoz^LD#`^)@12o^LO=F^2mLm{PY5;P6an0`3veBQhbkW14ZPtR*ce??a?0 zWqu-hj0j>BCSxK5nPSa&1_mSobb{8^I5yKV25uG^TPKcZ^ zG$kh`>>+He5~tlQ6RhKmCW~6QE*R;LfFTs1Gbn-5%k}GQcs&@`Q~*o@8BkSgmf;FD zqfo`>m|_o_2iKLvTd3NIOrIejACb0AqD?axUM7q&%mB&ll4%X=Iv_jI!k?(!0@Cn*|zCqe!tEMaZot zb1^*B%p>~NxI}Hu!Z`#$RHsjkw+qa=}o@+%K1O zx85@2)zjth`Srj4{m=i}Uv4k&0Y^smi%1i=57$>u@$#Hd{rDU5@yW zFxBg87J@up@;Gd_c=uLX6KkrPx=FeiaJ;BmwnigY%tQ98L<&N3#_d@|^3MDk=p5QC zQ*6J$T8=vqvhA84_q%@ljeK|Hzx=`RTfcRD@pX**cz$_#_rjo)IeofAZH}3$!&6lc zj~U2B>upSecBc&`A~C7z7V%)*ip)$ibI&M`pvv23C(5HVf#E?_lq|@Cm5O}PMd1Kc zC%?Cg0e#xbxv%|RWI|JxW%DTw7wQR@N9a;=&`dHTRV%JYWuYbfc`1GMx%2ygs75-2 z+UK8tZy6oyW7{T+J+l6$8nIjwzpBP%g1me(QkEid35dD83W^e!?~p74bgd5S5w(m$ z{xJs^8)*3h2@5J|lA5HZlu&#%D#%T3ybdXL2I2b%Sa~ zgvZS2y=Ov!h@ixa>9@If^sSk)RWI_4r$|KjjNV&BAYy;Iz$4w+6p2V<{%f~w>xdvj z1^`_F)LJv~h^=?p95l1ut(zGYIbG-UT*Pw$V0J%-X<;4v-l>WtQKa`4;k`AqVgQ z^ikYjL{;V?g0xLi@_y*6kMj9l`{p;`f;n(`%GvUHJLMEi|7hi>|e7Bn2 zQSXQ>5vB#OB{~g#r08HMFAipVRFOK3n@$`Tajx`;VF2e^zdBQ0{OI}zdS(pLl0uhR zUsN%o)XfO{=SqVqHwLVlxHyAn3(u;$q$nlH%Sw2uvQzf&8;-0V3i=ise)u%JMeKx(c+sQ%ic|@tM^4!Xi@5gdWPlBWeNnS$dKxl4|ZQ6?Clb zK|&rK^mB#PN(!B5sAMHMgusQ0WQI>Bvy}NsvldvPi_0jPA4h#^D&rVfa-PfO0%`)I zSR5+oy?F$S(VP|Hx9|NphDSt1>n0-2G~B0qYo;RiahPD+x>RBd5;6{NO~Ap{Y#d|j zo#xcFVNVFM_ZA-36_CDfrZT6;^wvz&=pU`k%*?~*bW^+EN82|ui|GL9*i<%2OI&rm z>}nP>vKWm~>kd*$&*?5I*Qd*IziYxa9djDgT5GLZ55__xV(;z6^Xq@} z2cQ4hKY1~r2}Je!N-|~MLi8(VNwoB{i*E|l1f&HqGj7gpgp5M#$8uw>8USLi3EpIQf z^^}fqsN^{C^o@kb@m8pUo42|p_Dep$k?n$Um$pevNRzfDpl!qb$o(q&H}W@sFn|4@ zj<4PxZ{NN9>DQ70fGlr-2cE5Xy+>hC}<{2KOe&P8xo~}DGkC~<_s`nYK z+-b_rK~z(qVkqge%o%FB>>SE)MapPXR<3u|R)Iue$`yizN)ALNFJUpQXga@;4pNo5 zE-EF*QlGQDRiw8*F|5;C;i3(d%SdF+pCTT~v(x%%z`xKS;=E+`{dZe>vjs_$9ac6ORl ztcJp}1tNC(?R{W{eV#>v3@4d7PTRmr{w^-v0-y`CKFQZe>O`xdUQPGIq7T&+8y%9Y zSxz@=lOvhHmujZ$-gK^%f`*EbDv?63iti-MV=MB3sja-^jTk zw<@pci0}TzKNDa63;_BLpHR!T0U5Vvy(wVH^SA=@auQi0B(qW$*CJdC27{BXE74FK;AM_E+gCBG85y0w4`J zWPgpE++RUN_9uyu^tilMZ2pgb)qnT*j<24_^UL#>Z=~55U%Y+y=6TMTkr7~CXN;h8 zlbIbJs=5esx$W&Ze0l&#k%*j$ySqRlW!Y9JMcc9D(OLTy`t#7|NV{34&``guIwgr? zn6|QQNH%SjfPVnn7L{TZvh#*z^G6PJjl9GSR`FSaIz7=3-g|sBMj)=mo-DwCr9gbq zu1(hKK`jZZ=1OV#9(v@0f{F{l71=WMSft=;Z6CWa7OjV@S{4J3;;j$rqf~&Co)Wpo zV`eQnr7k@wgo-8GsHnXMD4ww(7c}eC-Vy($l~F9E>1gol0dUon3HOIX!dc+3!7Rs+z0{OC+(a* zyjJCyy!O95O(FCUo^|jfPVavLED>QQLJKPCM+7r< zXv;Dq(zCbIy^x0osmIxxHr92Om}yMg8{SmMa5LHWK1a~XgMi)ivRL5>N$n!vWX>73HTURDPVs21r==fms_720o@ekz6=47y=J3{=i0)gD3{yd* ziD)+wF#r<$UXkc>9AkLwy^X}re((3c`^$gn`qWZPFBh3ZOmet{lS(0gG{tnZj{Taj zP?Zd6s%^_T(FJJPHc5wvzW!LP!@Bg2+jGuCFriJvkPe@6eU--zPoLm+1E%zAhD&d9 zJR?Q69etO!OJoSg< zH*Bxr&h9CWyL|E)zWCbzhrjQC@WbQ9-+uY#_HqxAw{KtEBULl8?Hv%W8%CYdK2i4! zy7*TYCNuMx)toX8Dgq+v09mbL>Fp@vF0QYX>i~0%>dU(_0~1OY{%z|l$_~t`K9*~? zrsGG7IBlI(VKz#!1z%JLh&bR8s8On#PB}SzT~>5D&&DM1Z#P=QeDVp;zHXO1ccUl zBIk522^Z7@DT_|1S>p7BT5P$<(^|SD?5B@io?LRNoEif3d|xVl_U!DbiEpZk>6n9o z14_I8T9ejdQ=zPz{QfvYzS4et%^vOqWe)2`c{DnXJ$NlRs{Fu7;%nWO?U*tHX zx6I6O!?xA#|F~)EhzV`^?kl-mAc{HQQ@2gFYredf))JEU_ppxE;y&!7Px5{jc#N6t zLdBGP&CJ;-oLW-&MqeLz+>GPITS4Q(c6ga3WoEb(LsORS9P9ApsyEGS zy3id#Xxn%I#udZ>r7gI0iot=6lWQYGp#r3S`FAMEa-* z&pY?nFI_}xdue2Kv8~Po7((bY5;8S0I)|y5h%o${OS)~_JOY74dcs;WOXL zF=W5s`HgH_VAIPkIhlv;U;oJV9Rkclro$A{(j9S!d*;yo1Qpnp9wL$97>8UxmXAKg zal`h6Ok6(_H5|_nNNaf<*e{|gbJ89tm6r(8d-T^LSNzl8m_PVuKl|+;zkBmM#=O73 zy?=h8K<=0!P)|&Vhzuv^Og!^G6J|ac{2^vGn3KtNPD`e7c(_QZaz)6oYt~rzBhgGF z;j6XEs>|iLPa>9?(Pf|E*&M8iKA#p!`CtWT0;z1+SM@9e93f}&EUPoHR#t^0v;OAU zAx1Lbsy6v$VWeapV|l(yxzMexV>SLj+UK8rzXT(>%HH~Kbl8w2su^dGS@UyE(Y1Ff z8#VsG2h5K}(l}8IW2G(wr4qI*{s#L^Bw^61XjPFbVU}nHjjFTEPFI&&tHmrVmFZqZ zu^@1ey7Fxot$XE+5lc~!X1DCzRB54tR5gfD69`BmM5tVsjOCmc#ef2-!DR(isyP5u zmdkR_S`d8o;igs`cSJ;xbWD*qap?^10IjLjd~hDM#C<51}ZdZ0I&>{m=;Z?(3fRfY97h6AvsK6r^z5@wbjkc`MpExe3d zxjV=R#N+(tVPoAg(`Q^SyNE`(Kw59eK<2i0NhpLeNNN|umo5h4*|cfV4LidLkuz!zVbC7W*RdxGa`!u4O}kUoDsuOQYQFvfqe0p0#+MvCB1QS9PWGR3gJpqdF;-i@`tap5axUUdO(S1JnW!PNLc9p0T4^KDG%I75+a~M1(GIlyxr1~c5y6%>fkuRx`e3k0G_|&waCszb>6!^S*9do? zQSMq~J(?jir&}*eTjGEF-sq83a2e2h%jpnMt-2GzD2-40^q5mjNab+OneKhtd`vTy z*1q@`zx%B}|7Tyl?x6YKbX=bD`Mqo#ZqKq`vNdF)?-C)c5jqj;>5iDvJMKfa1_;Ih z1o~Fr>1~(e8ERseJf0yUm^nknO>C3vQ#?N-Lt2yjGpnlPglw1xGIiV1?{R-oYiK=Q zUS!`e5BQMQ<9MHHd3#H1Tiq^^z<9=V?LFSVlWuu?Nrmp8%5VLS|C8T%{?|XrG2eXo z?oLwkbRR*_xj6$gb`((&M5QU2Ki2^AKQBMT4ddKC_Y|Gy{*A{9`@w%c_ zfLffFyc-Zo4nN>QfcuP6mIxMO0{qs+0)WXuSgEqG&p-d(sTr>YQH8yxiY^Is)#o*5 zNO6j_BDAZKuJstGqCSgYkv*_>|0v_>Dv@OseXiz=?*2t4Tw*}>q$fvhU5&a{G^D2- zqx3>2w6b{CKt_=z2s5~vtp_ZMyFhuQ>jxyiWeP$-!wAC5isW#2Dg#gLA|c@3dU$0* zVo5(_y~4HAO9)to%uE6)2%pB|l;;MIL@U=?T_CouRfD^O`87?c*%MXwxotgHp6|Ky zBnBcyLdtcFt_d+7VYS>Cg5n~PIVX@T=jnw(nTnM=!TN$QM@C8lB*{}b+Tvovvo-Bo z(?IVQ)2o3`^iA)#1IxpjTCnu+h<)GPy|-4X99GOscc+^u!L^r{`(^LPeYB>{?A5EQ z&&jkp3W&m^w-({yp(5MfUY>8=G?BI#+~KkBovYMX-`)sL>(RSGq_sAVxm~w8DXT!U zX5IXl`@X$@^VY}w{Fnbcp5N%U!Av5s?Q(sJH*e(fBz==`Afz`ueIhF-VO+_05K)?*P$^AvTVSlaL9=G?n zex#SzaF1{?_4}L2F z^?y2_kN01E_44-RK8B}ErvXfOWC~_bwkI$5xphP0I9!`yF=A={?jEh_iawFv+ZfTd zzSeP6hozZCVn&E*Q;WQ4|^0*IyMP+$Yyo=>zZ}YUNHEMzEn?eLdYm>R)|1co5_%2geTtBCMSD-Os<5 zSW0T7&?fC<{+>;85#dK+MBrzf$|6YAB#x4kFRF}0LdqMj*b9&3EtR_G zkZMK?v=fzIvkZlE5s_-DE3ZkG&N}$Ho4^J0S>ZggI zbJMk~PJpO1DIc?ME!c*WI%a}EWcF^<_(bgsP_i9WRh`pq*{P0Ww8j&3AuuGZ2dGeQ z&4R`nDhZ7HOhKdc#^fAuRK%J&yVMAnWHO#oGP3p7n)xwRb>6*oyIi&;YOSTTPRPD* zF=p9lJnnF`Zn;tfftBTr9zju>NXTN7X$6PXI6X}ZI6zh1(lsd&Vk^VO@Xbo=68{_eN_+@HKWwK$F{ z@$NFFc7-)+t)#V_Lwd&;vTY%X`#Wu21c*S}a}HJ0?W$&Ryod^<1CH$i9}vj)Dt*ZQ zN}B{k_A4@ExHLn!Hp_YFei5HA%j1x3%j2HMU7Myfi9;couBL#ewX{v<3+Cj)N}fK> zcV9wOazZ!kukjq?zyI&^4}SR7AAULS_vwDWj~SVQ+kKWaNpu9vOdxa2%e50_&}R3; zZ*xMVnT^OvfwPFuOf?T=2uz3Pm?hdQ46xv@HfBIj_ElsY2%@ZD=u?&n4aQB0P$te3 zoc(nQ%#4Z^mYt}sm}N$*+3EYB{{Ct9m@f(ti{$$zDzsG zkS#KwO1dwIfP^h4@6;-3#C);*01;osVfH!MEvTrR1SirQ22u+bPqdWsEBN3-OrhL&dwdDrcq#OCPr2s|3f*4WR>!asKp?|8`q+Bx~m#<+eoutuC3WxBZ#b` zw6ASV7k3g-AwODV_u!Q+Ne%_m)=g5uxyK8H` zb1xG6_4@XwU&V3%{4f1!|Kew|HEer~sR-r-LQO8OF=6ZdK`%OOn zv;wZh01?|oM6XY97br60-Pi51&-<`%{X+iu%lRw6^75~K^#1Mh@p6B;joUHqrOZxg zHmReB0x}~#B2^=0%xv9mN2G|hM#N!qA;Ge$>X6CcW1`Zkr6u9gpQSb+q~Fz?p=^q^ zq$?^AuT4g4{rvYepvTw%a^Dmkh8eO2jmR` z5^*vbge!ttq%FvGVbLWeDIjy{9+T9D$TRY4jky`6O(#zea!whu+>%guD>v8Y6ut(5 zWq~iH`B_{ZvMfl_$R+2i_rqGt(sqK^tnWmEOFmPu2hN=v$y_`O*q)bN4z)8GCzRt+ z86{`qOrW`zRK+wbI3EiolC@g&2w9FLLgnm>abhOA-lQ)J z5v$08b#SbjE)eE*Y31B3CMI>~dJ4b65ZKIQgrL+hV62$ML|&0R1R`>saOY>b2B$>k zvR9OJCofG!+SXaD*`ABh?T;&w?53?ZHS=&u>AkOsO58(C{XWe^L|U_$z81XV9!7$o zJ43B5TQ4fh9100IVY3XUUw-TDcsY7+W?K9|6-D}VvW~1-L?}c|)aRr6ITL-ik3N#)4un5HBPO;hGUVyC#(@g^A|N|Bd0&tpzU-fp99 z%_BUdESN(^WJLDfS+^sg&BjPE84;f7-Ny9Zo2QJK9&i!&Oi44HAv2(+9vLf}bVd&D z<4%$TBOMAtEWkPiE)UbVhl!&NJ}7He6=IQj#X zp@hgebO>*r1#gplty;){$!YJr{>jr%W2uL7d06u*uHbEx6=W{@csBANKPbd{n$H0c zTf}gZSR%_hsTKwZN*0$W^`wd~*AZ}wiZVwHwpzMku4KVA+tw*OX}y6W@9H+dWY~S{ z73YVHRjJtlPRkA{K7G4v>Df%R(72?e zhwliHYLYXU_Ok6QJe%pbyLD~b#^rO{HDqS}Q|J5fQQ5@NH{%h5=PR z&tm&<>tDTitrJo8@+6;B#jO5&A<-eCV(LBtvPu*b%Pqb-*7`zLvJ4|`7hhAB%!ewb zjErUAR9&{zL&g6e0uFs-4%C|woYL^$z*^zr$T0g0IeCtsZARf$79Uvz9E9vB0*%N z6!Ucc43Q$3?^_S#7*0(A3CIz#?d|q_gMiAnhzNHzZB3ie!Oq)e^9UMTm&bcEk1|Av z#Q-6(#D{Nt5BG?i9t6s_OY<4M+wJ9kx%T6E1n6A@3G}V2Ni&P-MB$k5(zo84RTPUU zfY#eMX1HH2U7G&%U;NI`{e?es-8G)y>#J9?Z+gFFhFz{QJu}f7+Mc&(*)RU?tvtO3 z`??5y{SAnrZ~F8q-+it76}m&aaL(U0xW^na4!K<62NKx#cz$P>S7{;{(t1X~Cn(*{ zz;TDD?wcGp*{*0?zJH^wlbk8n*E#R$(Cs1##F3X*$ji&$`EQSZ_Jgl}_~pyj@9xKV z|9pqajF=gf&PRe4Fq3$RM8HhUbWV84pv`Q#A;VN!x7&0;R_q8oB~$Jrw!LM7#gUdt zvn+aKMmi*Ag^AOGjxs-xbpyK3QVy>4Oc$NhJ*u$VjYLB8j|k=J{-odZf-Kk%Wr}zN zznMX^UzE*%J~reJK=lNRi=T&6gmNlIB+JMMDz*mqn!@R!3F;v}|NNIgr=L@{%>@rA zIA7(o+)mHT!?UnpS$S~gD1=)ILtgRSr^aGYsBMwRPDaNn^s5zL2g@S)R#{ByyQHRm z;xbh*NqJuEkq5(if$XJf!3Q#UWM-PRrJt0dxy-pPg#gfINe^hL`9T)Ny9Dc*08EuB zf?5gL0c4>Q87dE3SDw{G2D0WA`bz4#)hxxc)@XKGW%Db|21wgF{3wi z=P@Spm|O1x+@5dR3_?O4e5NY8ngD7tjv47{+EQD$F&)VCP?Vp5HLX@sQp)u(RpfHn z$pokFDl@m<*(RxQm<3XLv%a@ECV<{7J+{3`KrF`*`?Yg1%j2rH;=_BZrhqU@PDi5m zwhV&>9<5s<5P5z5`rS{z7{~bTU;T?Y?oi3+XX%E%i3r3*GTy({ZfRTgT_r_Z-d|+f zO_6pHYjWH(L!LfD+b|EIKFlm{@5MysKx;KWHA94c^sN+)+xv2E*)Gx>B7wl|UA88d zr#x;VEfDa5*0DWd-nBJ<_eM-*e}y^X_AH-#C*O_R-~QXTfBt)D`@65-yZdoWh#bdc z%M^)#ju}C(4ADWjEK^j+j70GkXp@^~5|tC2R4V&S&B=P)x(5>#YCuDj;ELfeh zkZ1~e$8?cmPgjj&Q(QC#S-_zvBqE2`ZiYQ<(&}?HcqDi{%z>)R@*dVGt--1-q9~>F z2PU7Z;`4?ri~kd+E&KZ=Hd#t(4$@kx87#2{?DNmRUlNb%SeJ>!qng#(v;2pO8cdGx z`4%XwkP-@>CLYBmIe$i1LC_E5T1t9nxnQ6ZCpqD9-t8w4OtrhLt>*d!q^fh&B*F7A z*BK*=xFDxSkn;{e)lLiTIEIt8Id8TV+DK$Co?CV8bZE|HFNbSy)P0EQos~V3DGek9B~qK+$Al;g>&S#) zMnWwjJzz?+ButN0bt>bD_&^;GtOsf+?N#K$E*_LP6vrYfo=LbfalWsjf8@EYL~%Ma z!q=9)kl2Sluxv8~kXh+hMW9&luRypLqaQSdDuC45Q4(6)JeIw3j+3QZ`2b0fg*+76 z{YWER_`wG}5U3QOQJxW~5YK?(R6wpX;Q@lz3agYJI6LCPyj5xr<%J+JfD_b@q=9mw zy!E_V5m3Yl=#OEvPb=WL>`Vz*Fe5TgfW#^wVpjjwC|MB3vgq2HF2GW$R zV(n>L)67g?DXty@>02vTSfrVnnKnI+N$3#e2b*oPIm(kdoT0bbxHE(_0?&}9n!tY9 zL4LG~06zV)_rA3`XA0ayq1F{M#u#(Xy|?t(_Rf7EF>8_$DPPoP6dBGr)f6e|-Zl-V zq4U@;{rTOkx7NCiV_37^Te@4faUV?nydTFLzBNmO((MqEf-!vGy9AE=eYu-PjlfL(F{pnwVrr(Zo*Xcbx(KqcK0JMo2ph%bSoQHJH zyX*DhFE6WyqfnaP9+3=Oo>H6cuaJRpU>w@JWac
ID~Kp^|8ygkcDpT@f{6O!Yw z-sAov?V{J$F<$)n*m|uItIa|NFI?dGmQDgD$~PLf^P3-lUbxiQN@f%RX{!K zFAopGtgF?{8H9=T18H@sz1iNxSv8aan4wh@#ZQb&z{0PPHzUlGbx-ojikw& zRuNn1#DkGsTe?-pkO}3U%)x%T5R7Uat6!|SzR11{BeCy%=NDHAE4DJ$;})ZsdyMiz zqiBxZ>k06F@Ol`z~Ln92mZr85u<)T-GFuB3%tW`P$SMwbrVLFXfLA>!>7%kagG;;m=HI zuvyhylIBpfRa;ng%_#K}Xao@_KHi!Y!BN+^w6Kow6je2g@CCJ_^_Ce_!SEyJLX~f2 z4oBgtW$27d+Mj1-KN29THX*AmEz^=QJnL>b`{MLTnV<8x>RaY8da^3FJ-~Q;w-~C_wvL5e6XmJwBihO_0NZBvicMutyhr~qR63u## z`%U4If~VKGJ)?Dq>fZC^J;bp+>9`{uIpo#Hq9WsfTFyIK7u)z<6R2KZ$II79(T_gO z_g}*{G10b>#;3hWhGyo=``oU&f1JPa4_|)uAHDn8^X(qb_YuID(Ys~B1EPAnO`F*$N@x9u>{~xZn2ON^BeVB5XO_RQ(4M~v zF9fKXs|Rk_ODcthpi)$pFDT!aEV;@#K=WD3sa%ao6e0-vXvtdI zV}~X~N8v+F7%8$!eu?3TXZUA#c~0qfw3UAXpO*$}p={Al3<$su}>%5|mj4 zxS)Gf{5&at3sx7&0vyhuzM4`aavAgMqKKc#u=Vk-3yKp5E;N{vkf0!ob)qoiiXx~} zKo4T{>jWZ`Lb#ibvQ9nC-FjGP)YU&={?6PfztC*yRn#G*GHB$?p zUv4k6G{-xfw0STi$?L92jl%r*A_q%|dv+nSo%{eCElO)#u-*{4ZH5#Q1~Ov5ZUVH{ZZCI*v~JYcs%iS<*LC0bIY#)@ zW)B!~%`Y*0fA#dm@Bi@C&;PM+{#)O{aT7I2Vt>N@B@y!KW84o`=NyOZI{?{tifPrF zSc5dt29r!z$-Lblfqp@xXhS+8(5^^_?m)=(W4V5Wd1q%WB6+-*{Z($SalF^9!z1T_ zb*RBQU@C~?$fwsPd;G(H@a6yTYj3`~zkNR5z1%(XaEc{$L=4Y417;k>Mnq(KA`-nD zr2G!=GsesmF>5b}v+MUn%$m#sc%Gpto}sOo=)t%k0b6E1&Wtp(6ulj@wMGG6e9#NB zPgspb8GbTua5Z&B(txOnP3MhGb30uQpJxjz033_eEE?71t%V!)<8m7O3la14Je=<#sRYT1@v+ z%`pm!!$m2W6+)pPAc(b;TaxhFAF7;<;0min@tO)?;dzyD%8DwIyG-QD`@d4Z>Z%oo z{3IKiSz#w7jFYwO<8rPtFdy4Uu41Z;OVrzyT*oXtG%ueXg_6>T`df zN_A_oiEe;iKk(^j2L4=qx2m}%#&vi#w~!dOJOI(0i{1W5SxuNJM&sHBFB>CfPqRr>R;u6`8{?mo2y; zU7sluW6b@sAwvP5F+IW~-T9F<)5vUQa}2Ii#Ei|pHiHQuZR^L&p=vyd$=kZT+K>Bq z^OGO_vA_OT^yO=!VbU%#?{ayS$066J^r6(|YdqUeJ2JY`;Ox-$siYeNjVsRXDywQF^>yjzG@rd>>e{8Q_Nov0O zxQ~4KoB#FKzxg|dw0Ccx?_(YwGvXMY+A?u?YHtvI8EztdGsbL-=@eo+=nI5h?ds6ihIa8Z>2Kd! z;|_AEkP5gs&jl&p?KRU-*JlQ#LOf`{cOG?`nPnuXFwqF^LX}z!7QyMBKx-|LUT(3e z&3s1L`$2l|KAdV7QZegzt4b3&7pAsX?Yk-**9G zdS*5gAMWA((qo1ylF^scCI~!I5_5TBw`Rv65}jt{?WmBL2}OEF#ka(xmrkUdQZ^cb>jIBxRl8~OZJuAjvH#Wulx zNE29tcKAR{WMIE&>+$?n`VNHJ1~I){C0tr#`>bj*?+Ca3DQ_?N^ilrwtNVZR-+cYs zfB5y=`|bIDpVI?o0uh*Ho#8edU}UI?iq4eVtoHO4*+jQyGxC@T5wh+m^cU4*2I^zi z;c3l0Gl)BxGVXe0dJ06_)`l;%p~S}Nkz&FHU^z{$>73_jWJ>wNiI{ohjPwNE>|#|W z-rAm-QV#mDyg<2S0Wc%6HgkY@hDw3jW*-)i``J@GMlc^FbFOe)|AVtk+ zaWZ|KY=uyxj-=FD+NBoG5L~{hN(N=x%L8;Bjl`q1j22a-xOZ$A3u4PZqBblhjIwv; zb*MNWEoo`0**K0yhJOLq*;-?Wy@*iQJ;wl7g5}RAY^bf7tWAa~hmtD6rb|d3p{mq^ z^U295x!;f0n=s@|R>}-uX7@4JM5~F9D7Tah0aT-+=Few!!&ZH7SK+Gzl$f;pYP zb&PpTe|oxX-7?cm=l#w@F^*wPr@I0?C)D)my4~L0%+%D%C0;VTr1pNy?XuImD%_tw z`RM-k`FQ*0yZ_67A76iw(qQDnZ?f%@A=lR!cVOnYD;++$Ofti9h>GodX+HPYCYIx! zK7AwaFQO@}2b%N?{4OGBE$>H0$bP|bqwj~rj5$k$-F9e$X*|D(Ngj8(zLFn*@$%pP zcR&5-e{c)jjxnZJhvN|`&m(&85irq&+@@bI9f27suoOh1^>!bjW@Ch-JQ0VNQHnLY zAA_TI;R|L=JRw8$HiL~|>z(vCcTT(ID@1tI1q`QWNW7?FRisQ2)6xYmQbejo2}Z<| zFGdRxE_~zRk}<>ChO_RJ0}tCE7;ieJs0jEyT~6j}6B&rw%?Vp_TkEUS=(z$3MNNve zEGI9pe6m&xRy4cseD;e}`W0djtn7=yR_-30$4TZ0vJ^y$v1{B-74@X>XTHp+-Y}BT zb3yG_e*0g4`CtFt_dk1kySqocdw$_6-;=~OO>J}p1d>#<8EK}+%v3pMl#@n4 zRVEurll$;kx`&ErRTWjr|7epqX0+Bugn(GBdgQik&Uijk(mb+)y^&xnH~TL2NKp+a z*~F*S99u`4a&Uu3>j_mOtU@FtYG1dSsWtOg1eesBDsUFr(h@(!CMOb5v9l7Vh8yA( z2P{V*y1ECaV&31i8-y-f&;?!8Odu!C?y(C~ZX1iE7wIQ|1DV2Od@c5XWtzVf7G$^N z%DaUuvY{jdfe=-ab{U~ghXiOWl#3dX zYiBBjeqf!Ss8SAziq|W9PgZ<_r#$ygo;anKvb;yAz@$XQy{xj<>~Yecp{8nvlvqq~ zZB|HbJ}~Q(n9S}T!1Spq!jB>fv&ILp!Rum{G?Mcp$}O{Ag*-&?SPFX~rYu-~s{kp| zQO2Qbe)3ckDCT{3Gl-DBhEjp1&)BwZi#oY_gP?M=Sb>5vM8#qXdXg?8f@bOwy|r;4 z#G{)oa{ygY%mNjS>1+ag7G0|MmJwECMrNQl^%%+5qENMMYvF#m>>?B*wq_RYK0+IOOr8__zL}cONy8>OglIDpy?pfg z<%_R$w@?4lUyPsqNV*6lk4pp&>^iG3XyIgWk zv97JB(~Z-`r!UH}gMh?XUo{sBh^9!9b4{JeCG@G_KFW+S5i+U zkRs(Y6%?}E2!vbZ;S`r?Lhj*ZlTULlRGbQzjkRMZ5L&EUS_u(5 zrl)iLeV|I5Cp-%P)Me4*kuXM^t~puC6v;Ea7ICtKh-bk^Eb74tb}zEP+Q6MPt-9EP zwpG-=^POLmQxYPn<(osuf+=|?%R*0-AQo$)sLfn=CUxz!QF>G*Sx3<7{>$;M%>CAZ zL;koisTK9wr5rF$ctTEGc`0#F!#Y^yKL{pR9WFto;$LGy{R_RV5cPFTE%+VFSetLW zLT_nJds4k+EeUg_2tJsFAQhU?wChroU?*uP!DF-3P-7)l<`y~Sq z({wRIP8^3eB#a4Z7z3B5yuYMc`}DiVum0obfAA}BzkYuEJi_ySAGt6+58<*dQbHvl z$IKA1-flA%7gbYmcm|fSL8d0E!-@bAtnid& zo-C5eO=DY{46WfoEi2Ve@OVjTsmcl=EM!0z={t+z$d?+W+#}zvQg93P z7eq#l4t0XCm#jpqnh5QrTPy$vb;d67D1Dy`)3DZPQ^%)A9oi9r-mIBbB9sUOO_fX# zvUh#q14*|j9y3{|8Uwji%jyM-(r2xBfpocpF{5ubZi6Q0 zDl(4Qdz154f%M+y;r-ImLm_=LKis;iX$IUw1f~)xts8~)BI;x8*9`$PQEOWGowVL! zIu~1IQs{2mhRFT(M_>QW?>_zFFTDPR@9N7tT%Y28h?zk1@(SsBzw6Vhj0xQYF4odR zdk@{DZFH=m$welRBkwmcLk8L|t;u)^X;6hb%mn1^UGw=__ZG6176$Zo>G!|?D?j~v zzj`13ay#z#>6zLr$R+|Hav!duy0_=sXsxASdTv`E5rAaih*Z_yO;nhvmdMTzOT`^f zZQaJ8%WX;KhNE?>en%up@CQVUSynEfB4=&OgnK_vc%+&^B_O2!6I>>P6$^_b#C&FT zt2R_53jd2lqV$B-TrZ=dT&;R>t4nDdNdr<{p{*jz5v+dq0XkE=D=O-XogIl>IcjTL zmdgZRMe6Wi$IH}O)k;US&p!J;KBU%`Yjd3@r;0hF7LUXzKA>w9LqI<;>g39G!4Xxz zxRG1&8)e*#5^$fwzItyTuoZ|47dR{>^jp*v`JkCzrwLhWOKTFst>coMs9HYYiMwyLE+@7<6& zXY5bA&&i}v(yqsxTg4{m)(j$Zj=puCuf4Z9JkkYf-F!}ZOVOQb%o&kpVk$meH@&?a z>Ct;LlZe@@l~c1-9*=E3Jx!%=O@Y)r?&EshMW87ZXjMs=*IL=tm#RctM0%OFytZNjk)2yXKrk4WNqqEivQeSzo=% znbJ4t2D%ieLQSk;&J;!8a}K?Jl*bfp5+2WAC)Die_ILmO%dh|Dm+y{u&-XE%_t9|b zUF|+yn~~7wj>BV?X|3E2>YR1I?jGqWl zrbZ&RuQ7inyh``!ic=g~T7oR~1EaR8`W3ua6gb*BV>a}C!{RTN(VdK6oiwR0e=;Z5A21P%vw;A2gIH`6Sy2Pk=@)gQuI+>N!pPbTX!(>%N-qpL zwKxpQ?|th{n4p7t0R;smK=RyrvVs?YZjD$rE!_6LQK+2>qm8|Xs-!5lsFGg79YfzNBU@|MnyOf9yv19yF$On3gp7`3Hd8e{Zg*?eTATNw z%yJbV(YwuvF`enorq-LqVE&l^=*>teW&g{;uWx<2-|t5&LBIHzeeWjXGiIioCn(^N zm#1qS4#5mg{;7@8E0HsN&em){*N{pQyMOPiOnmhsq z^crZb@z3ikEY>@HE!4{WKSj!tR3(_*%}_NFBFkhF>k9LRESYq{!JtD8?;{bVi2~8| zbJ??2?+B!qH`3WfElbjJ{6Hk2r?wl+oMp?o@?qH$f55*`0I=_T_Ps^hC?&_~yjcpp z;-}@pQBm5*lY~dl=psK963#aq5hV%3Lugk`xvq7hsPNf~9-C{Tj}j%GX!W{;wO1rx zF6kv*m<)FsS}=Snw2&lG;$rSa{U}jp!bI1lCeAGt`%>6awd8!32l>hu!Vur~+b?v3^z1s-KaZd<2QzBNm)tSGxZsMjAkM#G3R#O5{UZ{)p0*sE2d`O zH$Uh>+Qxls+m?(D(6S7NN87gX=FJ`X-T(PtjCXIKjTwDng5xG;XdA{I+Hu^GA!acS z*|uy?+BdP5>C$_`U_0gutY_TkXK z&EI-|#LF=~(mm#IZB0bPEP$881GpbtVBqj<#>G34oAjs|MMOQr9Wxg77otsu!z%2s4FXLWXCGo|)MYNzqwTtxQjrhycw{vlTZ~oUR(Gsp^#yP-YAXddA2p z+Gd~$C$H+6Y;9JSL?hSweC@;Nyi=%Ylz&ETN>H1>ZpG69BJ`%)x9;wq=`(~@DuU@w$KIG>z0EPVt@qYM(3;6wmGAoo)Lu;5*_YKD(a6}?Q!qInRND8#Uws`kOuCEcJMsJyz zo)K`Dgf#Q*W6S>jZ~e_5{=?sRf8^nQI8~noA4b4MkKxvgSb?W(tsT?Fs4*d~uE$K6 zjG2+J-ck^Ob09v3hnlL=ABM}1RGA(LjiMCTeR}ULf#YzJE;3oB3BN*HF`Z{Q2*V=) z+ILWhs~)6YRLjZV#+sXJZOD|0Q*C!j>Xu${_00t2Di;PTMyr~|y5ou@>nQAW^}nop z`q(j63oDDk%to^?OVTD#RE^U(qz(fuJB}(q_TBG%A1jh}Q3%ggxpLD_f6?kFqcQ~8 z(tda@)RMH)5jb?mIS$BT8Iz{5EUYuv0g|QjTqw~}eV+2@JXsTkhSUkMUL00UKNoWg z4qAerwMeM_EU65XX{a@#?aFRqF>*+r#`I>YeCJxC_ok*2Y0can1ak!CE-M+4@Y?PBF_ijDGmZ$` z*2c?m`RMh#fAa@V|IROc{M~Q)n>T2^Hp??)+c6K>E;$cC;4>oh<8NcUK$_%4q*N2x z0f)P^&O~t8n#`MO%QHy}f4~LdF(NXvZS8(| z?+s;U4+6?eT)an6iJ1T=#mmYUQ`U2-THTGSk(Ej~ZI~bP}_yxH7AP`3B0lhw>eB|KRpi!~@~X-S)#SRaNi*$9@29AzfwOu)CI zxfnESCkn=8<*K||p1V=dP6iKiEoo(eKdj?%PFBi>oCNAru{uWBk{4fYah8dXD}mse zp^|l>yq1&@n1F_gRTC!d1f@77xpqCJHZfJpB)j*-aTdyp@>_#{D$NR=GK%urS13!4)_26J_3nJS3f zdYkO)YcEtB{?<()X4017);Ok^%wx3PnPXtR#SG(LT8%#8ae3NfIsj|NKM2yy#u&Y~ z+wHDqBx3LT2FdVH(YAM@9N})QEvPNSLm+dGqGp=3t;vk9Z!drBKm6SxC zGv57;|L0Ht$-n&id5UR7l5^Qwqii}M4Jh z&9y~56GzH<`4)(rHQD6K04OY} zuxphM>Mu@3^SaicCt;rR2Skz@mO3pK=d8{{cGpAzM4+`KSp1L$iWLA-7*y7eS7j_F zN!0sbED%FonK?@jBd-A^BBH{3viv%@Lex{e$V11+Y}_UZ+LANnTClD`t9G6>67qf5 zM0EE1X=@w9dqqo*pdMi+{60)~Q4ktVdO=5;3KdDZH10lT>!%M%IRZv7tx9@l&P!;k z0Jb^Z=ZsiJRGt~p_hzCu)3>cf=A7;`v_WVIa-N8_I_3vcyIj8gaNo8*GcNl^ zy&~HZCKB*Nii{>O?E;wU{&YER_ss0J3<8_Q2qeNIJs{i}1siA1(0$wJV>R#hSD$?2 zC%^aGpZ^Dcz55M55w6V;fpJ5>>b7CLaFhTv5n`JFvTc|{+75xj^SFtMSQkIE?|HnS z8Ty6|Ko4|^f!Lz*=0r?$JC~`!=Ik^W77Lm60B3Sq{vKTW+T@H z5D{&+>D$&lazuT-10trS$yPa-3@J(;-W=Yx?wR)^7Q(5XEMM?);WbK8`qt-SDv>L; zNO+6rhN3@tWI(pok#rLk%7WO@li12L{=@^;w*M@YtBGV7G}I_jAq|RD>&k&I9>`gH zN|aa~oH^U!0{E4 zK1xzCt0#P0kY$mIWW~A=PF;0?z6T612OoIa=>%mXxt=mYo1H^LW>msBS=1DNTe0Q%zm-XttLC20Rs1RO@RFRa z;46|%yxI-%6{@pZ{N*&ho<=2?ZUv&6z9MN*OJS*t`e1;Z4Zz|H$~v-ZBBN%S>qOSa z6<(D}-Qe_Y4U3+kreiucrDqY(gf*MVaKQ>wrvoxSjNThvWqWV&(D7HY7rSc$T)bCS z^Z_~PL7KC516}&uMX+twnz@ImZkH{QF#~I{*;M;xF&zR^`icbTYaryHF~W&C8~}n#xWrXNoEpO?OQE2agf@}RkxIBReYT|5i=(^Fo#6pJUA@(?-Zw*Au9uSk#g7tQ~H=R zr#(;Aq!G;O56h8Rtdxqh2{k)UK3Trf)f8uls9LM_A#=jl$s6uTKMOJtOdt9rk&F

w|>1%qR$p$A-Eg6AAD2F*oi~8WCD3d#?&ifiabWJRqwezc%8BGle6XkDFGj^VE ziKOc(TM4e?>a$}gN|wzKy`OT;3f2@686anw%#d+R-sEO_9M12hRv)*_yvXJ18j*;I zIU9xDWMvHJpcyk%H9UInbj9nv*A1tVi4_*kT$}{bdYf~G<8s{?IX1>Lsmvi{wG`Gg zJ(E7v)7zyBB*T}NY&YQfc>es~`{nuN7b4Kj^0?<5Vy4%R5Dt-?DGEf$wn+j$kRq+8 z7_5sZ)Cv`A8+@QOn2{MR8FLPM{n?NHlmFwFzy4dJKaDXV@PM{9r;Eyc22@94-}~+E zsmRnI5{ydD27~KOMJz)`6oQdJrdYQzQ&h%Gn0jPHk&b=YSsb)L8J?n0ID`@JOQcnJ zt9x2&vFN--wG{OThz2Ag2zjsiPQ;sn5AnocZfD(D^p;y&yX6ZHDF-|5A~ONdo!M4` zi7t_(EPgJHo9%3VsTVjWPMj`cK+&a9jW!mvQXoN*>gq;FI<=)N`(k3MiIblIWRhlA zgykYJ6Us51H!mpYdc2@2j(Jwph>(@WbZYsQR8G#LNNW}PsMz&^AQN$_Vpga8;69%P zzL*w8PdnRu-cyyM9;g2{9$cB~(X+URk6~viN;0F`{Gvz~W>Q+XlT^nykZEenf{`N4 zEXH!3ER}npjQgtBjiuSHsv0N7dyOh}MKe^?ePYF-K1{4c==6AApUk8RGn|fews4_3 zqr$zlh6+R0A~k1yYFdk@I(JQ%=1Eq7P((BJ>D^3Ox;Papst4!c?QYa%m@ztlBat>s z`at3w3;+fOg4w~sUreu{LN=Q*oh0toblJ|KHC2VFNo4OT2{WBz5I6GSy{QT*DZIRB z-Hs!yxA5@k>?cfA8}Ijeui8{oeMShvQ&rohFYjNPTJJqPc-kw_y7@sHCZ$tY(iU08 zOy`)*Osyr-$K3XQjx@EnKyH`I*FXH>&;7Mu?oC7`=Y)v%4M|8urJ1NeOtuXGf^(JR zJOCFzNWT=_ke;GB@7SL-U1E~&X&?Rko4@fle*7!H_Vui!+v#ad$E=V!72R43;Q8i# z?@~S(DyFx?k)lS638*zs8rUkA*HS@9GCVw`ZS6KPA~-ok=GM(U2tg#%Va>+$*80hX zn4ThvD!9;Q%Tr8~PCgI{teHO5T1%BNX{=VcIRS~R*tr5;B0un=doYo6ZTzaVtdtX$ zl~Wv-sNg}uED5BXCM3LP&xgulsjajP`i-1RzbK*u`&udzs#ZLflPU`zUtBew0c(q9 zpMUq4}gY5n6)l#XUS4l(~kuVgp`tRy|%26A+z#z zKA>7BmWt=Bt7J)^_y*sRn$;Fj%Jz?|FLy6YXK`Z+E?9J)h|`sYA0KyP=T9q*o*NzA z=A?)yGieb;;tKtYR4a#)ZqZ~1t~ujO7fv&!%#Xb}!@^bd1P6#CMLv4f2Sccw3NVMO zN^kZ6TM~WiwF89AF?w(1tFzY2KAmRMV|XPoEH45v@nc$Vr-h&v>XvhQGoyiT-?}2h zvzaAix>NLMO^MjVn96XxZa#cQh^ZfgxCUz|#uEXx7$8L%SFwd?5_~ zM!6_TB3f_N$0d+yt)0;ijO>bI*w&wa_EY`tcR&67V}E(peNWwReT{J!+huEcyOpKM z?Y)=*2@f$El&4ha?Hz0^4;aA6WYX(s&m?n$hy1@sXOqe!FO@Cwpp3-}p9$cJed8lRR z!ineT*)%G%wnFs2j3jl~t?mzR=jBOY?TFczLHQzZ7|C#H;LJI60?U=c^9 zI_tBVQT`2*xU#f|tbJs`%A;v4lr!lEa#9RGuzC6bG{%}$rG|9W!mYa3TIQ*J`#ZmQ z?(t}RL%fkUc{3BWK(Px{t9HrjuVIa2L*kRxdg74ylKCnpPT60FLMqSU0TTJg(k6XhtZ8JQ2`SGJ{DQgv3+bBz#WtyIul>>lOmXC6cC1l>&6WxX2(_F@2H( zK|YC^PM@^65=caCtIX7SPb^lPloMb@?;w^Cbm1*1q0&;imv&FWoorUq!Zad^tZk;f zw*p?sOfqwy?w*Th0)_h|DX?!%>uwAc-Fl1g%rFscS_#?ifl!g&EO=^psKHF#L(S%x z6?r2NVG?5=$0U)~q@sO*Y`vLk%!FXSZW<+Y0?@jRV`^`v;v-t8Ia_HoBK+m~1(Igw z!>uW@Y)>R3JX*8XIt%!A-E#(26Z2pvQu$z=gd zXWuLoW^)4(I1ynCI)#?Kkg+p0Gl&e20L;u2Q4CrU)8cZlJyDCIddY_is^$JE zMwSuBDx)Vi;ZZ7mmGWG6L#w<QbFN+2si)~VXMq*JfPM5p0%Ada2Qo~cq3dce|xY%XPT+9u|;Zl{)j&&J|mhiMz z3N=ZQKxV&mM3h4!j}yix(o3dk%(Q0PwxzrE7Q_2yrW7I?X0~RTn8(znrrLaO*$lL1%&j;rCd$l{UsV)LUz510i4_0@VK_qy z%K3B6+t|o$3VQjF_^*Hp}32A*v}g_!e(x z*&vI^a7qE%W3R53C^!9rL~?m(JoZw;pTV*c;%% zc^$>rE0RKyta(gwUSJi+DtVs1Fr!=}#sljvhof8{(>huf#bLe2^&HDY>Ks^df#8VD z(*j%;O04H#vLK*EfLXN0mAcCzjiizDI6ck%$QU{ImbxMxPlALz6hOq(mQ*M5EV6`R zGTG;94Soymfr2|jg;4yVwdRGEe9?Hptzd=H$CJ>)D@D&H4o%@{W;~_0-h58prH!HG z%i)CA*vN_ntSQ@Pvoh}@eRUk8NU0IaU}Rw_$T7yYZJb)tlksOULqt+=90&J=<;OIg zCMP~Cmlhtqi$amv%5lkiGfLaT)0&K9Ze5T2At@|+aPC6R<25U|I?|d-O3dk*#z1S1 zx~B6!nChhTm}u>|-+hd}@5lW}NZY!K(v!M1Gi%4otzEa-s{u>>(CU916_vI$buBxmjye0}`zKlwj>_0zXgG{TRg_Q_1G zRsnIC|VCBQ@7!z>LmN*kPj zfTq<8zCi7(=vYG*N5R31Jt9)wJy8z!?1ZEf5-Ct!nJ={-Un|Pw_KeNsf@cbb;8QPp zuCDJJ9xt)l7Ne@2h6I~eFAmzS5`MM4ULGCY$6MZ&$4n- ztE9+tC^0QS3MZlXyiKDbdu8zmg+4EI>4HcG#o(2fg9XW*V@@3vMcaHV01HYoO;4`$ z=z3AG^hx+z`b3Xti<< zQS#!oUjWn$obe-Kzx0E_E-J0HaZDBXn2h4>CcQTTS@W3NrIXQ&Ok&s&WP~=2=~97~ z>t-^c6e}HS(weo_j$`zFGu5_rT3o9cBDI+y`_`@R^UWLmxo>^*PyXBS=8ZhPLOAAN z#eol4M+DR$jejF;%k2p<#Sdgk>lj0}jtD`7XnXygfAhcn-+%FYe;9oy)wFM&r!5O& z1coOY(MaiAW22YCkd$J)m0ThLlW?R6RHwir%^DbGtu$iR^kj!#3^O2AZpY;Je+ErC zG7{32rWwOIOslFzq^gP0*%=TC$UFlyK%hkhQfB#Vhs#qxn_86Iq6pNBl3X@&yu}v2 zR>PpvQhd3XF$H|V8mm)Osc|saCZ-6;4&t7wv?+$TBHC zJ1SxVA_S4;@upUP&hwwC-a@>zwRR#cQckZ@d1xQ77#HhMlh)cF(qPE8_8^&5L{&{p z7lqD5o2t-#?a`srW^WM$*99OnhA2L4dN9#Kl~T)a4nLwADdACw2`xfa7|k*OfN;U4 zMGK(mz7|xKNwX@1nqdWNw%%#^)U5e2lgqkrpROi8M`MDTXeP#-MTI0yFe?F4)h^eq zx2~pRxG+_1g%PNV_12ml_euYa-b@pwFz!nc^wH+mh*!wRWFb(K+sj=)P^=mmmzu4$ zIlXlY59_UOy@W>9Cu(>`xHfCO&tt}zM4z{tMO~mBs z8}JFA(zi@40ax4E{3a9;@H@1_4B-&XIYfYQ*tdW2&42a(`oq8X598^h#??h49G=qm z?hc304WsOYnvNN&Dy=cIfzUyMelqF__t4fz7`4_<7?%+b5HQ2cJtc(@b0AaHtH+Xv ziiZcyKUEoE=u5)3f?R9$6oFLnRfTh{p#vwS!~kJvBJYk^tGpB9C}MT>98yq2^>PGQ zM9r2$g8QVkFVC{jE0$JR344 zYj&5l;wpH+K4S+>Q{%G3&&(5<<*SJk(mo-~lTA{SNxj3<)+4ccX1=D{{`2?QES^_C zy!xO;DrQ*;e%uf8xGXW;dcRuzJCEa1nydk5E&Z0a{i34R0xeT0;j4i{YH-LRK11o@ zQmz!|dy&G;b&W#j46#-Uz=OE57PQMc_IwVkBZKvb2hei4U8kRj4@KAn>AH4xJOglwoV+G6H;L*UKtN_Og38) zv0t`%ALZXKh#4`bKITFZUZQS&+f?Ovx$l?#<;~kq|Jq;b7Vtq;VF7RM5J3jU6je+Y zQ=mYPyuTAA$Sz_V=8Jy%^UuHb>;L9I{m*^>$nSG0>maHyz1>|@j}fV2twp4EyB%(( z&1`yB2S-tL#Ecqo)*vQv;*)EBpOH)^bWi$lRLG^!;l3o-W=fxXRm-(B(@H!ECGtyJ zh$KQ5a9nqIuKTJ2`iz#sndAagZwoveU9<4j3BRaux>Ol-L15qbfdhMS zs~0V&npmxASCnS%3y^^nBe@XKQ^1YtDd}Nb3=JY2d9sa}e~8mtA0n{NzVp2YI1O1W z1z8oenkJk)HYsi_YHJIuRVEfF&SFEB+v@SxW@>2*Md-Le$bz$rE-H;aEbxdymqT>w z>NWW?R`w1+JtdE6QrPpspobT3>sR1tQ|6{j$@gzR##tiQ8Iv*i^h762Ue7UC^}6pSU*8F1vRh1 z+XCtCrX>^cS%4SG7%3U_kt7-CVTiuQnh(&j!1<{HiEie@BU6x_3_AeyhVB|38O;6C zn9)=n*-U4|oYR!)z4bBmrYA{U$?ZXAg!`D&r9>{Y77C>kx zL=@UM#1`EsGbPjAw@Yu$oFjQ;5o!`N=s@HgW54u4N=YOoDQcQC`rgKfW{S)?X5TlT zPIO?~dwQm31onNSg_dYCfHyrvrSV@E zKoJA!7+cfFL9Oh*Xp$VBYT#%5BE=5QRE7%_p zE9kv*a5>yn+i5xp6(8=IPQ6s+WJ_ypdZx(Mw~XXk&zcSQ*1CHv^j17lTbu4?3e|_5 zo@z4YNJ|iSayIJCs3yvtS?F0`hDtSWM&0@*NM9%c%SzNPu~GOhYCM6O8M5Go6FVmN z`Sj{3bbA4hJnLC>toDkf7T}y(-o30wTWN(E&d;n8EhHKMWXb=oKH;47eg>@6lF7eEY{9vxOEUtDHFs?q8MD*0f z+pB`U8dh>$PZ3IHCSu|7Qh?4<#M53c@TGni2zwz$u1CL-EfYfbCAX)Qbm*!R6> zWV)A3rbq&`>K1u_%b)x7o8S8^-+i5_K&;LS86n#%_z-OpBOy#$$#ww%cWIl#GhyHS z{D=SX|KaWL{!p$@?j8y1N)mb>DfIOYj8W7#F`EQ8QamBu+|vv0wdvuGlS*OD8s!+W z;z(4d7v>BAVhrK7-e@r-YTS9Wt&`=c)-q)#phR`^Gz%4L({pC2S|1sMOWs}La*-62 zzfvwe0{eihHA|FH0aR`EutYeuj0XxLtrn6L(lypE)~r5U{2wO-v2}*9ob9$O8x7QU z9Vab~6{$*gt`-%te(?k(ilbG&7bl`qdYJ_5v+w@WQY@T?KIa`v0anJTv@OVS=Gr*A3PrX^o6b^EY6jkSv!&6wUrEByag9nab)V^Bo@W^ zWO-vP{m8H`#&m^s(T1W_%j{BRdEr+zU#K{{^>qo2{1!X&coHWkgqvoPNvt()lL;sC z#mqU$1K}ZUZRJ4_NU<7wNj4XibV8A$W@cbHU>Z)zodp?b&BA8|Dn>NgJSWPBPtcks zaTpPwvp3@>LM3E5=x;qU!_%7bE%&W8;+_l7DNx=+5_DqX8H+$f?3ev=xx_I_f9R9q zV-LR{hd}nrcDo(n-kN2miAIJ{93k1PjWLX<4&Pg(nZ@+Xu(m~bYfVh|rz;x{AEUE(({%tLz1;r5+B_Vqvb)gS$h zzh&S2G$Q8Apt&=|RJFI^L5_i_iM5jyObUtsV=@2;YC4Wc5vG<l+2Tu14GL>42N5XW~u`$eh!7;sv4__sjxMa*EYgbx;L z1mn5?S@ro*=&M&-0oW*gFsN2xEigMFjzz^lJ$Lz%WFq5i8kvgQbVcnIdQgvWN`r_G z(?6wP*s=*-5W|TQfc3OU% zkXBYjK3_>DMJry990gbNT8W<7Jgb_UJ~oQ$1z_=&*C8ps__bji`%ExL_VLFs>sc^UkxggEPG0a-{RLDlk^WqE9zp1IJ z>6}bI66;124>OrFrbl64swUdZ=(Vx;W+oY)UM_=Uj7#s@ILna2p)q2+?&ChOe7ojx zXj5>VsM@)H3Q6&?-CQ?Vw!X4ev!*3xW7Olm)DqgT&^+t zzxv<&?~Z#oV{sDG(-YQO&^1}~?qOn{NYxoZA-pvkBvmw>Nur(*FZRsvq##AiA_8h- zhP8G~r_V++O(DrlSt2(^VpeCSL2~wIf34F)#1{Tp6JRE+x5Dj;RIX6v$OzTq zVpibq8Cmzx1fFZE)q^Qk<2hj8hEQ8>YBqJkFz31b<^H-J>Taa-AzKhVYm}kok zf1MJvOD17{oxP|5r|Q}YxctDV@ASwnFOl^$ayFQC0TO0Klc?S1(&|);y*AGt)nM}T z<1y>>HD$xH^wxjm`$S}B$wyYUAv3fp??z>TMP#XyBvqq@R9Lv_LZ!|gvt}AL^lFvs zSu>hPO=x6Mu%Vz+U1ThMO});5HgHL3txeJ}ctSMgtT25xGX)g6F*t*g(F7UB5X|Y^ zGY7J_Mn3>{slBz?_D6O(s~y{uJ9aU*4#-jKuYmxV=S8xxAKn<9Dom{GC7iFaGA2 zzxr$TjgRM?9s-gaW?E}0dLLoU64JJ9k}bKG`t@xW@e~mq9-7iy~f|L=FfHi~a zh=A!FE=^-*0NPYw1sQXgO+cje4WgbIs!r5bRZK?&^Ak`gUwJP$8w4d2RID|s2-fU~ z_&|p-Nu@ROYJtnfGesg+iO}VFgvIq-+&kJj=NdGbkIZI*NC@F<*5x?)d7{i3%|&rw z53|Z;JzmrwbnPmuH?0<{vM*$j2G&+@&GE79JJ+p>6xe6XBwS*O{2*iO(jDL0<3umm=d-oT*G&KI+3#FC*;H& zr!1&TGMBULS?_5;k+pN+iz?TlqEeP^69>I@p$nrS*P{GCWuYcT1(CB&5J(E61&yn@ zLf|8z$PAT|*cQ2)PIX0xRd%@H*?J2PO6-V=7#*Nu4T37}#YJ(Vzrv3ox87Ll=kVU! zbmu7G5kluYI&+#PFh*=!*9S=-Cc1V}Ql_TirXU-;@E=bIbjfqfthECES~H&>1QS$5 zDCuXxo;a9&TG3xBIql zP!(i&nl(|`_Kkg@zC7=L{Et8V!l!wAk=7v%bL!VYx?VbbHAf{^CYz)`dj!AlgG7`mFdpTy-VoE&Fwss#ugh7n) z)fzzCdZ0)Ya|YPBm}+Aqtk!f!P^Uv8T?X6(CaY(b;L-*F)U$$7a#=f^UvA66kbZW~6E?J)IU{VA>Y!s#X7s` z)uS#3-hy1HxNBv6eClKhaIW$xALuEts4tJ+lKQ=B$}^>^WG+g>2{B`V(DjPQViSH^ zi?n=N-Q|*T*NIYHV36EKO%O>AHPloc|G+2}<@vGB*p#XgMUi&r!lRmWSvbNvcRoO+ z)&|4FRZMb)fD|jf-V8*@x?68ulg@dy^59`p=b)_0Slxou$j>C{b(TOX$BTa;Arj;y z@W^qmDTFqhk54(lINC+bsVEk#3|SGSjD?>u5BP3veMu^7Mzt~*={W$YXehcQ3VUQo zcp#&`8tjh(&9q{_D-=lum}9cqo~vzBQHYss%`)M0s+DDxyZ7GcJ*B3m2AVu@qZ$wu z_iV~Tt~TKrX^M>8_Ld%7C+B+Zy^rJAww{Ryv~J@*u9w~C9LLyNBXRxqa%)=`qy#)X z6U`bDW@=4+jEoS~e%bu_dH=V*`}*Jcg?Rst^h{|z=R_vLW!vKUjr3g*@I!UW;{~dz zSLFV$|K{I)^PB&ZK3z|74h3(3dS+xy&l%Zz8#BonSJTMy4}vK?L&TBp#f7aem8!KC zi92IuPD40Rle2^2vs8r{(?yMW)nYOuqAbxf7e+@#=pv04ZabzI52_TcJT@>rs$0s$ z0)-1o?Wvg`~ z(W`)w^W|c#h|chnv*;5L!XpQOy(7Pb?Dh(A&wP}WjC8KaGb1v?m$D^`Xp8~?sXs+2 zWViu%0L~?&Nt}8R7V-L-qRJI-4=b5*`A99Ov@TSEzqwQ@MRk_KF$#s?^ieeLq;w}p zD+1G13eSR7Oc@fS(HHq6!hJ@1)wQ}fxfJAB5kKYn-+Mnd23c9=SPN0=nkw5SvNm-z zmS8t9eR??9`KgK+0JFCEl~EjF`gN>5Dmnb;0W5_HrHD!(&d(FY1a}WT@uGsQ6!Gv< zSZhlP=TvFO;bEpC@Un)>6Di_?C?aNhtnDj5M0XD)G2)DD)>P#<#&I8}))YPmRp+WY zJ?#I-)t`0CwjJkTU^H`O?!C`Bx4!^E3LQm|ASqE4MQg~WENj^HR5gYl{3Gk39{d-o ztQzH#i=8BrqA0z`dicJWxxqUyfQ!@Y%v`xv%!n_(W;kMA+d)Br zQTDl|`#$rSDlDTxKMk8lRHtN}&Rb>8$SS#94poJu7;5q=x$MV`ux%XsJdT-CW-cj+ z!on4pl{2b+$RsPrX*0JxavOdehgA*J(mI_sHH!$}#@vtXrs{SyNM12g0#kq;a+QPMIMOasSJ9(nKblj$Q9J?Z6mV_Ki`*` zn+-6`%pB=?4Q`+-1xrChKr>)TMi)%*fXVYkz^He~qSxcE?VwQ4^V-3QX{m#z&+$Ys zmWyf%x&c67DWX{#$dBOxs2F*w%0wzdDpj)P#34cas3<9>Hnd1f=$2iic@|f?8X;T( zE`YX_M>O;=2qh!5O$dHwV3Ua*2VN1!DdeXR)N~i#;WB`5fSzY&)|@jE=$@liUY7^` z?cf(-F$&?yF)_kQzm_(~vAsa`>2Y zqDhJfj7-auk30v{h{OW}+F7YyjvbP-h!j(EuPh;|&hQnq!&tl_Iz`9lUWf_s<$ui<{opO=nTd%k$QT(pp)PCIp>TjaVwGi z2z8IhS#`M_=iBo_^+Y5ywlN~&IHs!RjEXr-bZ%wK1%z6;vuf~yuc$?oh4&BX+rmbhTn4?q!$P@fa6{HfAYj`A8m8pz z%64cz3_V4bj9X;IN;Ry#(28CUzlC8uKDvw7#(%9ZUPT6JI%wU&X`@Hi)$y)b)tZsWW3}s{+2<2UJhF%@(n*z6 z!>@Ey?Io(T6>blcigg(q_A*K+bu|ati3cmEaoYx5N%7qtmtCs1%}J;*m&(k7oCL)i zmWgAyrtJIfV^rjDizBM?v~9;RGZW0O?L3gM1r7#w!q_)%o0-_n`CJ(ef_ap-x4Nrm zgd-QkRHaPd<|o)0@&I{2hQIcftjyCk;y6Tg9&X_i@H63BrSDb*PX?@$;mnfZ~FiuyLHO^cQx z28?b~Aow;>n-LME=CyJ?*vCj`VLqFXn{HpKU~o2;pv3$-f{!3Frbe{L%s92RUB{Y@ z)KcLpUKVoDszuzYxCwJDtgZ$c2nt9>1SAFGDNGh!jx)gt$$olzL@qX@DVUR+eaxloBKBoUH87u1G$ayK1`=jx1|8pZ||(FYD~)*q*=NQaFP(Fy2yAwd4@+u!JKk{yyd1~8?7Iy&(+a9FvpDoJb=UNO zoBiWen{!+) zHK$03pUY3VKk9bU^IbmO*Kydk)zf|1P`Qz>9&`^ozk2gu{%rrNAKC4#s2-891yZxp zahVw@J{+X^rz0a(O^?ayTr`@f5}8r3ip`XDcNReJ>7@uOsBVOE-1b53{nF~HO=wd5woe`YYXkz!GjDv^S%W~r%LRL!bl_^cMZ zJA|)Q{d}hgO#4UQ7XD}h>RPE?mjtO5(Z()9Z+~$H>j%Hz(WHA zGSKYlf=$){7NFur(gkP&xWqs;liusPBA*&r?-sFXB+>*r#l*e#ft(T6Kbbh7T9F|l zk`aY_v-*ECXQdCQTe}njQCUL>JS_tgkVvz>lmvj1%1*u% zAT|Ybb)Bfv8v|bah`A){RXXuNn3k}`&dOsR2jWMSN_4C~NVylNT2$ezf7;L_Pp}ps zpooP1?>eRN-}4BlU`R}sJ`cG>S&cb(ooQ}}gf5fgn5J?>2>47ev*t%pM@Wa1WNoJr z#mTBf#~5OA><5nFV?VGY+%Jc&jWtAiS=oww9|mdPB%-X5kr5H0RruF^Kg=B-nC?~~ zW813Df<^Rvd%|XTxSvkrbUww5IYWm(K0GoH!ToYM?6j3xrNCo4=7fKcNS$v^z|AYu z%_71!my~VW=4BcojoLQPraHuf1PY9%_OP|?O|8{0NRQY}&tG&Q3lwL}X{IFVY;nNe94czDTQwk)N6 zQe~-Dig!X4+H-ZERTKEw7?;ezGb>`ln;>k`@!{2>;H4&|V>>ZApkzk02c8mAGZoci z!q&>gT$U`*tQsb!RNdj^1(yJcNT?P=G)nt?vc;tnwJJR#=pw=-Ba)FSrNl&CeWYky znIxTG5%A+1lQE&;fbvwv%v?-FV6B{P zPI+`Bt%MoK8bT#%W|gP@a&I z&;7W4adSLPckyjQ!{TETs5Cn>t&aVpfBD~!vCEtyC8rm9JXNJ`r+j!Tr#s18dHrNk zxlcWhUHmWq*Z*}JBhBXI#4c(m!P~ZFikj{dEg5-cNKUbB95bt}=0aqqjBOLuIZKTY zaCV;keuVi@2XFy*3xOHGy6uswx|GmjChiduy+Snpx2eh42I?Sa(J&7Hb|%&gBkAPI z1|gSGhMA%hGP7bd3^}-%_3;$6Hl$g`@H8N0LZm|lo2H=sh_u5RcuHn)M5rE1E@YRM=`!%E<)n^G+Q)M z*Q>#nIn%w@2&A+K^`;Ht<1LYisEU1$DmAbu^<#ifChhf63&?8^7C>2E!9eQHzh7tnPWp#fT-zlImYmqiQwHh zCW+^jkmvaDIF7M7fR_^WVQ^WKtO+ypEHmHSPp3^vz{p0&JtI^hptOn_=k2&m8(K+Y z?uhVV^Ekv*s^Xa2?Kv+GuYUWtwo^*X#ZXkUdU!2kXx!9S4^moBht7+=diD7ae(>4< z>wnYpT|_n;(~lXj-8d$A6q2fYmb;_rn_07@M2@5c)K3EoYSd-MItal5T2BneUZ$iX zDK##U5CshmZNxD{&EP{+0+~4zxRYS3MO1U2xkmFbaPw0=;DaHsK5&aNZHUhU@#xiG zkR}G|y*dFmU37>>-fEdH)kf6k5arXI+Qo+LfRO2Q#9@ge*I( zyQJSynnGm)#Vp*%7Dd(BVm6$SCBh%Qq{duMl0~y>AFbN-3VHyUPNT00p?S7N)heOI zfbM`-EMb?4lJGRfjL>$VgbBo-d3u(xTf-j^TU>D=x~r0s5tG{mS>e>GNR!OeT3adj zRKOe!#bZTP8A=GVe%AQ_5S_{qy9#8A(S>-f8Yw2@V<9SQKim7p_4cfR3v}H48GUZ8 zv#R=jATI^0!h$_0uqa;*>OxADoIy>I3g?EwW{Wf z$hbURPN(fSCgB*786l=mj|X`LB6Cij9jYQWkEv!c69zyjIrlwgmNPG0L@LtF#;}OI zxjmy;`R4Nyz6~|CVOR}j??R4ais-OGN2&L_!~UwKzSCBLsyFaNn`fp~H7nabi=ZyH zR?)~N`NdTeC5v;pW@RzP3(cJurRv$ujJW}p;cUtPR3N-fvcjrIhz*mSRZ@XAM+BSF ztXx$ctFm6su^d<9z<9Q>F*OEeqrNLC(A|EDls+?`ZW&&Ga%!d-1G741D{?b>*;weo9iNyK#PgBKvkmKvRo||iJ`72HMg2sD86c>5@h7$JK-6u9oM40rW5W4Nex0u zx>jNZm1NQz76ZvYL##+5XVWdR-%Y_lr*~X9c-NVt{n>fXz^#f(6GIY8g682i8Y)@6 zGddR)#n)yFzE}7M&%iFb4}6;cS zX9%`gl@+O)wLi(tt=xQEUtaXQcb&rymp$gnwRUIuhw(Hp@$c03P0;__9 zl&F?kiX5=lVRn)-A*yq7(I6p_k~l-9Q7?ngXL9aR)LK5#3KY*)8-SaU&$K@!wKV9# zY9q1|pnybz$Gnth1si*#Us{j;%EfpunyhshC|mk0`h{o)L}+XI*wT~s5;763D0W|& zgbBJ;T(CpiS|H2`Zy>K>N}>>TfYGH=8DXMsIsiB{u7X}HQ0WQHmR=KMYM6D;m_)xI zx*=p#O0I}zOwjVGGAmLd=26UvxSp6|0=E3j%&0Lo)hnu1jWrzeh(=(!CRM2v6WI7Q zBEc2MN_9}U=HrXX0EZIx9ja=o4i^aJ+MO%jYNjeDaWL>|ah%9&QMatjfH+zi1#WuT zj}lSyXz~c|^~@5}NfCmRT|;UL)iZOp^{BMZN&A*WL}QDBSiuQ0sUT;JP6HsNRg2M0 z1n7sF9!Dg0xdn%Jq3LMJa6O;4m=P1g3ynF|EQ2FnMj{+bs$xI1(nKoi_Pp(nPj0$x zTZzoM;&n(?#1A-TfL9Qi=KA<>LDw^nIrl@g_J|p=olluLXK0aRhH2W&G=aBRJK}0p z#=h6FS4=PqM>AbI z*X!u8&gX?cIKu))A*5 z$wj8A2y8sDgax;>O`t?o?AouH+BP;7gQa~$?E75nV;kQ6R8cNGGfHgps4^chD+RI) z6|)Kkw(BsV^ju@+z8~YfEvyI+kGfWgZ^Jh~F2^yCnW^m;f$*1zbcg7?2)~FxVR`vaT;jZ;F=U!!%bB7r^C$0HmY(PTg=Rul?7iHXp7A~tM2Y@xga;S zVRAf*gy@U>@#l5_7*A7rYhA~#r}O=f|JpLcbl(pl>xo+7gYqBC_SYt<3puT*AHyRh zMM6+DOhhBahVMskEv}X+#|)%oMb(T@x0#S{Ta*estyOvom#k7Z^g)p+3XYv%OF4UE zjn*Db#gTbL8Zd-X;K)&BYCZSZJx1z-Se?4+s(cX0*# znqlO^R3YtA6a~CGttO=+vbpmDy%X5YB(N0Q z`64+j+qAs56IMHwNd`UO;i?(A!=;}-2fY4pet=liRMb^e z=ghgsJW#jwblS!k#}QJZj*Mg2cs4YIeEo$8kgi2y@~( zhM&$SbRJ{dU?y^Nb5@bh zH7h9*0;!@MMf2Dc)*^P@0TFfpyMvm-QsUPU%|KOAs}xgu;3e~_y`fiK|B^zdhFt!W zVi?wwvhQmPCcTJTybnWb-Q6hH@Tgqv8>}?_;9oXr20quRJug@5D=-GLm{x=IDsc7_ zqBETnMmr%^#wN&+PPatoRqZQUV$bZJWv$Vk!Q{(YLJ=`U7@xe3uJYy9LDjJA&|Wh@ zC0qhc1UZM4S))oUTnN{)5%?-oSV&`uK)ps)!SRDYbV>I!1~}CJr`Jw)`DndUBVJKUs~81llw%}eXPuFG`ga5 zCJ;<8Sb5kvvPT&V0kkYDyUP_y1BiDNKF{Eq@wb4*?pj^#coGU#ZAp^I}I0$SsCHOPp6Zr zCeVYK@Xn6QVP?nEu|MslG$4%#bxVT$%eho8<*k9$UpZ5!KY>4)n1cHC?d zQNGo26F>TCJzO%>PMgfM(^*evv^**L_Amc(+s-(sQuHV}X7ZG!VSdFaO|Q}WHQRqQUo~x zXeJ_ZKPG+%7P+xV2+C(U;Rf7KWTa?W8|Mpf=jOL+VBKd9U;oChP2H349LKqqAfD9< z!<-0qCJZOUBO~STa3}cW^jS4WRqcJfssf-_GVoTSC%dRz=Dk>xM%Rj~xC~EX0|VHa zkS4hJxy|A#uxA>C;DM&1*>3Sk2)!w9=+~G@T}@+nUSug`Q)&xytrHhyfZZX(0iG%M zP<@jLdJBS%es@+KB75BtuA19;>O#P;M8B4+a6JL?34?9Y#Cq?iU|B3aL&^1PF$UII zM?DEtb=2WxuqOfGd>|=}7()&jZ z#F`d6ZsH^0A!Pz99V5^Vx6a=V6EYxhE6A>swyHT5M<6zh1m!5D9$r+i^$6{ZTK;1DLju^ z$hC@cwTL`sWCFXeIU_P%U8;PzG!^}_&oVt`n28UP9kXg)@HhAYDJgW-b$v3;&9||g zhP#c;#dJGw8R5P~1S~Frw#l%0%=3$z&bgK7<56zTGDOcWwO;05d~P=6e5-EybU?jQ z@7}4Y!hq@Z8}&`ZB1P5Q92&Xdqto+DN3#)1%e~CXY=)%D9)Rz4&QycbnSkaT6MpXCNtF@ri++wDQoX*=g!(}vKBodgXW?r6-$e4SKVNZ{j6l7_o znq8hQBH|l)-%s~XW*${$aQ>5h-|fYXzj#^uIsfu28)w;Wa-37GhRe3q>CR7E&AGjL z|LMnndHnP<*-ij$v-Fq=r@>T#pOLs7N2X~hId(_Z1q(F=H@Q$HGAp9Wxc^MEDLPBi ze3nL(ZCi-ovYU@8!5$L8j;aO}q+%s9wzJxpRjCShB*m(rT6AY%=|wi_NeUL{klS>a zOR4&R+Ay>Zco;!I*k;hAFhIOf+Vw<*@u=A8S<-w}(E2PwD){)&l3+-UstN9vSVP`j zjvD}IjIsE1fi!@mSE9R$$F|vMlC=g?5|f2`-Rig0Fm;ozLs&Be?@>wQCf1|PcuO+$ zjA{-5JlV#c&F#grS55nfjOua;Z(9&K*808(r!=>>o69cZnw|fIWrewS_6OgEDu6{? zZfh8O^^!??8F@*P5kaek|#aF>fkm}4@iW@1yb~tWt!_mLuiW- ze%JxuJ$Ok z$A_mGxzC!abEc0GDJ2%AnP8-Gjb`r1S`@PxmC3}AIrEr#5C;$#vp`l9DJm{zKrn0g z4B4a7EUPXvaPK8dcvNTAo;6bdm|)b}XV@6ZoIq2vBa#s+Ma2q58CA%3$ga7Q{3Otm zJXJ(}fxno#ntF=|hGwM2u19h2H4T|6mG#^dg&i6Z)W1kFBh?xuZCJWHHi@+X>iVFJ z;Id`VR&6x_|36ht<*5@Qsd{8aMN<4hLfg-9DEJcy7W>zK?iaP6F{KvI_~dANQB!$L zVT?2iPSlbO%Mh$9o(9)(viBWzm)|@v_g~KK+V`HSZ)40`)Q=9-p%qChQ6QAEXEbJd9duHy z+SOTBsbE~TpAkvJ2U`*thTV|!$5g{6Ys$J``E{Uub2G^#QkJzov5G{cn+dIbdB~V>^wxPd7Ua$46Gt z5}E>wl$*PI%wW0)uU`%K2)aJ)Py5(BvwZlP5sP}WJTAv}KF#C6^X9`c(mOpl5=m52 zRU_xi@Bhl}Y3Rd6)db6l2pz6vdOp|vy_)FVOOe>8{LR1le-~g4AEh#@>~xA?u(81N zmU?alqlr{VQON(sHbnK9IkrJqYz!(thZV@IAm@o3E#DP`2@xNYr7wpdrL0xaxIxUT z%0$(XFRlWyhLq75D9cQwjQ;Pe5Gb!CgrG@w(jL|(Fo+j24VGO(3DJY9t3VrotW>r8 zE_4-qjzyq_2YU023PM(xl6tfnME;w$ZK;{MIV_h26@syO_=S#DV(V6FE=8-HEZS5x zm|JTryZ8knVnkB=v6m&CK+$RG7*Xl22y`i;K9EX{%rtd+2?3IXFSC-?*CnNTbJ%u) z%W}v3@F*Lxf%!(a`eeEEtAJYXeeuClR=_@LHZw4pk^UjM?|Uxdz8j{8N#-Ku+yues z`0S>RTQDM1YCk4Y2xt-lcq|Hq?>GVsz0=JJHN8YGmpwDZ<#O49V036CoH!hWHgnC) z{j$f*EOED|r=9l(dIElF>cf3lsqB0B7$vpuldMOB=ZT~cOsuMe0y9&x#&9=1Vvfzh ziuBF*W5Sn509j;h=S@Wm8da6yW1L2*KsT5Ihfl2*g^z7}^Y&6Vclz#~`Y6>JUN^Uu zCHqwOIu6@h&bRsSAmjY_`m6onosN;F04>IL%F?I(@NM{T^Wmp)90AhHK6934$&ob+ z0Eo|$eGl_dYG6}}NKBZEa@kf(*aVcoxIV!S(A^^?gjPzbYGsDyzgmdI3^N*ycI{xE z9Bkp#+{9EZRH6ivbxF+#HrMD{OfW{~K~$+Fjv6iMi^dqj&JQ4tWALn5C|zH$)>`T(FehvPw$0$a?|MS@j( z9XX45+fwuHK3RWd(Tu6a5I+B8<~DautrghnE1v}h4Bo&1gHE`2>prnLcEWdtUW|rB zcPRKO=Ei3Sb&-0r3yNjbD_0$gJZPZJSw z43&#ozy5k5POw*-{8nXc2r`G}zD!74<%btieh%hrqeaG-pSS=f}1gm73kB^BSOU@c(8#;Fpoow#6wK0+qQws4$Zo%#6h!m zkTLN7_x&)pDxF7w<+izx(||pNR7K|LW|OM(?fKnP{KZf1-~akI>T-$Wkn>4W2nrNvVsd4f=?dX=X)+x`s*se;@(cqhwYcL6e}o4rPFzth8p9n)But8>eA}k5!T> zQ{7ky`Ucn^oB8m;^h#6M4fa1|ab)i-&?R>>$x(bZa~AGp)$Q==XYkl&Gbun3))ZM(h@M_h)Jz#>AWdjTcMNvD5(erh8PK!~hmdrJB$jDRIf=N^2dgQ` z8EM=dgH^_*LVr^JJBO?eY7&uP;G5O@JC)i*4mTIk8OL$#U~|L6Gh!xnvebn(|L%9 zo=nH6?IusRa&v2^vs@l!f3j_pm;_;k7RH%kW0*O_$K>8YkC!5_MaKS3WET2vRku_H zrLJI+ArdJ^%08n+b@(w8c1W;#F0-UFy`z)jkOwx{vJfiSw(ZC~lGK_=R6wb21kDxn z{8-htNYFy%Q8N*O$b2#}WUHHcv;_~R#=3S}@K07q0sFykDwe7(*NVj;Y+EwYnPE;1 zyvRt+uGKd2Q~;R*bcV?6r6tA-_Se>aaDZ9J*~MR0TW%qE`7CU|s&xtLQ_0nQx#2}V z*S&g@q`yvoF-v!F~Kl+rom-!{3LIItIYTLwJ+1A#HT6ot03bq?1`PdS42; zW&_+SOc7^#B=fCYaEuA>#ZEckFS6{#6H!CVBFrZo$0CMDZx?8ltITzVoojtM>@e_0jMiOXe=5_8+rtsq>X-%2bGQ}7d}jGJL}8;*fbQV{0?L145pDNz>F4&lGdlF=kZ=cj=TEEbXpPbSFl^I$yehw)MCnx*JVos={$f)JoA` zGvF2Ora*epIEU0sI2)|(`jvz#BDB)IZo_)PqFNl3fhogxl!)b$3P&@vOWLU|S!uYP zH?l2KO4PH7de3)YxPUfnMSLZ(xo4v&!2kxATz^}y&HOcSR@*&UlCo+{r=prPvxXmy zx|@*uRT53{>r$5;p+X?EUn0R_ugZi{6u!k^(WPp&53tC(sI{aSTw8(C)9twSFS4K0 z=5XX%SJidc7u9SUa$#3XKwC*c;mWmoq)1!ZkZw=SIs$jAFNoDDxr9ZoSiH157td@9r~_v+-q5tieYmrfP-9L z0JR7aW#>~;k^Sr?tU2(kLeL|kwvEbicqe5+C8#2$vLDA7V{-?Ic~MM{IVn0%q9C*Z zaNsJT4N?F-L5Ug)Bq=^ z(6GO{EO6loKMgv5@Evo1)8e_tdf8&**ek$^T#XRr$gT|19@BVl)e+KYu9D0!6;(-9 zwSs@Onm=p&tv6&8Zb(DK+O}lN$zeV7s~=rXo7v|C;7n1T1L$zAJ3D}wk}kd3mASW+H6W`cC8{mJ)T&nE^R^g6nmExDS(*(dO0_{0E;nu?Q-SK5UKjkl3X~Or zAXn~97h>X9#>$0`fI^toi{f;lc@2o>c#Jc6t`J5SclY5Ul~ur_g1k3hs=BWvEj2~go*6bgXYTtPCy&fLrbr>KJ7$_W zd=-7Olsb-ix$FVqeB^%FXT-ZV_cu4EH;4Y|4?i;>nj)s3e)Aji^DhgVj5+n@hEURe zsH2*ux>ba}cy;>lW7#%QE8n*BS;R^W{&S`Jc-gNra!Ho>X=^#3lptVCAT^GXnJKQw z^Pb>%RF!FD&ESB!&y<$40oz1=Yti&Z0f0b|lF}`tXi1q5_?Jc%&RyoUQ?*YKGH+Va zoCN0M%Z8!*O8QmFiX19Qq!3)=YrErVe_LCD3`$F*PEk=5{pe>c=!I7Bz!cHZC0lCM z2s5RKNm~Tp1_`y?H^|B$Hlhjpn1vn-7lv&&Y-H1R9_eZ{@i-fauQ?k`zfRe`nu0M< zJo85ew*kcHU<=6(ROCuXlV#?Fk}XVHSN5^yQZi%qDxkIF+{adBtbMHD@m{bl!Qtw^ zs#20cRWv7^mhC0U(I}Qgg?rF2?gomRW-if$FuQb_?v^=8FF|l*7wdK8qRAu41e-UA zDy6W2S!0f5#+=+d*EMpm<0;`GqCsN6;^sijWk_T(@h>8pEKa<*_llf60+OICNHCbE zSJKqyzPFk%lR@W@Y>N|k5kS(dX8SRPCjE?{OmHE%THsxcUdcCap;T3Y$M|^ zeX44Wb8I6cXsZTF-!S+@OV-B8z07qUc|LDyhGuzranFeD zv}M*M0xsCcjEIQIC;m95m>hG83M@>0v*WUh)IKNjCZ*)%`!9|;7lL^@kC;;^%N0Kj zfPJTJe0hKT$)EppJKd=Ge%W7s@ZO6L->YrYv5AQKCU&YZ1V7qGje+M)ZRA&9=rQYb zhW792;iAJ!bfWBabvQDB`DV#HA~VGt@=-OLQHbmnb*4eY+46X=U2S)$@?$reSFHy{H&bs;F z|8baD;lbBtyTK`p&OH4HFO1d}v`c3=@sB_K7Fs`Hv05r3WoTW{zArrodgSyrfy1*9FwXJg;_+vA{-|TXZ&Z2Rl-Vb=|-QoB_b_WCD zi7Z&3B9N^cZE0yM;seVNuHJ`3XO&rqZZS1@)(8@$s%jfUgk3!26OGV0C@4~ea1??w zbGlnrW#n+Hs+g&wr_-n^iP}zMzw9{h<}oX3qHs^_Q8Ox`g@6}=4?mp-rf9fwnDC+9 zFUQzCBj+)*3Q@BXoFt?&A|rC!HW8_r=C+;A)if_3c0O$np?~tjpSo|u+}vxw{K7B& zgKvKS7b8ozlZusU9lPuofd|0lqA^WO+{<0&uIKY#{=vVOW2O&vqwT6H98h%(dMmO^ z6||+jA>>z^QXsunXOxH#7qlXaMh^t;cQ6U;r3N!*`k2eV~JZYX#}Sr zn)Zln%JS8Fi{1d;cLzsadIm&sgyfLk%7a`?GJ!J>sLln^MRm1Zt=zNynXclMjG}c; zpHjK#18s}eVL-w_12!{;_Po^{_$#(l<0YD~AO{A}u|wU_ulkFMVG!Aprq(MZ{Ma>j zYx{B*Nob-ym@^5H^m3R1n)ge(igF?pg0{p5m>jr_m>F6KnuIk(ZWPyXLO{HsE>EXk zvWd0mv3CLs2IgkJNQA!LDD07?p?V3RG?^3v$)Y9zFru-yFkex~_h@~&NSm18)NO2T z(Pe6Z(?T!5)hjmbgkpDDVS`))WHw6En>WjX+FQ-}VToUNU?hH~AS!r$k8rndK2l_B z5k;B`?QP$^$Q%N;*(9dS)3T|k{4=Hs7a_1vuv5pUodHXaTzy1k%}fY0;k1h}(alTc z$jMC+dUnXNGffbss3`&xM`L_YC?uB8*eFJ(kCB;U8a=kw-naBGZAq!F6xaNO*A z+I-&++Z-+2HbzDTkQmp?FtY>(&NZ| z);JZekPitN?|o>mUd>~Q8sdln_|B*!6U>CbW&#ha>K=u(Q!zO*kfaglEfsNvwa*D; zBTEu{(7EnQd))iq$VU&pyMNuqHYqklO5btRHm+Fk!g%Kw*J!DWv z3oWp@s0N>41aGo7cB#}76LH+LNWvuxPMc+nZnD8;L1{M^6;e)okk&1#>cDW?V+HN% z;yi;VfYmT{(zT3IAt!-c9aL9znII2l~(V4NjMB5I+0K9SAJCyN)0rN{p zeTaT2(UpV;CQUvUE-fw10o9yD?Cz$YZAh;WOlGP&IvrCJ)mcE9(7;wo&EBvP8moet z1`zlLcf>oAj2|>;*~$;a3Z6X*tQ)Rz^JTsk*D0LzV}&?fEoBu22T%f4JJYaz(QK4tfIh?Cphz}tM^Q) zV@@%+AZfKWf-Mnwr3}dinj9RnT>4=KQ)u;6Rqzvq8NZNu#JH>OrI8WNw%W)b#IA^M z49}7-(vfhc#yKRWj)u})4W7}Jo%Tg#AXsqSJ{!=~bzoo@oh1p^J@U0mRsEB%eRr)> z;TNMlp7ej%&T3B+vM^;%97YVYi(cW>Svf3CTj;%|t_2=phrV2Zz80aPPa3&CwM#<6 zDP4OqSjm3u_nxa9uUSymlLb&Rw{?beb>|hU?)35R2(4-G0Rb878|EslI{opAOGy9I zK#K5`ZdD!$B)kG`+)AeP^#2W7T^QQWE^elaeRZrJHNci zC+pR#5*PyfRi5#GSkt8hx?y?~Di?Yji-fe6DuJa6Dyecyo>JmwA`DM)vz#ef1Dk*n zYtqgz_YQf8N-ETgcB69!9+}J>!`)#BRR~o!QxT0Ld^5P)0D`|cZ&zlzX$5k7!EDbg zna4COF}+-NH{Hg-#~H(7hPzkf*!-ANL^pFNZ(*kgR)C2N(|2Ki{1>l%jDd_}$ys zM;uw=R!~f2t@@g!P-ept@Q7456h}NLN2YiW@+vr(YhjJFX-*qXp*CA`xGjpBPx8K~ zlYpxXI2$aiOdrPR29}x;s8sVYz+w~aP;Jp==dzrtxK(P{wdczK9s*IDl3ZX_6{}fm z(k$M-_roHxa6AHKy?_CH0e4f&D)32QgiTslx4ob|ReRA~H#iJp)-F4a#-2|W@lBHA zq}79UrGTNku?6ia68E656RC^@YN*5|IQ92xus3$v&KT14#%I6YCLx;3HK~Q|T20=N zS|tj+LUKcF(OIa#xt2B(#!^+28-UE1!ah2CL8(xb_0<5sCl}#NurtG-idY^`Li025 z&c#HZ<){|;%W!UDEtWO=CoXbtR+-Q)rY&0Gp+>7cShHF~%GN1oeNlP=O-(j_EOPTu zKw%ldUS0sQdr4--;+_=KYSttqPLnBOKDe+nm2T}KL@pR6g0WlVW8kzO+d$)hTA7v0 z8LG<~xC}fhF}jI@Obppa9k8TQi^71Uz71qAm5G`omm2dic9Li)qNLiH%iSSqk+!tP ziFC|aQNvv-t&4tT*k&;U+PBShxbFLe^|PuDvoUNN5T@9wlzo*wh* zsWkKcp^lmF-s$;Nm#L?doX$BDR1MAJ=I6d~%!FtgZW~ZA0l1x&kNYl4k#S~Sj;T&_ zWKoNdNO2#>j2Q(wU6RLL;N;;3PGK5Wltd9UD77GY4$4EDDMzH3wGF?lg-eM>X_SgN zf_v~DvXI5a+6_BtX#^mf-PY|=($c8MO6HtOMNdZHJ9DI!g{Sd8ZRZDtJcN-~RQYNf ziZ+X0Tg!9BEf+t4bQ2!x3YG;(P*DpzRfUsZVcEEh^0k2%>;t7yfyD?j;cS`<6fMeK zlDi{^4xRhW1|+h7vG@T%Y*9M^0Du5VL_t(Sh;Q%(8W0uSqbuoF*B_1L>0!T+{sjl%7i_L>SWep zV_BjI%>vwyeu2AhBOk|n(DZp~i4mJ&rW9F$^no5cotbXO_b`4dkTAeoS>` zRIb?3KA1vXfr?wHrrMmL!@AHDtUx&%V}quDW4qEYaw47|&eMH9QqT%2NL z%tPxe!k(Q%XTgkC&7V_&EvIzIY&DUM&bL(sAEiJbLo&h!xUlL{g^-$^jKPBoYt5_) zxP-!M>8iy?3#J?sd2+U(iS8^{z)&w&7#pIxWLc`#8h6MG7K0R%)^mrWVm$#jY%ONo zVM<9LBQlzaqK$WUd0#|^WrZiADt6_@DO3zN^pxAbu7q61I(xT9d2}JvUH;lcf`c}u z7ZfkWxko0Y5@N0^v;x;==}cc!CJ%7%5Qepztb8yIb=nGTS8LWKyv>?h!tn@1h^D0A z`>4fe)+$LQ_KoijH1LQkccSVU(Av{PMnVPC1|-r;&P0A4&c{F_e+~Fq^M-&TLE=b@ z#c{BM?zMV(B?7)+CU%1uFo0voV)3XH0!9~;grZQUL0=cVsxvZTRt2w~sR-$D_)@b9 z!OsiI#|cWq3pN62rZW>$__kf$N)s#&_eqxzG_nGdQPB1F%XC+;Q?_jjbndg}OmmB< ztn!mzjzAiosBGr}VvTR++o-HL)3$LOVJh1;;6ocTe3+=7Z_Wy@CZ`MEaU3P(!xF|1bipR(I-hWSDy~ufBB0jQa)3YYq1ojPM+vY3+Ajpf#c!(zdsTqHbSx8H8_EM${GFg?7l=a1KM9UV+yu7%NL_&3{U|DelP#>r-M?+FXcM zvfxDR8<_ivWN*Gwa|z29@3y6bhbltaX$T~cD*+IS$F^j`VnNK@KpKruBV!s=!HCHh z!l1QAj3TUtNtm0`AOpQxK&Z|1#FWR;gRWUxRgqE34jpS85#(WTNI`RbfbC1iyC%pH zHL1Wtrev)sE4h_LE@u#&bA^yVs;qdexaZ+;7e&QcguxbZic3 zLzVdOg#EcTZeCR?!``!?Vz2-dclp0;fa%PM5f9zjYR`feO9b0*X! z8AS|jhN+gy<#H^nAHMz>8T%BqG5qoI5_4|nQ;AmAe%T}P=H_&4H~+^EK2sl%#aBr- zS4!PHj_>@&ujS=HXQ&a9S;s|(%a>pH=6U}v_anziN)#cH@Z39%^WEKxk3X5m^f5%# zhpAhsRauH2kuxPqDE6cD!_6#95f1~W4pp0AwG*C}V}^)}IrTm~1tl4=DosZoY8KQe z=8SauOK9ZmUa}#Bc6n8`;vD5o`*eIb-0)aJ0xrychcHdV1`dhYgN*ow!uK4-EETvL7ye8+Wc5) z;DS^!F}ko9op}})fNiU(wp*uv{ONaCSG(d$iw5->>${u(-mt;Wf^QuVIDUHNHKnm= z$;v?nu8go($Ct&wK?b!_NxU%f20#gAR^z9&enOMmv^g|*iqv^ZJA?Y*#QMB>Z{l;G zW~hd}5aU_eOF7A={Q}k`(#@>GwgV8#gvj8za^SBhynAoz1`u7h(@?WHXGyuL50esi!*1qu zK3y(5r*GG+y4l9Nl;8j1S31s{8%PB()HV@!lX-mj;`ERIfhZ9czxdw%ia%h#{J_%A;^y?XU@OzETuu)AOt9i_j~hVkoj1Jt(PG{ zXEA?!b_G#~bcojD8hkLKz)68V1=XZ=fDnjq#*==awAUZ&4JqTN#j^eGi;QGS7E=5C zcxE|$waLT{X-c9mN@~*_0FUrzR=+*rgJ!ze7Dm$s&il5=vQ3#KHi7` zk+vwkE~Yw~k2hqkPy;*fNteGT)2;~ zrlYJkPY#H6a!B{v&kHnnfN&@$O{K0-d5;m~sB}%_chm-AB=CtKp{kEQ?pKpuR`_K6 zyU#7b`*nJHTT&GKbh85R)q8O769`AIg1>CNp9Y7AQ%>L90t%yYpDsKPB7?RYYbXSC z)8j>n%o*)*CGIZRjT=cj==-HtmfIK*96((H6m%H1*Q$~;w$|B4)ja0lLnV?m&Vz=f zX6EE7LaU8~*-TYz&cL1E!{EHZK!eDeZM;eQ-5-9X+v()+;Sy^b15-I~+y3S2U->7$ z{11NlJF`@_lOD6iklWjQd@|E`yx6PvBtp(Nb#te8cdQxn5Zmh2OMQBpWBbv+|C4dP zjjSjT6|BS#_Pi!;LP8^!VAOC3A>gV4CC;2Y4W!-c>Q4iI!%f|1POM>yP4w!<(C*oS zUze{%(j|i2M}hATa=8lRipD)i>#7j3F>*;Qnmx4`)7o8{Y75ygWj>>6!Bvknq5+CV z>sph=$Eu|z?0A5h@-EhP!&q8zX^bw{{t?E=B5tFX;o1wL=j0G(hLw=YO`sLN_Klcd zpFos&9-?j7kN1a>77QVhF@zGA!n}(egOWxC^ibdt(It@3$~sdCsgk?bCE(fuvz%3* z$BVO}+;lam`0qhjSEtiOBNmaCdve^665+K5j6+nq?`aVgw%gRyDYzGjrI}&wTN8R_g#{KDK#5KnxEY5a-U~dK~QI393id! z@)fQ~{SsAD=H9M)y_HsJwoJ9S6QoH~_vSE(;7BepA1U4GTo)#Exz)Zkc{V|7QyE!E zUCDxD2BiTrv!pKjA!^$g()$#ApNb$?qu5MU+#Rcn_KE`ChQ5(BEfk5F08EDuQ;V$Q z2y+!N-<&su>Nssu<%~{{(`n0|4MCB;9CMsc=E&VDH*?n+;ZBqQbvb6L8BK6a$7!1p zo9o;3-~G`W+s*@wV{`RQ%@O1_%wwj<{O)i5TKJ}8lT0~nk|po%T}|KrXngR#JU!|8 zED!fGkNDA#$>u_j1`VF1US^>p4%f{9X9cN?mn?e7Xgo)O5Fo+%4@ZiwX+-qToj z;UQ^(B0amr(CMJZ7as-T9&=3*1!0QJBl>nN*1VWh)V2*}jt|2c%rs_*qVu9bMII9n6C^6yR2JQN>1DM z`1;Lvzw`6I`Y(QS=F}?5;0ZZHV_MDEuS!`f}Di6jztp~;_3^^h$S-zT5ZsV zG+hK6@@ly+dP8Utn|eU_1w-%5L=QwUpj;i@Fp|{*maa2h!k11|%5-(a2sxAn=s10W z0YKYJvWyptOcWWLvKHmCG+$kaWJv^&7u-}R-k@E-FojnH-#r#HQpN;6j;eyl(`0`J z$h(0Qo)+G~a3l3p4AK^<$cJLTava_$0Hc6Ow7X6wnc`+P} z2U_2*Mn5?kFi9VCmCeUqU{~R_dPpE^1*Nd{6!^?-W!&rDTD(KWC&ka`V6CbubBF(q zK9j>F_*x<=;+=P;BCg0Hqmx-BCnTt-YA_*6PVdh*&mQ?2DI6wxN++)@} z!dzk4#JNf^9+kPNQ|AJ;cagtC=2e++BX5exTpKnstK^tdp>H&O+K&?WI;B({$8pRQ z1eS9hE8A&g#&Owi&fAxH`nNxPquaKDO-Of+C~)A^&3u;Dp(7QV~`T1Y}-s97wxF;+bOsm9)@gXMC zE|Zy{K2xHy5f5@;2nuu4;){~B1g&D4qWeD4`{33GWlfC1Zvx_cRkpJox?@mf11NJb z3_2@eW+^ySN>;?(vK`Y#t|3nq74S2vE4?%(Q4ImZ)vsy^4p|SYDuSksxdMw8?}$^~ zwHr*SsD!d4X3h=@VWbvyJxb1>wh@IaLSKiFumi`HYCw{FXaxjVG?qehqg7&#QIF8S zxmRyx#Lbe1QY~Vky+}i%q>$+HMJE-bXMxW3wl!Ihj-%K0Rpvws1DpW?IAAfe#~`@9 z_-)q%Z=oVW!p1^sOs|3>E0)lrrb4qLWa4j6vrnb?%XMZRMg(zQYB#E^_fWc>dG0Yc2+JPv)Q-kG9<)M*b3@0bH(V> zEILzJk;BppRh|dU%HX++;twC4*3+$I3Zf#JJ_e~dYBngEVw`qlB!Takvo{4ef?cT~ z$i-PSWH~o{1#T=qb7o3nt?I*Pq;c;mDP^V;-XBt+s4pk2tg0+E1-t8X-bCwiM5@dg zRkiP-ZsuB*6}6pDDmvVxWZQgZ9>;XEp&B!UrrxEhJ}e@@4pX;d#$}%~^5yOASJVH$ zKlp5%x8V-Q-dtN-6%mO!MfLJ@`QX*f*T4OZjPl!?e0Y+%*Sj})d8&uUJl)9M3%Ois z-_u5A=zgiseiEO3CW^C+hpT1%+;_e`&7!p24-i<8cv6yzu^mt{@Cwb+G))j8+=IO{ zpRO4hnMZ$pN+p_A$CKm0E)nyQP4BPH0ST_v3=;#~;Hoq$X9W1vQYeue^Lr6Pqy1{e zK~Y3bHv&;pAB<+f$5BNyj&{43iqX)pNg;|zQc^oGijFjP&2UI9lwH~z6}(C5y;UTs zRa8zcIk?8@6NE4)E_)G}aZ~S#qMcmGzDfkiVbQLlO@e4gBYFvo0v@S`-lPZoZ5oolo&nM-8UDX=UT8dw zv?N{Bm&|8}v6QJs%xYK6#cAv1L@WM_^$B>2DC&GcuO1PqrKTjNqjTt_l~wDn%yBX$ zv!o&u5wYtM^|rGl%iJUBpT0_S!xg#^5fF*8u3v3l|COD{JZ5AR$S{2<6;H%=J%qFe z*}Eh%<4l!>*7bQ%Y6#n_KIgMUvUR&9V&Y?LvOc(*ie;3!SC4z58c{PdO58nHauuE# zW+q2i{N zTKnaw$}y1LCndViK-3e?24x7h4PX|-BL%6fGxK~teR1gj{)5lP&1tw%g+aSyc3Y9* zW;*B`5W=X+l4E9~Un#_}7%31!qhDAlVY6N$2|$Bq9=swzkR{+& zR|1Bjj%O9XF_<|-X{3}{;tWA|XjW~|fu|#`HO}Bjj;VZMAT(j`Rl1r3V28^#3NCkd ztCiqgr!9q*e)MRQM3`HfganR5GmI)Vsor2;y+Caez1q3Ff2^Qyh{_ULj+9h(&}J#T zdD%O1!fyXn6`=}wkq+_KtN#p9XgoDlqBBEaH;O_-WJWuEj)aNjGpC%2n&4=vsxFJ{ zru4_48B-&ytNtp;8>??sk}DYAg?1AJk%fwCcOWD;A`%Wbt&)EL{WohRdOLD0Xsnz? zrxsdOT}9z*I7Ye0n?|>9)QFVbA_rURadl;iqWJ4-~ zGJ`d;&fEBE>hJ&8*M2^^A!<68AOQV{Binmyam@GU{yYE6|7f>2YATnQDajc6$q+Mu?P+JtNz9 zlL}oT!nM%e?spVDDS1!~97%OYGK->^*4oRG?ov{wo+UGrb`#z4+4Zpkpb^%O@G4ul zD{%6YNsAl+v4viC${)5%!68xMzXqE_EHi>|3JADE)fRnF zTb80NL2UZA_Lb|@mHu;pTUFW9T=Ngt1I~_zd0o{70)z!rEzxVDiO8XHu{=F&}R6XiVq$y%X^lh>3`dQtwHK~-Xb!xRC zxt($#X{HHuN7*=SbazbjMtL`DrH~DM!iQM_CFEh530x<}=6Z9Zt%zP&+V$4-;RCS+oKMwd)iV&F^VnyYtRZ2w8%+k6Nyj^GK zlpb>)8IxnX`S7d+23bg1G^NYvoRV@nodhKJ6m>iHxtaasss8Pse_7imq7ey3AbfGH z^|8_(ef94Cm%sn57axDHfB9NP(@u4JSC=O>tC%^{&YQZ+>o4nal*m!0H|M(SnOTod zx*zu5hq|5X{;^W#XP|5&}!YwgOVPsuW&(JcE&`(MCcp_5X3ix@azCsCZ;P4rqd()S~a`68necm z6b$=)tMZ0{CKzjIKtzg5oT`cd!~VJa{9VIw#1!;bP>lzLx~#wb!Aa>^dKotBtsQbu zII78bIN0Ig4W?5Osjo`nzeZ^Lxz`JFSFvgXCvO|BD*FuWMZAi@&!d)AqC;4bQT22j zkNaNT0gW+4Dq?QiKo2dZQHfG3CUCVD(=^2!nKRUFMno_!Iin=&ybZu9DuP34Mw*+M z&B&Q?WQ^hGZBsWn3B}Ml zX>%c2BCdt-_!4t`Ufv$O9Z0p+Y{@PuN&23L^|LC?JgH|)n2yM zMG1gOsvVF7o0S=>U?~UnXQ^6rykXmfJG-7wgbm9Rx+Oz{kYpc2?KupaTTw*|wW(i>eJ~jLc?)M)3c#NCIc+R2 zO<8i4^;Um&mB@Cri7%_HlhCMrYIWC-b)2>8k4{~LK~$>p8hN6IM|QpJ-#diXslMJS zzf4q5fHm$#BjZ}`9C!q}drfE_F`h1v{rcq;TXz!IbV1*k1IQZzFRDFKL`B?3wCM`o z4wcAc7@2@VWDcZTXyho{7+DOH6;oGr(>cjAGlPFXX>ZBL@Xfrk+;q4t30lm#4J=h+ zO{45zut(+XXok#IDRZ;V*c`)@GzVfPV|Ycv*bi1;nXzqS8{6C`ru&G*kuZU& z`}yyGd_PVnU+g9W?xbMv^=(*+R%<_g>7(0U`5%6J&M@>B-M231jV{)* zULQKFCBEQuP0Vl+@16&VNC~wgIETQ{$UbXy?VNFX;s?48ZgFG@WV$t%t}l2-xiecv zb)A(*yr41>Q`(%R#&bEZH@t}FmT(7860x4_36S#}C2fKlBq26>r5;}qqAqsp%TqD; z!OE+&iYGQJebV*~TG`P}g&=_H{Ip)ju>x2^e^OoLd!@R%&)~GucvHhEEUeAaK!eai zl!lU2vKF<7Uh*Q!WT|Z1<-XyB%Q;z5dT1emDK`k5DQm;Y6C^WcMDO_eyOJuTK^s{> zxsV^Gu!prhQ?oG3-I{9A%YnX;e1o&1tR=IEW>n0HBizlm;fg1O{k3|-8b~Eqm3==@ zBLJ$1DndLBTmm-gT2*uzO`&m|nyUI3m~GD6Nkxxi9&<+KJYr_e8Euna5g8R+KDV%2 zsSNbFXw)f}{b*OjN~tJ~h}-k_*AMvzKYr&oCqrx!rJh>P&B=IQ@rKx*?*HLG`JUe0 z<NT&F(;k6&~w9CHRCm-kE{iKep^R0aOS&HVIdK2@gho`*ksa21cdbmGc ze`&XOYWm%8f5Ts#KoMEWevID3OqG5{-MA@mxydaMm)p_PerAQFsAOOtUaC6A){vT} zfEmR-q;@0|9{u)9g2)pGGbT$f=t_E`6)wFLMlsq%4i@~YjR(&Q-sQaWu5?&7v<+JF zD`m3nvVpY7NP?V;v*6saspZuax50Xq5SG+z=@=L?vEavQTEb#5V17-qFS=N<&!Wf$ zFhCm_L=T$@(K4GqTZp5m_@`h0F6nLEgsV2WxLvv%qo$LCtS-HqP(Uy|rKOZHz+J2P zeJ#&^O0+p*i}Qya3F(Ac|((2llw)oq+wOQab zPgJRfO`2E2p{_dv_LRJxX%U6aab1ITqFy1E&VV2nWle}w(1@tIH6=1v>}E%JN@TU= z2u}P&&gA-Zx`;jflVRc~RoDvjRgjEy_tWMovILRa7{?4VbvM+O;Xdb_nPYQhbfMFl zqw=(E^N7>nn5b|;RjI1Y4gRr}Wy2(E3_}Y%BS23E?HthD-Ff`=Q~v&6y&b2`xXw+< zavzydm75RiSHY<6s&me7-TH6+-~NYm*Zn9ni764HhC@-RWIv=rLhRMc{NhWinUVVP zJ)MVU$(&^>H>Y~I&oNLmj?cgP;G4hjlXsWD{Xc)Sy?i+%qsU{)x}Kz6ca&@}s3&@H z4Yl;bDuH`ux3{Q1a4>IdmyQ-u*cMvfaMcYsX>fhlm6>OWndh~VE-4woS#@P&1cbU9 z>TBk%7Z_oyU*1-h^(^K z+=Xj>1TGXJM)6hUvrT&OFJ3;;uQA6d{`#G|!!b)sGsnYhy%wti8NwVtNag9&IgI@`&ex^*&o&u5Z z$eL=Z@|BQ!-2$mRCvmZ$3)6D?sfVGJHnEa_ay`GR1=Maa$zY?Ti}&Bp)Pe3zx*RQd zy3Z`*F2NMKM>HoK%@Gz+rEP9OFl%vpoBp>Nt+eY2r%{2s5OP^If3-fuwGs%J)t`Rt zJKD!=uXZ@|%Cln8r`_uNRvX=EWBhEyNxDJ`bs5WrMAyMveUPG7^~JsV;Aas*Z#rF{ z)$2;D7?Nij0D3m;WHGKLQ`)g*HO}ZlmwAv}<9532R-fU|riWfGva#hnWV9$;MbpfR zENWbYY^jL!a3?6-uDT|bsxoyc8bf!cERXc7L7>rOwztMaCu#{MpEcP)T`?IITssz} zVSTMk5fqV%ob5iyoLSLl3+8KyTief6RX6A0+rS(ux4CBo?uvfrxEjvR!f2$oPQQrI z(lLhcNY|=zmEoS1@N~v^j!$CbGZJ%CEq4c%2oLT+u5C)vDYS6pH;p_`+lU#N1?OA* zaV)w4fuJnCx;_8JoBa=e{O0z2R^p^+1dP&A6>l4s%?ywAaL>qZo%EmoU;kAg-z&>a zDoQt3Z0+_v=OHg%>it{sO>fU~M4h%g4$DIxA8VG}+*E`dd#TjjOMh`ERpaYF7hiqx z;kUo}XFvMuhcDiY^G%fOP=2dM!A-{Fxk(#WHEi-rEd@$+ry;K(3tUyC?dO<3SxN)N zg5v}pT9e4`Lb~)fqs%Dt1C%ZzGyovyU#jQ1lDz|DZ46 zK+H9nsS3?ER&!#af~Kc8J<9EmRGXYHM>;W~xNNz30UyB4zSl6M3(Ey_Q9d#rUhsF%+|FuzRxd{B*o`D zVYY@nwxH^*WS&O9o30BaH-h<91XM}L#uWzB8LGi;J7Jl={C_h-r2Fn+AmQ~wq7V>X zlnyb6%#7;uh``B#Lp>vLgkDoz#Y%L}h)BuWhP!)IL4lK=eWo~-@LWX!>|vZ!8yoRn zgCZ4Fo~RHNQ5V%Jok0i}H#gL4+R|;jc^a*AbaWJ{2t3Ay77}xVnBtOlp;9GGq5O`o zH4!{K^tP5HwizlIbFErtg4Hh^CWHINmW`WbHnpzJIJ=Utn~Yv|b&E)W7zSVKFj!&Y z7oi~R!2Of2eWw>4YD6lPEg@xnwI%|}YDGknu6eTF))iu=1o4?2YQ@5WS;)}9L&~uX z{OUT|@Jyz_eDNr>$v3@=!$l{ePG1hi7t95wX+g-xK7#5sZQ~O1oEId8@1wP(S1`6Y z7S&1zxnek4ZG^qB@m?krM>Bd(@hd?^fuNynmZTvc{)Fc|6U@u41LxA*&Do7)iU>S5 zE7G(%cUMdvmtI=1!y!Nwdtl+_O;)OoEjO!WM2FBVQxbg_aFH2`mQ&T-6a7}N^0f~) zj!+-(dPjdHh*T#tyrl;H03 z2$M3CIp^lXU9~;R$L5)}xz7xoTaxwS_Wb8>jz9jphnw@c3QUP?+L20|1rII^Y*T{^ z<}ee{501y*`Jex_-Q5khiikIFbUR6k#9k%w{KA9^5$vN{5sKRb_ISLvmXijKZ1=IMkYH*Q&Bph7l|_R(+=tp> zZ>pBb^%_BQ&Vmiw(XO4`qxGJIxcMT|Dp%|Rp>o3uN(W!=n7Uaev$<2~R3Wh`xSOpf zh_1FPnoiZnXojqi=&*`&)9=U_28-Dp&*U=^FP^1q#VcNxD&{VkfBfmU*mE^#s>=$F zb9Z|*i*`l{rJpF64R)=|FD;S*^`#Vso(q30v1c6b_3#S$F(=0 z^#jUA7IXl|s-l@e(LWk?wajVPTY2pvlurDOOzCpWsy2<1cC+izM6R=BA-KMzgza{; zBS9MslvI_$I8~}KtksA2KDSB~HumN1HRzz+LC^)yRjCrtBK7J6U7~0rs-mN{MT9OT z#a*(ih)QN5=g_paZNOhiL^2YhHd37h%Jc#YbI_bnq!Ci5U`ngxvQH5mKqy4qG2^M0 zyu7>l&tF{r(`QdN=M%oDrXuLiEXjGc(*?yZppZ>f5boc)I`moy(MZlc9n=+Rzba)ro@S)x8^T*IqMoi2$!xUMk(3OGfY5Wb-LdBC z!j=+E)jCt@Ip~a!nT=bjt)JE$WLi1$nOY)UU7%mf)}-T>R$1#FM9F{x;d7yb4Q>)m ztcAx)uUOH;$zmZ_bHMzgPrlh0a21`hH3JTf^bqM6;1SVvlgCRfAmqA!*S5U0Yw5W< z^s6+mv$llV_-Vu00GNSQ<*LqCB!UO4tuqzaVs%;9g=;K(pq{mRRZ`v2_xcY$0|n48qsPn(`#-gZez-KA4Skj|e8rPf$!o>!?}R zu+}xhPrA|_dr`{P0TMw|Gx(PqauRfHHMpFRuBIX)8$FoW6)3fBym#Hr4zta@>7)EP z*#3x01Xs`uov*~-imaUZ^2Pa2K7ahrpFQ22PqVworFd3S%5(t3CD*{*O-nX6b^UN& z{=0wu+uPT^u45<^GLgD%l~U&$owIb)ZOG>J@KiD5>7p-RXo+qob&)yi{=PEuaw!{? z`@@fatiFw5+xbS^_t$SKD9u9PLax_dxZAw7b#E(Oby~RfXs?ZCC@s!c`ILnF=87XIArRC#w4^09kENvH?_vT) zgD2O2Oz74o_|v91BnWh@B~bTR0R4#ns#c|c_{leQks^A<+Cv2&_(iK*w1Lhj>INTY z5|1_JzH~OyBEU)a$UH1*-PxZ>TSnLz>tao?j0!nOu z+?0V;dd>LhbtWcJ==;mvBU0Vqx|Lbm7yyh!dj)RlZb5Ld=`&KOv-*4!+u^xt8Z)*5 zt-q>SWsL#KqdN2{j3bcYHfOkNW)25=Mpt?aKRyEGB5%**KYjl6r$2qTyEzkU#_3BU z6q^TC^j$p*S7`~hn(_Gb8()9<%fJ0=mp5-s%{G_IMKfx@NT$rRVRCt_(PMtpxtcy-~6QZON~uW!_@rlHfv_o`QwisKKsmWZ;tzW6|vzn^Ml))`_t_Y{`7~p zuU^i`Ns|)xPr=wc2YI7Z8Mw_*4=fBx%%@DDaL1OYMk`?jMa|laop=J>9GjZe2N%;mlE`XCbM3us@ zU%M34a?4*F4OU=2!D2S}=@;zM*bBajEQBR%7aATH4f`g@SQHWq-PU_Il?|^qRTh+Z zHTw8*Sk}J&0*>w4pK8-waJ(B8E~v0xeHpCZSc50+QL<(wS*RH|lpujM<)}x2CVuoN z({=&<|9fU^H%#2DwXD)5A{OwhboA#@(X*a#n(3F|t>3n;SCnB>eB|?V|MKPjgD;L3Hz#42h;MUErNGTOAT}OOWf(B}_v7+E{aRef5>NYfgFfL4Nr~`9|a2r^8(KclUbxLN50*P7Tb%dfup`Cs{^Km6k#%+v|Tn5O-TSg)8C`lo%2BY}x%My#blJNgnlT!y4&qy&*vhONtm&7EP0_1=hvl#%vvss+Qym z&rGvvb+MtY59{(*=o1BvQ_;qmC?d>;7kaLXU%}3=o7byQ{3>0pCad7~zy*j-gR?8f ziaxj9$3!CI`O)Q{e(gJ*a@5TjPrt4g-HBG8uH-XtDsD%*_WA9ee(9=fJVBg*aFEc&(%&mqKr$|#l8?kNf(XzZMs(x1d>*}?o zrFi^H+EjWh4P+HHU2Ov2cQRO|MxwP`^q5vfHcsts8R72z zlddMTE9P-c);KNP{7RO`I6#wL8QvkVQZvGR9LHf};Ll~14bMnb?6gVXH`4w}g_ zXlCYSGmyDzo7s!In?L#K!+(8!+}@l3oj==7bBY}ug17E}o*AmHGED1o`ES1S>973r ze{|g4$#LkMGKQL#sLZUJTT#=B+BT8YtbBTs{i0v{`8p1p7c{sPHQu{@`s_>p^b?t3 z`+m%sPfs@=evtbfPZw1`ee&_RdvUydcyV)j`{MmS`{VzNCK{W-D1i?ltOU7{s}#H9 za@>42go7{<&Tw6tL?m^stX)+yHLqKx%2m#BQ@TSCYv$F>R$o85=3)oHo3Oz@_7I#V z+wXxmIm4n@vTYG9&^a0VVq((@-;XY+T3w4-rbSIwq=R){iVzPg~-XXPyae4MnvyXXTai)0o>g%6AKbNZe z*>7VRxb9l?gR9zhvFy{M9pa4_mpJUR-jk`dq(*?I)2c{947#j7pW)_sJrhtXu3#?X z+!tYHeR}@z>V+zw4wII)w~OM!mc`xSJIYA;ENdAczJXp>Hey`TL7F7)aUCraz5F6ub%$)b2*+rb}Gx(9NE!Wa~HI39jI0v$>`Gqffs1{L*xlxYjMThC6_e zb}wqrjt(LcB0}LGYi&!K8{~oNd>QmSE1lQglU4VcI*ZV`>J0dXVp$`#`8~QO6ngHo zl%#*GwP;8CMOO{fmWuMy_htH-jl}BhrO{h$5LJnxw0-NfG@;rHtXW{)@NB@hHf~su zlF;~yCM-*RXKsXR>36(_Ls?BCP39U0$79uq5r-q_KbNf2dNG}mM1*Lzqe~jq@X@-Q zJ*xIB*PK6FUmW_Qb)WS{R2nb3=IkJY#?2INh}Bn76+(YeVLk`P-^C?pQ}DdHb^F!# zN1v+^Ff#dYZLH4SH}{wUGGA5=k=xswfA^EezrL@#n=?#U6M{R4YSe%dV0gkch{`cG zqHv}z;v#yv{PuT$?pOciZyl=YZs$`))qdE`jm$%yc2)7A_4uU2^RnBfa=GX}wPHS9 z_Ag$WX=uvj&`edU`*%JpE=N5*>ag7R%V%GV_g}{2Bfrg@`@4s)fAs3<_T_*6vmf2Q zd>PT_A#EM2?K1cPC&>ErClaz9Ma?WSv&7va=F%T3?fRs-nkI$1r?~esuMuw5C`X59 zJgXJ$@mHPvgp$P#fH`e$Xi;6b>|CF_SMQ;Fte5_VwJVlT6=gl@35_dOza_o2uO*vuv74-SLC;W$r1S zvOJFbw%RWdsjTRCFMayq(=j7Mx&%k2&{1e1JzWyBla%VJc3us;EIgq^7W~~qN<(5& z35S!GgwPaXDaG-{1ClXfOIP>|2f3^J+E8P2wMwdVOaXgme0g0;A@CRCwz3K&{)Ln> z?S79ijew(oo5>rK>dMy#5-pxcxB-1zv{nQGS8>vogMGgBo{QHit0g~(vt0eZDspU! zMbZ`vwfNnYK=9U_73nxi`5Cka*j(Q{-qI=6c9%wsrmVEE$cW6=P4N9DaeqNYsFEY| zz@HN7B}2p6*h9HxFiJeK<_xU73Tlgpv3c<)Ya~rqGeX5;MpR1Ga5K>(qEzU;n`PVf zdw=`zw~uvuKFz3=uEdjj!Dr@31wtKhHMnrNHza5B^;7-hfAYPEEOWWLi+A^ScWayH zKI;hChDyelUu9*?nVavI15Nmx4Qf?n7P^f! zgJL^ntu$W$pje@dLiOc7+5!cqeq(yLB=l4e|M=5ylSqdumT4G}S8;#7i)@O8JL^7%fGbNfWV+J>m%yf5y zB&uJ)q`Jvqs*}=65P(Y+;;ckYw>Q84cTZnlNdDLdA61S{~In#gJcmI zX2WfZ*3Z}K~a9E6WnW)i34$g)Ug zJW6{QSol)cNh6nrzg1G;P)iqImgi@W2bwo`m_31rV#8fUhC95!hmW2}iZ=?$QsRI` zOck-vCa&gf9){N)?v1xmZt8I8wl>X5ZX<_#8?G0$I>TXknkQF3oqzXl9^UMBd)j7X z%b>f(6EG%?CIQAN>w1cqDmW*qs`Byi^gF-t-IrhgD5F%h-o3M%8@WsydfY!8?;icd zon0>S(T94zsU!R}=+Um~JHgGesR z2wXhtgt#xE>x-(2M$(u=u|gCIuuN(Fe8n~Gib3?MjGSG)DrE$M2DI>MYU&hNwi+-y zLO3%;%gvhxcC}*1>9iglUYb_ouC`GZ?kT-@Q)8c7b@j?nPN*qTX5JUN2t(m9x)Mtl zp-Lrd4IQ}-Cz0%5Xo|^NDp*xN!&ly(NDr9}JFMN?^~fud=s6hSnPmeEH-?}aA-paY z@vGL!g!WTSub>3PBHcBvJF=yvS*h*O#VRi(crtKW)O%1+k;sgQh~Vu6g>_%NK4XiY zx9gvDfGmMCvnG2wTW-;AaFpUtQJBM7bE5ervs=vmexxanwQh{!Ak4TH>yVlj;`3wz zyxWgKs#`=gu)#w&QuY~`>2Ni06*J4-X=GY5Gq9-hLomgh-@O_+wcC(Uk{mi|Cmx4GrxN6cenHP z8-045$6ot9pT}`I#>JJl2gw24{JeE+>? zO^gxOFnTWTza|2!IC`Sw=rf-=nQ;L2twj`I0>kRQ`5!eG)!8;2D2Tb9bXC4Op9~UJ zv8Yh~0n!a~5&YHFEZa;7pew2%;0$1eMdx9AW;bVx76`Tv5*rq`M|+>gqO#!A?8rsw zi&f2i>&dbj$M=sv`F7WXby{3oMYbo}Ar#jo!`i}Cmwb8tFImygp78Hpx3UYeXk%zT zUlg)o71`Vd!{m!kxpq)X(9(dt2opv~{<&_l7Ez>29{w(hANdb`W~>ge!7H{2?L0wR zimjG>?H+l+k=wtz>}Tu`?v=3POy05D7pKRfZX~@O8dY20_cL74)yqi3@%p;9HbPy89`ByiJ>R_>AADeO9G5w6&+c}7_2&HQTr>Q9-k+Wx zzkGM|-rbQ>RpZ574L@xzuzTGPfAQjSfB)Uz{KtRv=YRe1_TJn}WJXAvL-mGcedB6O zhQ(As0rcu~+lX0&o?m5SR946pBB<2_=}Ipf)q*mY;N_&E!@6Vl#A79Go+F&VmO zY;09%!Ksi?VlP6+VClT~_P&=LenZ;e5-l87h&t<<&~=dm zp7Ej;@&v-7wlB!nr_hmu{0gv|m)t}S)H0MiP$-J1r2=sFpA#Q;V6ul*%$%G(Qn4;To zm5>Ni82Z2?f`RAtvbt!B|bp! zDRX{a_(=?N%*A(`U&{XQCzp$klY8)kFp;G96uJ&1evg%~C&nMxh64n56&JOPfA+Ok zzy42uwa}>D+(?Anobug+#vxVm@~$pVG9$;PPY-qbvTjZhvfXU5&vAEKmp!w_doN1e zhs9-&hsT`9{b#RrxR}esle)_`PA_iv*Kgz4Gve5fs=Bs> z;fwcP&0}KvsVjrYIKHU$<00e-^F~7YFA+2H=6!&%bzTj)w8ePC6LqByw>ntx zKoU~nUz4h9BI}ZFFQTh(Ojl__s%LX8_fmRjJp;+I#zmQrf$e@VSdU2B0uft*Xh_fb%%5r}O>=cEL zNPU&k$U2box?1&wA)b-HgoQr*wlkBoKRsqd=F6MyzV!G0?s3;`L(FKEltyLWNH{B! zfB?}@-Nw+4GwM5~6pk>G?+^X<$1k^^|FmZL%a?h1sxfj8QIl}pE>E(Jao$Yz{)^Xo zv(3ok*vI?t`R%q}X86fY{_vA8pMLT;|NOh3{_1c1%A41(#aB{jaX$uptPk~Qmz7dW zXj=;F(>`>~nsO!3_;mAUE#GDv$#FjKKc& zUg2HKWRpXP2JprMmh}8cXZI?N*(v3jY`k?{mtJ!AaaGHPG_xpq65bze9_{K*iwQQf zAo`<&BUPeZ|4E_g_PmcwT{f)?)w%9K7kd|{XCLf60P?P1xjWs4Q$GZ9`IR)%m?N3%sg{(F(LEA_R&;swF{-+@lwWzZ z{pLUaHJOJ?)zd{I)lDmF+X!dg-PGkt#pBIed-Y21@5gD&H*f3aHs8MU(o49MGhc4; zoqzt%{)X#Omz3N2j?>)|I&(V$_4V0N$kg&YmB3El@+(Q@}GJV!-3$0TK zj59%k87PmP3piA+hiBfH8=HUKt)Sgy!47~$*Kiy zFij>Izad%vEU0(dJ7qk~dIcG>Eh+RFoYC9g)dPdefL;N{^SCXLlefs%Z1DBk@b7=kx{(4%a|L^LcnxUv>o!UQSn&1SLb)Lxb*HM96De zW_Z|9sH#Q={4R`3^J*H6wP=tttZ=WuXd5+orn>A*vSI{@8N;m?p0%PHi=oghOy>$u z8TBpPPohy6?gh^tjGWY^NIO7_nMH=s%(}xFuA_`pmJhG2;U=az^Tm05IpvQ(domxx z4B-RbR#vo}CkK%?^MJ}w3a@VsxOo#BF0cm!G_P`r?ht zynK0|F)#N|H}Bo)-HS|pdiNliGLM`0@9cCV`~Kl}zCYdk>7V}a#e1)i0}H!gMHM!U zl4gOISbN5=wT4vE=E9nVuc`>)Ub`%^EkR3{RXU{SZBjl2D^_j0=g5G0dbetN%?E5~ z@#>SiHY?I7Wpf*Hp`g+}w*^9KRJ*+|+5+j7^ybrE$m^nHRJY#nUCc&l(Cc%|rO{A> z03aRKRnU9QsO$ESO&;Oj>}FUbdymB$Wn90niX!44fBK#0HKxxTYQu^vmF(T+b(>Xf z2DXR=y(;KIyWvKD0eo<(YZ=d(#qloou+=t}s%*i?q2{>V)llkLDX&_q#qMxfb9=S8 z;?sLAsV#=qD;kqhhI-tq1ALsM_5>PVB{Zw+JYPT_xn{f|&+Ou$Zg?K5&H~3kC~{ip zjM1Lx$}~q*^X_RI7iPy^{DK>ZuU^zVyNmp)%$+UIm|$b!&m7{A5W3>7!Wzf z+TPtAwy2F~;^4hIkDol{fBfR&V;g2OQcJ9vmvr7VGW~cK1`#2#w({+#5|68!YL?af z)tBS9fBXCX{wq73^u1Sc95!6%EcZNitFo*-Qcqqp>i%MGcs}dt@%ZXZ%|o|MPn(`k z^RnlDQIX>k6*C_n-R*IYo1g#qi~scJckkaFp)Wsqf85@syeDRNd=b!z-pZw@)FW(%?Tv%}@B^ySF;#yE@ftG6^uPQMxl z>KzXa@Uk@oi&>;fyGzh9MPaA6Dx#nAv<`hKxz|a)p#W z3UPs_HDg-W@GXQlyFC_>jxymY?Abrvhh(qk$uf{-K-#SaN?pogqe-t0$tSTkWH*DY zqgMMO(J#A1{o0j^h=26Sw-$!YlJ{&>5h`;^S|rlFBrDzZ0E7cNmxtY6Ufb2{AH$r& z5~fYV;Ld<@VI}FJKfhcjlrC*J654`J=pV#po`1OUprRLv5E#O#rPqV5RWDs)bK%jw zX(UU4_`NBvCW(gb)yzj(X?|HPaFyn3tT1EtAdGS9+NrHzTz90BTA9)dudxMpk=9)7r1Jp5)UZv#r zwEg5Me(=??oi-JT04#}t4g=rXGL%Rz1Vgnya<8g^z&Pm7Ri^S@?!WU7zy6J1{Dm2% zQRSxFsHaPvZ{&EIfA`bz;$=Qw?1T66t2YwU?#_xN#J6uV)82m}QL>#xH6Ev&huxgx za>>)k*KbazQ=NvH&WGL3rxejK9=>|3nvZWD=f2;(IKTPg&E0#iKKscR5mAwTK8fU; zpS=F~#rX6$f8*c$n}7HIM;|uA28v-pj$GYIEJh89Rf1;G+0SA(u!KJU{1yk9pJx&XCx{7Ca6&}fh@-FIz~4T5ta8#1~!rMY4urA#d=+}y65YivSt zk$B+NxEg;Iu{P!>NP@$PXgfQZWzT>8(&QO)rSAa91Nym__WNJ19Stb;P9L{PRaK;! zZtiQ7ik^b=N}&pK>F>iUa||z)h;a7`^5Hm2sJiOS@V|MOKYVkXPa8(Bl4ob|6?WGJ zaV(^$8A7)10sI!Ib*^fXzjjmqf3E(lS+gX&4#f7}?taesraQ-!kr9#eSaTJs3aA1I zf&jth&?akB%S><5i~g3L)FzqrOS4UCwJ3mOLj(b^Ko)?SvvSUejCbP9PqzZ$VZjhnx`ksg2hdxA&83Pbw2uKL5 zC1emoo8D98qx<(Nhwr}k=0~gk+4JkgV!E2t&fFk@oZA#W>7vQi>#kh42! zo~`mks*RoGAo&a#OB&}@&;)u7Q_m9gCy28x1>mdfOsFS5B_60ZflB?+HIdcNUcF(vp_` zVs-)@RVeye=SH$%x;VbTI)LlRrN=xJT2!qOS@Md+h~Z#KQcwN93LABnDRV6riDK5G zi!d;t(T63U(U2Li1lcB1B6-8-WyRE7ryHz`)KbdpS6lU@Fo~ctLRR^Np8F%XoO`@X zOy$9>NT6V}+n4fzH4Vq8Xi`bSGAV$Rrza_*^fk!0Fo}th>s#g?6hTMKTc%~@Iz6Vz zt0+}=N>}wOj5!#}8xvUS$c*~L=K>%Z97%Y((ZM14b6R847At@#ERL24K`fCWIO5^Q zZ{NTF>fqpORX?Y1TB0CBh48b`afTdw-y z;`#UfcmMJ~{PjP$dLjZL#Ns9a`^K1%D{?xU5?{xl(u0HnN<;uiJdh~wqV%6+)+$HV zq9Oq5eWmV+dn`$MQ?kGu3wy6Wk4i6^UnFMEJL{CC%aWgvq{u9-g3jF|??p%$IOZNJ z@1O&g;VX~hB-+uJDwgpBA`vmh#5{23kG9Wm71E_lpHSAJu{?ANw5H%BWg2x%0V$Sy zWDvoe-F-!L`#FQQpt)EItB44Z-4_WaC|RB0wJ}sJVJcx9Vume{a;6EER3Y{BAO(iT z(@;Qwj7jevj9i|+&zWD+OOTIRlCc}mvrCGzK8!uMu27eHR9Iapy`2I)Zhu zcRREP5j`xV@Qt*O7Gej`8qrR8j&K@0b2oi%K^j(jO86$!ZV2s}l?rshQR}3me4`r z+e_w7e%VwaRW7q&InY7e`)&01z5uN@$KDliI5CUgYnjAEroS0ay*zGl>F#WcHv} z(3v=O2|DPfKggKFt3c85(oyNjr0)wz0zC*wAzgjZ2nE2LoIWl%aw<=>7d)esTRuh- zWd{X}&)ri2E%| zPX#yk3%P=WD#!AZx2FOuhQ*ld4Ov|&$~d2+uPJRzDM=^*B5|ZcQ!8yUOHg7eK~=xH z96xT6NQR$8H2Viqlwa=T62w&k5;@02q)kFXT}?05B*9^Yht!{5js(u?2t~B^#&-}z zFv*}6A#-Ip{s#)Dp3aft>8+5m9v}O9JCL$!REF5}r?B|{0x-wqwxO#BqAaOF0LYT8 zGJD`C)K7*=*^s78#b6MT%tuRq8?bUlvsy|;&JSLqj7Yn${681dM$EH_QMr?9<>qGLyAwNitca`pwQ%#;5#P%8sU5GkMawVk|op{LUGX4fk`>ziZ>21 zM@)xG=D3oIBR!K^YD)t(_T;(3(x{!-!|zH@R7w~kj#+uAVyaAcC0tVUefkQDS=d|! zuoZ<6DBNTIG^!RzNIPfVvP=S!1vj}KeC04i^{k@op#z0}grr790O3rG%FkD@EkLB2-a7p_F)gRMmv8R!X8l^(e_7qX3)~aQYaWU}U5FTBOPWn;iRc!gS*3byffn5ofI=^B!YP6PSD<6>fDb-!+qXL2mDUMQ@EfE121S9bFjDF{vZ`h+l*=zyC)V1^jN)!?4 zJXH?+-VXuiGpZb~cTgD#5xPzQfn(hDwzhH8n%RuEJKy%ej8*N2!4Pru7zHZ_wuY$IC)_-XMSGj7MT_qK!TYxt8$=#b`sRJq4mkg3=5yr-qLK%Z^y; znaM=vY{0IqFLO*Nivl5(4}j$BuO_AflALnW+&YT+vOGFLqq^v)JFHmRzJ!k_FAi;c z^30o2V#G9b?7fvz%8-(WrC&`?Gw-#W<+PNMzb&VU-qAu`=0-}p@>DSLM=Gf4SRB97 z8>Ww3#KJn2s@tqGmZE&LR%0m>86-`yo(3v20YgUV6C97L@<0h2GUEch65@b4yfULf z1fa$~s6~8~4vipft`#CodUeL>28jssRx{>E)>88n+s?COxl`rlNCEc}euH`x@BfZc zq7`rY%B>?rHBrGNze!b3L_ioa#D2S${3*&=ui~Q$?1_Pyqb+wr>S1XG_+&h~&_XENafiswRbo0A`v&!0zqG5~8yf zH9E`?B8fvFkU58MpUob>^%w+bHpLj?w#CULZnij`K-YnQU#;oru)kVj+nQ;^yAFtA z@FD_{3#^7wfY2T-`pZ?dm?KBBxO#pwSxh(z3~h)K0K4@vbe<)8AIQ1wy0w;URrT$F zL}am=PSPsma=C7|yXCeYLTDz{7eD>Y!_)eecVEBy@bjBpyKdX6u3Y6LNtv}GK?dtg zet`Fi;;3lU6mZUiaOthow-zw5&4_5wq>hRu@1c}lb&p-rCAHk)LW(ch`_&oD%@!+V zdeZYecQ#rsioM*Peiw82k7SY3sMk$trOK`YA{nCy7^4|Y9&?4tTd%~CAs<(xx+KAL z%97?nRaTiLL(EcO%=Gm$<*p-ag6R@`Xin}tE=?|~+&Wc*G`5!tkUf>heeRI~SM^o- z@~I2^icVQqOBB<{?GNhMyVqltzm!UIy4E-fQM853c2-zEGpfv9aupJVtH$WCRLw{| zluJ3vS5Hq`NU1rwX#(|V7A=r4;udKQef*rc#N|e~2z3hz0O{%}@}BhRc>pLu0cBVb zg}KXQm8ivZ-jw}GC55w71t)i;$oUx0e6hiD5elZHBbfqHLYjq|T~f)EPd^$WD}+}n zd``PiFghV`lNi@(Z`jtxSMlqcxs#tp|C@FXz7RVKc3$| z1po*EBl9r8$q|JpAutim=f)Vmxuxj@gNJ^=m??LOEd4+S3l0H7#L9$j==*^xVqu2_ zA&?_MgU$|{F0glt$?C7+sSOY-u1$A z{o?lM{s}F&?|uJkZyij({o1|5ZC)f}p4vr??*Krl4S5b@VNAh5kP|2J?-0P`Zt zg%TDbA|(wD0nG8)<1&ntyrojh(j2Yq)aXeBdSs=47RBmx4$z{hH6ZtNMCG7S^>}_B zl1DfIl%!u)fT?^jZBq-*kSDEC|&Hz$Qgz{|T-iKFhMe1ib ze`iDl0;88DV~3!ERxVU@9ts%Jy(q&B>i5!SJeSl|C(~yG{U!xEL`vCxg|G#E4fFbC zzKFU-nSK$+SoTW+@~#vTrhOS>tQy56d(Z42Fq*7lesQhARe;Yo5iFT3?i|&hIoA+G zvjjCIvhduJvFvpAEB~5agtT0YfI=V^aRU7Phlh{u%;9ih0VJl3nFzZct4gX0`aZ6A z$ZV$#vq)8o_jWO5bVvv>NQ4;ru-O?)+m3>a ze)o-+fBfBdUwi2|#_;_4?e%77s;Y9;ICLxLESg@HY8mS>Rt|bj#wi8t$|lA5l{qvS z;gv?JoRN9eNOh5;zzfTsFzEQ6b{a7|-Z3C$bMwUJXiiG`BmkN%AVS6@x|@>Vr_{0Z znF#@ldwBO=jdNRio1Ioi+{tJMJn6tAd z`Q=f9XuQ^`E94?Z*8FBrZAWfNAa(Kkz(f88`KV>Uq|V`lFJ;UKGQ_MVRoZ7}s6JaN zqr%Pktj#4T`(uehNXkS)$#zssI>DFPUn{;}K%3l~=SVhSW=xkg*@4ulRNYMl5$Z!8W{?9g~rIDPA@TwCN$D zw{v!__4q$o>gAB7=oV*BSH&>?8YW6jEM+Ih}YWzW_So zD9+BzX=9Ldvno0HW&BNmv^mn;Icc1|?B!V}V?{u3^1ggbcSmLI&wPO+S!MAT2aCbyZsg==;$3o>6w&J`k z|M>s>x5Kj+^S=GwlhYr4^R<`nA8c2v>zmb@*86;3%&;*l94U&dpWJpXUarxaz3P@cz%zJwI1>J*B1PG16 zv|b>n8C}UX6;jF+Dz8{Nhw}6%V^Gj04V0xA2_GUng>QM_}sQVAfMld~t|MTA`Ivc?uLyWbK;4(YEJHe6K_U(GQYSr;i+Dnlrc1zngs zP$)l#kc!5P6l^NIp!Triht}qD9b0Zxs>KHW0^fl`>zNV$mrs#-T?`18Nui> z`WN&=@fcgpMgNnx7KAP@r`+ z&r0brNp^4Quaaql^f{AQus}UZ2<3qwB{PtsSm_$3ZE|A_3*^9PP?wKF_v%~>qO3F^ zAw~|V=he)Iag88-yDZ$)!yU6!G5-e|$vQ-VsP1=w$jlXC*4KorDp+N!>g9yR|Tw7an{O;ZPyH6g-$szSUAPNvkG?fjv zTiNdH(E;}!Vhp>!x_25^TM5A(&J%1W+m0(&%_k6e*zBr%r$gI?>st`O$_(C9U3c47 zlK~_EAwbx*yIo5F-L7X5Q@M7#>vvt>53V-rO-BUVO}p8(0i>QZo6WXaEDjdazU^Gq zY`Xrs_3rBO`~ULyfAPT=wyHmR`h0VB-QL`ocKb))dF>y4{mIGHZI|1NtLt4qAX(!~ z5wvB9KerHh5-IJ-UN4#6e=cYtqu%1Zw;r;kaIRJerE=$2I~7iiE=;$LV!^>PfRJCU z?9#GFx#VJ{_Bf?A1!km_poW;0C;2a>T;`;2(Wxr=k293awmcf94FF6TZe^M;{A3bi zk`VAo{A1h>@dfKY$YT$b6RzE*{Kz%P>nKHTOD&yd+RJ+(7UZU6bi9rkg6aFNnfrS zmBQDj#es6MByBCKk{d^lmo%kfjFHMrD9KVS!Dt%BTqYp>ww6p}<}q~%SlAMfF;BOA z6R=9ze2^5)SQy}hS{F4P2#^K|rb&VTjX~hDC7TfN+yEN&y0kzRq$a#);P+m;dw9A? z&lwLMo#i2b_taE?ByF3-kM?jbgXe8$&W?tQYd{Ix0c%^$oBrtwtSZyg-FoY{JKNM` zZRiIwrr++!V2mt?f!X)I+it*+tBv3G)*?r_U9HeoXZKInx0~LFX5!YX7Ag~CwAS8k zcN`%`?tQr3^=&t_y^qFJLT3=}|IXL``rrS=PU;S^X_{aD{3C0O9lD3&|LJ$$|IXVF zrE%aFrV(Er7R1p zvx*Lc)Z+_sD^6JqTe>$j^E*qKVpXCIV0WI$(4g$(v+j6Pg>! zf(nmnk-W5`C8w}LWhPNvd>re`1<#95P|hsXmh8>(3h;`EL4(;o8}g;oOJ8VAy~23_ z%tw9UtU*{}ep>RWoHfOSz#L7s4=jgY?(YCVBI^NM%#si_CKz*STA1N9)rbvLXu)#%8mGq`i@R&@qgc#EisrNfw0Z!|$Nn@Z3R#_*I zCTsy==E_+Ce72PhlQWcagVeVyUE!kiFq$-|@v|zx7cho67HBJ1=UnU6OOQke#1T#j z-aDPX^YGZ5p4qz2x47?t1ge_5-VjLVk)kvce{oGQVr@78#sEGbM-bu8qcgm1eLuLy zSv1yA^Z@{dzK0lk5n!&Hy4$u@?QC85z4txW^J?41#J3&aJAC%(b3lwCdcbBrS*~{L zT_0m)B(UUTWRcCbt1DMoLm=4HH_vb0`^IZGeK=|CH@@-4#fQ&ce{vTh{O4bN^5Wv= z`lHW3`NeO3^zzXkzW4I?-@H3Fvh0WJZNKXn4OwTcvAGS?w}rCkqWHi7fG`IllN2^s zW~QWhO%=9~`8jQ;P)$jy!MW|t!+mlB$vGivV#BE~&inZpCj>;3LD+m$s5P-_(lj_J zJtrBDOo9v5nT<@F0On1X^vdXLDkz*Ak=#;>5|UX+hbizLMJHO+zFq*NoGDuQleInS zo12=)5_!ex6e`gJX^2i-D3Pvomr*u-o{B$}htHL3rmP$LP?VUpQP~WL^&4`8f z8V&9FAt}rX(U`F+BovMrlhsfzQJZFs7R{tt!c2^{5+j=YOec4-yzWd`oX>A4+qS_2Ur}*&|}}n z-eYZIAE9;-BRLy`AOgf-3?+pe5ck_Y25yd~?TZ^A^xItw5rq5AE+S11rlIX)?*V`T z3>jk$OQh*!e!SQ%H<9`3`R!!p*4y5*7-M|rIWjtHkm|~KA1-dU-g7gp0g*!_#GxH# z2a9U8{`UXjzy07Re}D7&#fw$H>iuTB@f^1zSKaW*?fSEipN5;;n`h_WdvyHIzwz?- z-oAHtUT=2Wi}iZb4uDeE&RS~=5@c)-{ki}60JhHe;x6nlsS;K-A3aMz#N}GDZ4#ss!Zc3GpC6_ zA;r}7kchgZ7w?D!I!SW-%p-GYfi+e*W8?^VS2DNknDQ>v<`^)?Q)s&>Y5QK~eQ9`y z((7gSYs{X;Y6+uxN;=2P8DEK9s)fq)3L-OC_@J#CPBzZ(BuIU z%d?xnnRHpPEFR?;BUJn;^Mqw2(XH=vw&m}f8}N*JoBZr2_hHa$E9n#$L#XmqDj_%- z8dk6#lA{)t(JD){Qv7a3*fYFnF&IlFHz#A5lshf~07+)liOSE>ObEh~BE~6Ob|eM~ zVq}xV9>U3&BTclH$N)G2hy2Q<`sOS5W=|fb=TpMK+cpBBA27zS+nL(&ZZM6Bw=1k{ z>V2qbkp1e}g}ZyqMB(ygay0Y9AR^e*w3v4nm(@Yjt#)9kax7qf?V5g0%toI_3-NbFytsq7b9|R0_U9go6F^#|EL)Z;IuvkNFKO8vo((xkR zz4yW2{_MZ`!*74`#d(M1EZz1403Xe##@a`Z@BF|2;nRJRpLkyk-VK z%8~8k%$rDT-Mq!o3?lmwqtNtbX;Q@CnBgjBUHs;nre#+=Bs(vuD zKaxgwWi`wQpoq%Vl%P@pykNmZsB}zNs26j5_Nc(&3KNu3Kbz~qq{_?)c z<`gLrbp|f|F6$|ki>SiFxywxKlNPtU9#*Kjto)WVF6>jm+F`2#di+<&N7U#{mgb{^ z=76qL=1Yk=nnAY*nByK5N;%$F5sQ51^#Mp1oJCC+^#T?-aDm*loy~1{9{dyxDbji@ zlRf3%l8=LN`pRVziW#Gpekl$clJ-Zk`Bdf~6StPUFH9a*Q-cZWeS>@0p*}Qu(>ML$ zyho6qiM$4*0lV2+I(^i1t|Y*MOn#hLb>gf$#-fsTJ(`rPh%CE4?l!h*7{pGS)u+z{z!1e4g%OMh zV106g7#WP4Hoo^PFg=<3ZXoBv5Uc5=Ihe&EM(+h=wHt>C8 z>mPpO)!$y+zV`O3U%ps9eRlch|M{2e%gZ-s&F{T_=f`h8eDD4VBJcYCdbL~jQ4ozy zZr#PbE5GYRr1%nmu4`kFdlE?&v8D<} zrvldiMQ#Mj43&vj^6Qp`f}r})toKD2`=Mme0r{(;TB4WXA62;vIwB%~FaxvlU&ZFX zm}E%GHb}nG=J@n6W&G81HG(jpIib#0HVY_ zN>49zl7Hj}r?mH&wLZBo;Ic%MXP`aUT2Ci~03uNerJE+1EJR_<2AE_0)wG@xu{O5< zI_YIWF=pj&rT_&+fxm=+>Ffmx(qs?y)M1t_tb$4dVu{UF2Rld<8|8Khg;Z~WlyHW8 z1SMII2us$(jubMCel(dOET=4LJCOWEQ>YCY+67z=gqi2l=_ipN8frNb1iAE+hNOUS z#<=pxBb+xiQVT^n$rRNy!5K>c2Jyhz_iFjtYxn9`9$9NKMg;L~55T+~%(RNDEzO#6 zv!uF42Hth%Xc3mH?((`hJMy>7*bOMrAmU^SlS#i?yQ;Ev6}z6C+itei(E)qU01~lo ztna#N)Kdq4cO0o9kWMF_X2GbB1iDlhVtP zq1EhBBWhebw%ni~Dj??)lD3?3%UoXY^n@_yIx88p%U~u8LzWJgLD{6HgorXzrz>FT z2_d6eZ^bzCJC$v7azg}pvnDm{IrpmwlSJK@@_v(+8ns%-@~Wy@ET3LUrad-%dlo?E z`0Vj`pcTcUfPw^qni|5ugl6o1GZLyomZDupQFbT`e??;2;L_uci(9vp9z53+b?_M_ z5+lrwg#reZF)R6-g@yT1AYISj$5PS{%0GUF=8FLThzcX+GBaksezP%(NcEH zrAv)+@*hZzWUArGdkD16gydt8FbZQJHwB{=C+i}N+Fqw7LqRG)OlNF{-o{9pd^Y61 zJn!|4D41fQ8vr1sSL-k z+qCtx-mF_D;34$guv%@SaPLDsYdG*~+nTx-U;r}=p=}2N?jsL_H)Mct*Ly;I>HZzx z_Qw@HeCx??fA#TfG5f`r7fn@#Hh%ZrS8g}ki4_w*S|E{bzQ&{@OwF`>&k-;p-2+{_@eRqRqCw-L$)Y2mr>AGh~g;z69l& zgq)|9p3fZDnbQvUz+fyq4((A;S-K|x$~ zF7Wgo_@HG*bDH!qQ(zZ}tPa7XJx=-r1$&dCCP_B`x$jSXx)yX6F(+rQP)YgLuU2FY z`+aE+RTO}1^$SE8bHH8JvgHaoE*gREwq>gjHOWE6BM^%CDbJzh8jh53ndlY9E$Vdj z5EvmT01-5%jOv9G80C>^FDP%X0A>@|>?D*fy)MB?airD{BbaF}?S;!m3FI%B??hR; z&)cX{en?JZ6K(G`;HPv4n1s(+#KQ$d+iT);y)0YMsX`~fuHYbrpVo;oi_=LD@brT< z?25{5%bmMG-fPN&2ap_P003gpWH%a0GQPZHA|j-qlx7>20HUHsWgG{kBDu`crHrPC?L&eLqMxPP*G zdQr_Mz90Oqi=LbLB!*z>+8OliFgctva|p8C?3#%SkrBcBFbrN0e1xOZquqKNLadt# z1njhNmfAjCuQv0;IRLbSzk7NR+Ky`1_F>n1Ap*p92!KGwKEJ%Z9sKI~^|!wJ{->+m z$De+2_wGp*;Gg~O*PlPXxm<1Ee(k0E^XadiUtaG9tjtH(>+igLh7f=H?0P<{*4yrl zv!f3$u5a7^2mj>9fA*7K{`^-TbuTV$-+lAW;-9?n;K#3@y>l|38CtdNRoizlL@;E? z7-d~0zV>Avx1XYwxV8j?VS31G zhidqe|9)Qf2aQP3Lq1n|)I^p1m7&~9)xaQtpbAur7_hVkFh1Yu+W}(A`;kmWr~fJS z&)N$>8TR)oeWL!zkQKFuE{}{p7-i1MlqRa1sHl-xIfr~>_Uu-WyiHAv)s03vBSJ8gK7$d^=dNjf^X$dZ(d601O&`LK-+6Lzas70?oi%pb`DfehqX%af%gt-YhsU-1c(wWO ze&?N^{{3%y4oCB8ANZrI<*z>cVzXY|oL_zK?&8%c{nK|}{@uqXk7sqoe%*FA+ivSQ zkxo|a%qeT{OaiD_fFWDFLIytBj43B0?Z?d7`bwYuWH+Ch8eO2%o$vH0?h!4ZMG;aB zg<={-nd=rcP}k>DITux>s6a0VnU_tpk@?UvtzRwI^t@?YklsR%Vfkg0HVIQNEFxHZ zY(Bq%P#ChMKpvLM znp-}H3$QLCm@{b=f=wy*Ndl>@YZk^7npo0e^mtjy!9Ai4isj|Cup9iG@4dK(lQ8Sb;`K& zjR=KqGKhoVazM^qPx__F)o4$w4FH*+r0ls02>H!HW3m%-R_lr&kuerig0oT}s16~* zLI{ND3>kn4;X}jkzkGanc0|re+tZ|qyH<7`)pftzP-Xpco%%oF2(>eZbJ%s{93g~u zfT{wCVcVia9(;`AD#r|_vf_R4L+|72@_M$Iwrxj5Xl=V*dmpDKhr9I-tnou1nd^z$ zZ8{&Komc(9ZQFYvd^fCj9a=I-ZQB{5q3b#ygwrF3y^kyqSb(VWJ_&V^#W^c||M2+m z;N^$^`hWaizWeqoGr(u7-EIgz^6|<1fBo5q_YS7J7=`ifqM4ALRCcv(JCWtK>w&l1 z-N3x+;+<)=K$tdmT33%x=f8e-^Vh%q;(z}8U;g@okB95qM>YQV{`?OgEq?g$@ZpT2 zA9j6r+Yj3y0f~?-p-n;uC6f`5IVO}s1*{rhQZ~HRs6Z5rWYDF-)HMM?@jcacCAqNa zJ0{~#%db6^ki>BaQ}1QX|D%#bs?P6NAe8*hCIt&;_jVShGIpoE+?+@U<+HB!RkyMQJtA#;Wvz*%S@+NGw8`{9n>> z#My9;vXVm&pP~UyUd-uLNA-D;tj*f9t1oHg;{*XLoOYMfghRT1qL~9Sf&=M)(v5#o zkD+uswDl`~F>2tF3NMi%4VqDEBAqg7L8a2_Jmlm!8!SX$#=^z{r?llNX;E8M)S*R% zA{t?t00Srp5^BS#Hq}_9d%Q2s)NSu zY<;^lMhn0sY&z@5*{IECs?-+A5L$$aU=sa%$CEeTeVOXoS)1N^4gn*hp|INGr0%yJ zO{%czY~x}(0HgSTjwG;H%OQ}&$P#1H^g-|*1$Y=X%dK&)s!Y0o>-DZ`s?F`v`;J|; z-E40!Z@;*>8JL}OH&;trSDVgnHa%eM`w*FJZBLF5x0`me-C1j_v%&k>!R*=fa)>Ml zL*N0R4-o)-;PPtL2fPk#4B+dupGO9EO4&&bD39iv=yyB?qcI&CHs>nvi| zhq|eJ;0Gu3XY1|dZdf$+*B;$jw!;SHC(mzg2Jg52c-jczWY!?iqO$+PPk-~PkH2gN z{_OnXq4j_K_~@TJI{lryi$|5Aq2F!0n=Wko5CN>U&XA!3&E>)xV>WymE01%RUi+Vt70ELvL?W`@x zWYst!N`yTcEC++&K9G)44I#|Q=@ZnBZd5Uk-jn(ckA9v!c2IgA6nMS>ASu`>7u?Y% zO+cGyML`r0&##y!>1vrKGDE13zZa@ebG7?Ejnr411N{J zXn9I4ZT8{J4R!HONf-zet!IKXWdF;LSd<#(>hDcmhwf%FXss{0%)A(xG*Y=^krIog z`!%V-PhC6X2#bk6uSJ@LEMf?arJaYZ&(=9fYOUW6QFnz443!FY&iPO@Qxpx$XGk{E zQVSkk<2Gur2(HM5Bf!FL`Hr|Zw2PtT4a z#29?+y-6E8EEG6Bv<5LmCTmcDEJdK$^+WUO&E^d11MCGR6uj+_+$M4lha zrj_e(`|b$hd!EhZA7s_Piz(DkOWvq`gDZHFMM^^OrMM})@v5E!};({z?E zFJM#Gz%rjUX1#s?$KUE zw_m*R_ACG8=b!)WhtEvgy*@L4^z!1zFVDVrTEBKUA;hKkt3GV|VL(7K&L9@+Q=tr1 zC+(vt%XGk2o0$Dr@}H^ zP8v*P7XYN}X6?NbhEN7EDhBg9w`943Ql06ZOL4Qx4+w#tZ)uo_%;_ZnK!I3Jmyv*l zQAr3HkWpw$Ktv3nKwf3akVU?Yk_GRYp|}?zkNRMw=Q2OEWXyVife}q|&Pz!ail{S_ zF~EXKrIMQJvPg0(O&z=`qB0KfkP}&2HP*wX>GGO+TGaC({N( z^f7*Nemz@EVvO5e--k%XIBNi~9YQ-qK_ei>7|I(-VvR|4VPGb5%j@Ow@#6mbZ~f2z z)1M!n96p{;zxU3|pIqF|ntDnU1us^sZ45|g483$R|Ln!`ci(^Oy~D$+^=8qSgT{Vv zwf*+p#k)rbzdpYn01lk#yEvOxpX@e)=<7!ZcN)iS`}WDv&1x4}wn09>S^f0ci$D9| za~tF8;_3j0fAVnt{nN=09-h2)cTol34&AC9*8Q-HK@bd~$=+39a+q<^q~^XglPCKB zM%qr{+0@k;GNAk-Vtxb1dICU*kz?)-O{NVMf5Q}2k-YITrKy9Lj_;gb1T`08T_wYu zj-DC1+y-N|T-7cu>-o&d>66l1rfpx*FRikz@?wK7bLD<88NiDgZ-UZWBUF+LRsQvo z<*-=*myQJOxXW6>NYjZbA4Xs_uZ(BxiM3dyKdA`CcslLD#kuV-=A{|60fr<4Pi;I$ z{TvDzk0(X|k3ew0o?s{o!P4g&d-=>dsjEgVOXG%~?t;S}N<;gfEvt@o9zaExa&6=? zE~LWGQrg2jb|~$YHf6J(B%fDOIJ1H^CoBL}Yz%5YCCm!Q(7199S*oGndxA;;0j

(Dh(&cXMkhS52B27%hUK!0dyM zF~-P)k0`)?h{A?&b-Rfnfa7$aE>fv>LCrm_Ukh6n<;ySAP*maz4_?K~0o zLlmNcBeVDrJj;AiljJP{BC4#N*44`M#fP8%=#Reto2Sp0H>(P1z1jA{m#f{MeD~ew ztF3ti0O>}4C{`&H|jqv;T z?>%VTj9@$PZR?-yHaj01gSEBaxOZ~3Y2SV4)t`KJ{?lhyfA!1H-aVbXxHzZn`bTHe z-@h~ay?e7a7Ve&d!ME#vSbM(>AtDeN189=_tNIEQt~|BWIe1fByR=KKef#g!HpCqwgMSYP! zo{E%t&rMNBt5i`aOW(_ZB%IGYtwGsS5!LIUsLB!)B7$ge6rqn=4lEsG7H*CP`Jm_o z=yan!c=^8hnv~J3pxmiI5)!LX+`h*@fSO%^|$BL3@)Z{vg$)JN6 zXg#0;TV>(`wIR%pbHVi#K+pGGp9IYYp~M1mYFe^dOX){}wA%v4Wa(r!eKthQwZWOe zH;*T8-#>PB<(i7uTdM0M_x9^G>~?6J7{b72G6|bC_z)stJ_^u*Hvv;xSG#>KcKNa zi?IYChl^?7`7T7Vw(G;H8;mu9g%LvJKE%juh%DhSL_l=ThQMjxDiII&&W`=l^RIvZ zYyat|zg$eGDV_2C*B}1kqh}U?SytO-X1L`+s-fBo(#Qock%0sYe1@<@d7c# zSMQu0EoRHMJDk?Hy?^!Ss0s3XyJKUveShxRI6E=)U{-yx?IyKpto_aBFFeFYhttlO z-nox1mcP7Q|Hlu%01m(T?CBAPw`bKqI-C6d@#I?v^((cp1Vaqlz?%>UK(U61EanYS zqHMDRbr}Q$8gq048kSl93LocgySwxcgK&=@`sg?U20TIY?LZhH4 zTfxA%_?!!@nu_EXkgQD)zBxX7BKm4l0?V@-M&905!(uKwTNz(YcerH3x%}%h zJWOb^`SQrzmdZP(ttbek|AyrmDBzU-tpvwt5ts3?BN&C}sdpefqf;}UwU|)+o6{9w z86{1+Pe@ptOA|_-U68VSA5j;f4OlWX%Z*46zAzhO05Ih$pkipHHIRgKXJJg9GeDQu zSz>x9QMXj`h?)l=vP39g2noO<)CTW4dZWU74^M3EzGHZJwaTqTjNZOF-BSS14oW55uoh@BdmMRBGaZ?cSGx=GlT*L zsWL`bj5Q%JCb>Pqz>!6+H~nVWJw7>j_0@+>Kiuy8w5ks#)hAcW${61d1LB9zUNAuP zvFnGEdD9Ky>FoxL%<9SjkiepL6XPa^>iN`78`-u^Wj|eS(NQmb-Y@z-md8%FR50moaiq2~;RFdU%qNREK79n)k17Q0(}vl!{_@K!vNZS*1KZkmUFh3R46V1lwX|5w z`Vc?6TFsjp0IYKo*<^Zv1|O|4hHzp{2psak1`z~_ED?i*$O#T$V|Ld!-}?I7fAfov z>&o=Pzkd3{R#k{RM?9X?3=$e2LkwR=AF*PG$Zlfx)*e!DrFPToG4y?u7@>9R$l zAux$}=AH#3-#wUtG0!&bFW0-jJiq??rx&s7pFh8{-TG^0_=e-}9hq-W%&U!@NTlG~ z(C>!OGWRGE#1J|%l+q!9(iE*zSq78~9FZ1NF&QOjS<(kjyB$%}J46AADGeEkIk{#O zPlen;Wr}VrkAh(GtF4>jxjHGwnd(mJpdo46RJRRTZ^iBP)N{5eLYNBU8E&L}L9uSv!obnegwaW7v-^Ud?Mj~sK274>0I!50FO zA(1ErT2&={;lr>X{FsQ~>prmFIvx6O|sHSK(5J8-^k`2sK zPpv~6XSmFu0n8t`9JgvIvyIob4ttxl1-u~Vz51((^zxn+6o$2Jz+2G@%sgIlL;&vM?yfmFY zI6nH|df7*Q<#5qB+U)#;!|5+CZ!i1dS=)->tf}r+^$_@QQr|zGK3(oE`{Bw5Lb!u) z9z9ev6Imhjo*mJ0@CO#&oHx;0K>E1zKi~C#{l)o&<%_51tIg(!hu6V>ePMrhYQ9_J z6DubKBG5(Ih1f;$0)k`=nq*a|@szm^sfQ&YWtW3J>4Bt)F!k3QwS^i@m2*Btw&Ro} z2S8nF#ROIBJ1daQCNnHEN>mXM!DQ<@bA0*;2t=4bn+_QDRbytTcB}f%s(MT}1&oGw zDEyMuW~ksxCg<|YUv}d&a!YDLmZxSFOlw0u9&V|L%L<2N2|Wr7vKgzCLrFcTvTPPd z$*}0hgQ8K{yFU8Y3#zSY18Fc%c5Z1+8%b18T~0zm%wqK%oLCw^QkbTAD;MY}OFObz zbTT7FGP>=O9)@hRrk@!pNAqRHPR4Vynq(YY$}qMk1G3f-Qi#D2nS|kHFC)|gpftjx z6eKZ_W#VB@Ti7{yL^vbAl=f>A3dCi3mvsWjlw(pYMSz_rA)`Fh@C-vqvD?jR z{p8V_Z?xXQbel@M@uBL-<==(M7T7TUDM05C*uI*qqW5=0`-7z=Y??uTx(5kat~+qG5Gpmoi(8TwE) z)poh@Log_t_1gP5J38z(TkFiw55j;JqnFuy0*HP$bl#J*)|%y}Yo-%JCUW!uedqfj z0#OLDi!lONkrYPWb-`K#B7r%5M@b7E1|tI*BLEiBSRz5PWCOqT+LJqXPhXjuPj1$a z4;Mdt=e3`H__XtV@A=Nb;`DI(+w<$+f8*7Qw!LZlZ=W4IkmJe)fb+g{j)3u`S+Iq_ zJActM?u*stv-Ng9tt-Tp57&KvHmP1aI#_#83=N`ZUWfSkw!aRsG8B9Njnjjlua=hR zUp=}156^Ga`Fyj9?e;$L zyEF5H3H{F8zJFA|)|g`}1iTAj!?F|UA^Ce{d!w8{lKZ#p@`#%Co_k?Vn^sm|lYHo> zB8DZ?Fgt-_GBU+1_fm1EmKy}^<#mTO{np%Y5tyTdL8~%u1l4s(udLjn%9~PUO!GeO z=RxfkgFT_24#JR;d#!m{%aMGo@(@co>{BhEAlsxRaRQgRG#(OTBb|{B$c!r`FXp|_ z#;gEQK{o1XmLw>nU{)b)b)hPbZW$fUNgqngjbH^!G$VqMc;jM~I@+gE5e25rnj#I- z0VaJpV~n0j0BKu24^LE>SSSJK>D!6u7ZF6{$mM4ya-+CDCVx>$7WczEjhP}M|@LnYN z=<;S#+l4W`_lw#shvBO2Bg)IO$(z%|75itKmYEuBpRe2Rz4G8(+ir;hcjVZlV;T?<($KDT{D7z5a82Y^OmSu>!a3ca_ zR4|MAKpij0Fvo;EYrti~ep6w|Pn{GC)+88B-7cBTny3y6#$XCaEw4E`eUv28d9-IK zapD=71rnwIay^9{(xJ$(lx`LG)EbzAVMG$9ktXN@D5p=xV|doMxd)2ZLiYb=LIdMs zj22uh3NVe!+iw>4&>}1V#J&kPWHNOhE5PEjhyT@C^;JATdwG$VEnYYt1OQj#mRiw!xJ#dIXG(_=?WX;~_H#%Wu z-5=x)Z_?1kgbwZz;fj@WOkGdLmkE*7b1L6+rA+jnb4`b=?{>ymL`QIB=(UA={my|Q zF^Cd;+_lD-ICwM=qaYFQb`ayR8^9tG_SY-2$RUccwle+g%FZUy5D__|ei(+fs~mYB zyIt=HT;)QDt||5=WUIDw;+z}09<4Ez-7L40#pL;mWkfL6BFki2-){O8 zJJQFv8^RD{@DcU(0Ynz*`iSWwizI|MS-h($A%@64#vl?|*4wsi+Y`X||LAxB>M#Gs zi$NqyW@bquJh*dswcLLF(cRUy`{v{O65_J+fBgI3{rQJa|M2T?N;}-2&brpSX*Dpf z`fyZL6YCyKn_&on;C9`9{q*Fb>wdXh5z$#y-F5bGQvH8F{qkt){^a3)_uUs)w-evKM(wxj_`|7ux5Bp@x^Lova2WcQeHXbCh=^zm zpr91DWKwrIb#O*ZAt{n)^6YaUWp5?JAenbVO4^~^$s|{tj1&qOV|2<97RhEw;|Q(q zRlb)pO6B%gyWCmRm?B686-n(UitVNmrD z15M-{Go|*rT|l*YL`bIivUf&~^kWhfVnMXix{c0xNTnv`E*sR*k;n`zfyr2FXfL%b zooX_bXha%oPAhnQ zTAdxwC&x31U~Pi)kqidUU`U1mkc7cZDv1n{-D2kZK_WNz&bs9iV(fgZo0>z6A-VfXLE(+V)_5Hwrvq`;fyU4O!Z6dRE#v(aK zV$Aawt4ZS|LhJhgSUXy+yUDCdPg`I9v9_kE zDv)TMYcFozd-7;;|IX%%=Wjo{zut7#+52}7KluDbHLX6sTsmuBY`g2-7Oed*AAB~5 zkl^)ZH$>QA`0LM~^&uK(y5L_qUbLORJFl-hKbzIp>uxPvS8jsz{^`lHP50nranW@s zW&(KO!#y{7^KkLP`=)XyVh^1CXtzB)oc)X6dGF7E^=Vf%yCI6h+irOCV7}_ZgM+C9 z*s|2tJf2T4cf&hJ_5GRq%jM>~hqHsq{oT%A4xwqBAz*>QnlF6(?QZzly8El^_4RIx zp|ivGyNmjD=0BL=?=|qf23~ctAa=}ykGsfilpZ7kL=lsO&IyyqH44E3hIo@3!Gga)VjUIl zETklij3`S4Ca_#`d~)aa&l?T-aOcs3-Fg$45AWX{qO5OL1B-R8sjRErYPDT=Lu3vR^dTHhpDH+XoPcfdL^3CceHl28{8M(a;b$h(ut6CPq#=v$UJ%eTa$Zwr=^+=fCsg z?_F%B#0 zssU@m7d~u<__NQS_ki!79C(QzuDfp@%#r!a5Ozb{u~g3PeAqHSYdeS7Agvjffj{ZO z#F0hpg<0gaY5kjZYmIe+l{LK&Clhxzu`}m>wc7nZKE1m1@p8TEcDpn3?^f}96a2Q5 zuVZ|}@B@qu`xxRb_?Dq%2}rEVaoNs}_UWiZD?w7nWmh=J`-DWM;18k!d%|k44hR6| zmsREs6O}dhA6q?7h6@-YMPumGrB58-J&`~?beBG`qHRqMY5nxa|`cp z@9Es47(SMEv4Aj%1x!v-WrZ(wt{Hq7m1Ih=?&*~!=BYf#MwAgE>qThnYLi(&mbodM zq1tDFJvOwEpL%4gB~qFya=d2FwMyr#va7m`gNP}pyLdvX7Znk6Odmt`t=3gt$v_c^ zHQhN~`uroxL90M>0makkquV_hgBJKoIvT3@9rL~qr+|rU3K!3W0Q<`!4uk5JKBinne;f8w2~{_W8A& zI5(Z}FoeN(>uob_x|?O&?uf{Tfyixdmu^05Y8U(9CiSpugZC`Fxn1>L-}Xb(xPys% zetGMoSVA<034@PKRbMT40mMhB3>s^^j|5UX8@!)RTx98j1eOrlAlBBjL+~Jm42w8p zB=frfh>;l*k19gOgvbG8JA`JndhPq)`1k+uKi@lE+&`TE?DO;Qy!GhfY9$6AAD_Iq z-5$>B)8m6LU)&te8Y}3W`N=Oodh_hy?&0C>W-9;|>Fqmr-nesevl<@WJ^JwCvSyUX zeGGGBZ~D-+{zdFRYj>>=gT$t?TaMQ;G)NM-6<$aG?USSHZdeNo@QY0w5Du&A6zPjy zdlTc=7PFRlh|)8MA^hmx!8<3jf4Exhm@1+{1vjddxlSP+K3`{8MGLiDp+ z|Ey&WP}TJ^M6`Gw_&;6Vo>umM`t12{H@myBoyG1{{CgDNcm8{m_y+mc8aktBg+22i zvJ)N<8HkfthyV&uaofwMpdCr0(nuAi29i{X$~$Y0P9CK$Ow>uF7(yurQ}BqQZcv75 zj9*QU^dE~8Z7M#aKpwR3CDEX>j0mWbA)Oy3 z7(7=PDRoeP3Y4hB%n6_=CA)}}6A&NsNmZ_!dYcq*DZ4-C(+1QN1Jd=$8h^?lz8>#I zVXagHmg8+tm6+?Gq&q%?u)U ztLnBJT8^`cd%kUtC(W(zTjpa+KRBBG=d1Nq+s}=8+W7|XrOGZG9@gf>njhUi`SbJT z;iPs*!2G8VCez4w&JKRH>X(4WRTX0}9CpCO{lEgooDV#=_QAycuh*-;TJ2hkKfAf5 zp?~b)a&!9*^xtv(%|^af@e_(KNvzmQjFDr@p%w6eK|l)ZmSm==%vhGa=Jg|CE|~y{ zNHj+$k5J(fP%;Q~4L~nb+ffZ z+g)#+Ba8jOu6E1iuBz?X!NiD=H8-o>d|KUXdts@pDKrHV1{Ma0l5O0WWdImLsK|sE zgN_}Xd=-#bB#Ibo{167~Xu0~m-}%O0fAHy}gW2P=gP(r-yqQgQ-v8?9ISCFNx4nPk z&YgAF8%uNNhF*UE-6t;}Eq?mN<-x4BktY^EyjkBpoRFo@Z#FmE?v2C6Y6yeK`FeBS zb+^He-C1tCt;jlrKRBJeH=X=y)d}J&2eb3Gk0IQx++_$6z(zc( z?B%Xqih#r;Mr+J9%OG@8yQ@CFG;Mko0IDnc&8pvdaJHsAmCNHr{g*ep|L}b4i5!{L zZkQW+^Pv7{x&5OD2VZpkKP>xsWv9%2h&v(-JRr4#HxXX2{MqN{-#uG=aCQ5W^Xu() zOK~{vci*D^8&mwc<*!qGLVO2#3c@J8K+CZUVGxOV;TtmQC~DANwm$ToFehhEie=Bf zj9|2q1jwEj%*i1-zeBlLi&BpH?*YhY)|2J4p!*k+dfBoWti70)=5!3G+DYMNNp7vE zr~+z{fGmrKycd!G*eJgvleG{~%)gUqH2`7KxFo~^i_#!f)e`xteOx90%ASuv^6^l} zLOuWVJSyjzC}NPlk#43W1Ubd6<`p4`tlVz;YiXv55mm5}=uCro3oc8inai5bS1|z` z0g8h4=cgbW`(<3-7)UwXBcWHsb65(Zk^`B3OhqvZ*h^|eu_gcl;4HQ`RscOG@3~BY zlHwIA)#q#&k}VF3lBejI_U*SIX{J+`XpPC?;`$_8W6~k6i$ev?hH=^xPPQ8aBpi&u z#M*gf?pNk(cjqS$j*YA0dS?xRfI&z*Ai#`GmDELKEee~u;ShnN02*sUV6+xI8w1Wd zQ&--H-PO`n)b+vDcIbWdy%%)OA&9j`1jw0Ty|qgeRMwz6 z4Fn&JF=z}6TjxTEG1+*MT(3K~S^wid`Qcyw)z3eBar5AC@&3yXW|PU6*K1eV(1k@a znN26_&2B(4L=TSUm+StM7dMTw?GS)y@DU+4RlW1E1DROM7SA`k+SN^EpKmtbymRvU z(ZQ$d&6lf|E8F$qWMWQd_4%fMJe_=byLC-{wd-~v?t&+4jG#rTE4vzoyOa9vtp53G zb>G=Pet7clpWb}q?BKljpLN5cazl*K(2n87Hr$_1&=>%)hP+4>o=kOdL7?4{re5R;^Y8)BLN}yA+#LZ$X$$HmHdR1 z{1>xj`SIyvg?zR2q<0n3pe6;BHdUD{Z4p5Ub^*0$%>mYXAq6T)0mwRBE+B|5$S4rJ zr!gpihQ4%a++nb^+my*6P+QU5*^8EM1c9olu5Z>459i0PJ^babKKaqN-ujzgf4{Y>wo$BqyKb%H86ko?(v<8yY1s;?>EM5Tkk45@A|qjGi&co zn;GKgoBp7-s}O#+Y7eLOCD&YT23tA9uo3Zu!jXYlWZ|l*F%fay_nRvkKlF$O^%8@PWk{mtz*B3Y4nWvuWIPUaunZjUDQa`0dFQIG}!26RXj(K-f>@dpo% zKUr?Sb#}P!Lyz>ljTb{406m$Hx9tYyua}#jy}15f1J7=jL%VsE{JXIIrt#lw%-1S@ z&Bzgi2G~e!nYVyLlZw=M2?SE4i_&F~>xE*VNhm`EoLbTRC0XLw+oG=4k6V8uk2lH}q^R^V zYbO!`INzG&vVD)qVj7auXPhYJF43j~#p={TOC@HBYYe)e=Qk~5$yg>xW zIavANEy!5b`$-C{F~*<>2&d9a2!v1(O~@Qsc<y# zfDqxZ-Br^XovV+J7^GXSg|Vus-}H9kw%g7KA72>_a`L1nWtaoh)Xs8WLhE{_gID{Zix9vw?UNx2baJ~J< zFWy_uvce|DM$FBN!UtYX^_vG$$_S1_?0GU;F#qjH^UDZ@EiV5MJqv>_uU&L@+ zRZr&A50~58n%mw#oYoKL)w50ePmhm}YV%QA^!c*=~sYuUUT8gs;``%?93p_>kg}<%tNOn3J=|*@Hrt+)_PaRr?k=f|-dxT{M*| zV+P92mzpRkETSsEjA`Wmn43x|FSz1}r8CT3RhsS#FoK9ck<)Kiwo0L1DBct(g$W#| zc-5h${%v#@$!K(bcY($>JmcB`Ae6x&jg2Zow^H!4xHN6|@YwHW1~Q?Xqjjc!>AcmV z7jw_}DxG+gLk2MGhn3SSE0{92+qtTiiDwj7=OSFh*rnSpw_`lF$k14tvz9^-z#3D+ zX!hfAEva*BHXul8a(Q)y1ptiZwa`sX}0~cH0LsWQan{Fmx2DtTBjzMGyjmmzWF*owbpH8PH&i90XDVjv<2p z3^?_}*S`7g_VV%v@4P|`Pp>!cJUF>~_s(ZG%P-sZ#r5iV+8|NqBU%MHe_aB@_AMZ@ur@P&4A2t#q z$RM%p!%Cp{yy^xdyBhq|ne$zD)B6XmvV{Mz+?dKNhY&=5|CM`}-EfD@t&fimX8-wS zH5ix}+IYDEyz1lg-k*0v6sghe{fu1c5#dJt4+ToJ~0Fc z>%d=j!?&mPq_Y3!#kw};uP$yrxLrSAu6Dc4onigR@7_fJ4e;jl>`Cbr_XJxRSjh2K zMX*t}yb!ZaE_cRi8flC+Tv(jTFt^WyqhGLg&AK|6%5~A#mDaJ;;G)6S8Cu(IWEjO& zSdM);&z(xEF~vUW+&2rGT+YXJ56n)jTE#GLNSkC5k`h-(dCpqYGb)4lstzO;5HLsvvebG+#{Vl!CpQNI*0}U<`!XX@ zVQF#!1_%J`1wSDM(aFw(5G<1DkySg@A@23YdV`vDR zj>Nh#-4J^p4Pf2aVTjhCakT3^0Q6BRYYZWW7?{CO??dgZ1g4ZGM`th z8)gph&%XKEFP>jBKvO$|P!kdJ;iQVe|MOSw|Lx^!V~qnqkp^iAaO2}ahae}A)GN5O~{@)W|>`}gL}9O0UILUi4GV~GT!;K$oujA<%|WY#g>uUrN4 z$u>N!tdN}dauXT4_`sRZd_cqi@_8Q`;D7nj;isG3+04xVzCNq|cG>R$dyxgm_vY30 zATJ^}V7@k=e%$-PRzrm2$~DGZ`1r{%JhrqMhX2p`?PeIv(aFoYn3l~SRG0?Z-jv!{ z${3fFV;?!)iSQ;ou|lWZvwyU5ufb zH0^rV4TFHRyKd-*1R2}9@|`zC5ztj`v)!$_p{ea^xkEy7Zru+}Q(Y~$^I5&!48{_J zYzN(%Nm?La<`l=C{EmHOYYj+t_fdlZbcO)H z522pcH|Ljczw^rT&HD0oHFW;L@#1Jve|){}qts;Pja#ob#@Vfp7NPUuYTdqkv>5#G z<>m5tIz6szZRo~_MsQYFyWTgI<={6y>{w8s@AzN5`^sk1^&o*IutelzLoe6UKl}7q zRlB>B=BDcnVh3nJ4iJ`soi)GSb{$ItaIa~8{PJCE;DhVUMHf1fU5sbOee?LR?fWnL z{wBmJA%XO}_Fuhv_i5XjAl=~C0H;I~!H#9(s_S+@Yn>$!Sa~*NZbSU=b~mZ*I)ov@ zMK^rW2V;$^=yC`G5CS8?W(en91fnUKX9NFiJ*?4K4)+P(ZkjJP-LohYXJ^*^!*;lg za%$aG2oIXtATNg@3jDKsN5>QQn_a-F$}+n$w-?{Ff^3M+&!yqR+@%7N$wCf5=D2U2 z7Go?;lfu^`s;TiOx{d#LEYmMPy=-b{ogChN|s}E-qm?OK!EpJxG zcMn&~%`gO4JLAmF&BjbD0u4imAjHtjCsB|?SgyBd?1Qt#Zrd4y*4pKIJD)bO?U|?# z(OPPU*o6=Q5ouD{5XBG;ft|r2GP5{GF>RD<5HJ#L`jC8i04Ydx#$*ggfGF>C0YFt5 z0`xIP7B5m;69R89F8}BczVqpaU;Oj$zx9jXKKpXj9Z%e&!}&>F|LWrA!Rfs3!dB#{ zt{qyjwFtPTH`)7aq#re1HpWT_(=bN4pyU0I&^U3wLd++FAX3Vdi zULQ?N8{)B})7pM`ySdX$9v;m<8+rru(sZ&4;cioVk%_gh9!#EXx{uZ!J3B{MIQw+H zyXt+%9DTHoHeFvELx{`4FJ{&G5NA*Yze19=tCV%Z1|LAmKh0nWgg4O$H^TD$b`R%s1hNcKpXW}q?Ycc!HwyVhOLcEAv zITu5?=j^M_t%l)KkQD&2)MN>vF?3|9^>Gs=hVK9T#%blu-`(zZU08wHL@>u^PjV9{ z+WqJhTzXhh#$EfoyrG<{FzKGySNb5p;x3YQ1yM0aZIVzETgs@e^)7D`r*lCg6SG## zWSa|0$3<~{P)Cp4GA7A$5`JX+W+)+8V>n1Y8CX5)O2Q46+LCvea}WbbvJ%B{NdzESAm@Uge4MlNAxYXuDNPUn=4>>oR#=!j&eGBE z;}7FJp;!|XE>!#R%s}L}m$Tzt2Hn$QdLj$d*(t?16n|<&;tZ|lk*ZLLx@wxxWCI%I zRo2+lVO0}3%a_V_dMPBAlf;`0fq;hW2oW+zLzUzMXAUL2b@$-xU`i+k5DcNSh#*m<>uhBKPy)Mo!#;8hLPmzruXYj#Y8rBehk=L4 ze&86bGlP%Dki-y0y3NK}7eW|fj3M^J5Q(gFeb+bBrrY_dabT@5$G|MSX}jgFU(6>} zWx4lV2<;FBxM!}N+jPUUv6S{k5!QV;IGEjRx){Y;>^+Cb&JeR0L!A#1q;hr$FV>i3u<>SSpci;He|NbXurw4OO4`#EY zS@X$@)nZy-Z~Bv|n>qKJoAtBx)>`|uS06URFs;m-jJ4+H&o7S_lXf?3hHyNoZnrHO zeEQ-x8iOqNj^9UD$hG$~L$6P&tFEsMy?Zo! zx*I;&4DTIIKI?`K$sC_NQrID3bXmKamqc5Y;1lON50gl zLCq6@B0Ty*i6|pENlF2V%R@PqWK93c7-LqZLvDM=L$VyqNn@9{ii+esq=eNJP%pj!ph=Zl!4sthK{xoz!fPfu_4LX2mxxHf zQVvT@mhH7M3uos>UOA}lPOHh$fwcy$<$f^M1RuB`Y~^B%1c1UI7>CF)RtJmFcDAuT z%H-r=^Wp|s$T{X{2z?*wgGuxO0iB^;*IPq~=;{iBuUuuUktpl+Zt#%_mz!3E+ir*e zNEAXamAzeemcUE&fH6d2IXPNbN9*0t^|7k#s`WSRV2qW>#^i-20IZ!6fe@t+JgHpI z41&S}goBSr!~h1#rtBBObayRL>qBJ>0D1rfusK-P7(hgDk5A zSgEe9n=@w?)`hmKmXwKj^VoX$T+F&3qQ<^nO0^qNaT2D z);O|cC^A3WwSV`;`M>!7dtEy`-?b~|frU*7C!HW;U_! zolXD#Vs%(mcPqCM{<0ljKb)iB7j0h~G-QCFsqOQ&1p!a^>27z!{B+WyvyL0##Sl)*vRZ0jZmzTb7>zNSd6E}sP;8@zO!&>0Z|Fb)#Vxx z#ymNJ(G|qBI*1~KU;!P11DH8;Nc{Hc?9R!dJ(!uuhM-^V%zO%wF-;1=Cq^Umo`a7u z0+JX6k;%b)cXeY30kN9ZVLPCK$bf;n<(de0+wJ`D2qF)?7Zx<+J&Q=+4?tk8BTIcq zL9WhP?7LowqQGow331(p=Qrz!D1_VHu--c za7)CITjtu3LyQ7KSPShUo0B`QNXbk@EIp=@L)i(M_B$+H1TR7ZU4)Ce%|$AP&e~Q? z#-+K}#3WW#=PP5>+-U6D+oXjIc;Zo8rel-7A?^IBAo9<_iYBNHkU3qSWb8XLG zQ)3@RN=Z`FeGpxqQ-NGQV{)5W>VED+QQPj6WG$Ke9NX}WqvzO{d~k`1G>XVtmDi|w z!^y@7wW&|JQ!!-xRtF9NDMsK}!oV_m012r8)d-d`fyy3#`t{7|6jXI&y5}XvFqc#$ zW60$=PF(>JXTdHey&Xp`(GN~J{Dy)lz zo=}QG*JLun%xV}z%uEP|u(8H4JA{Qb_l&%D|KQ+w(O)gA+CgMHYrr`U9Qz=`wlYFC z31~P(6b576e99rlu17*K*49=SK%xkmNj-F}h=8%yS|Zx6wrp{T(zb0go2=F?T8t5p za2TR%DrVkp`|07rSz2%V>8#$haXOoH{ZLP;x~f88S5-bT8H~)!O-}}5l+MSdsrtcl z`hp?=u|Q70NZ7^5EC!K*4H5$opdlnOLkLJ85zxkYndXdIf6X)(OCXJ)c%f))vi$uVKsyeLg7u$9_ z1Y_)k$b$LS!?T}0z5ds^GIGY6HscRKrvFD}lv(aoz*FV~1vS9Xf< zqgNg*x9vJ`L|J$J`w!1vy?^?P^Q+h9GmN3C?DK8E3h|wzc|Z6iFbn*0wfk`J2GAKB z0T94P5o_BXHa-8{`Q~yPA1tPab&Wukv6krFgV{?*&9LjI7Qa~U{*Q0o?P74~X2v>O z1?HK^KfQbWv)f$@5+rW7{VK+dj|*o9=0l{b;8!3OntOtu1iuQgv9<-iZ|Egg|8%q4 z8XSE5{?X#|p+9!jQC)x3b#FLw49~ppLg)c2#GS|r#2g$y)aG{-Q;}{QX;5tL+OWJ1 zdn2%R!081WqldJ30p{)o3ec?;ILinW-v*Od$1Kp({gpEC=D#E50~9@4F`q0pAgK|~ z1R6k0D}^~cwyb0CEv%~^PKz?g=n#_k(MfIo5wqd6E*}C2tCBFm&645-$rxP~(KAWE zI_6*-olCMNJ1@%tVz!9_K{VQrkHLVLV~J^WQ2_wVo1I0ep;tp#>Bh{>r!S%XW;q@J z#H0w5(L_b`--^F>f~1TFYUlc$DMUcY^HT}9$@p^a>?I%4I$x!5NEwYXsEj4qJE;iu z3WZ4d0Ol;0%GOwLA2PXZwVGpWBIDzbqb4ZzHrslhw7dHrZ|JaHA`yPhdbe zwmlgngc#$n?PM~M7{l@woQr_Y*>Jl-OQv?~>(z8V-K}<$#nktMsodszg#?jf3~}f> z5D3ECZkWyL-L@qJv3PO4n$0I+2yWtTSGy=Mow}j(y$_37({B4_I%&J!5JF%}Sl4dX z4qb>5pzHm7Ru4l+=&&JNOsWv2_dW@F3I_m)4A{pQ1;~&OIfD(e9X2`VLX4J76cNBE zLWBk}gg}&Zde)8XDSN_Q|C?$-tKytOlsy|y}0`8FE3v@oDsk}#P#5ReX%@i-0O$) zrbHHrj^Qm=5y~?1A098x`(Y56SkroM zj!z$}u`=&f+uUsGPN2T!pyG!;)Hn+>umlEW>$0N$C`O&yWXfLWTXyquW3J&wfVxdx zEcMDZUXq3Bc4F>lrN{~N-3A>EAXhHpJ?(AT)h;U!<%6l9PYfBtBHAmhJ(f>g&Wusf z3rZ8}XMp11oX#yRCVN-N8K4YQ$|qkO?WF zBKjrr=>^604T|z`bk$NISTdNb$+U)SaN@YsC%79Gf4|Did zD&H~2bOT9~1v0pt{(r6S0HT44Ww{g#A;&b`r|hDHsFK7;h=#B>8 zaxgQ_fM@9jGjkGA`o7z?LP*xthX-VUyCH1`xVrZ3U>t(L_G)Py_S>F}>^7~dT=X7A zf{#Ejn@vLq?J!v9tTFv=pxW++04(IJB?BNF*&2)hT_3#nLtsFfHg37u4I%a+cwtL+ z)Ak}v7B}rMFq1I?vKzcXY6m}rI7IFvTVo~@x9z+klVG^0D5M2skO)GI&RP~o)}tB3 z0O(^#8%8FdpqQ>F5eZ^o77=48vLIpK4UPAI@UQ;hKmO(4t%o2;4e`9G=Cg)5T0}-O zZK~__wgY_maMlhH33r|U`b%f)-e0ucw(TaBC6HzBpAYTY$Fs`an>LPd7yNZMSVOZ2 zfpG5dde>g`zOv?gz5CPmUU~ce(O-S?f(b+9R^rpn-?e7$=r7K%FNVPxI=1eE>&+Wy zM?=pBOl9q|kLSDI6G-q3GO!5X1n|9+X=D+A###>k-MiDjygPU~Y!mp(46`E2OVIU0nw ztLpcrP0#*$jPKPG@SdFc&9;5cabeA&bzl0S&Ayaij?W(F7B4MOFUMF*qBv_X@t0h4HhoC2N+gVXIth`$yq&MYXH0=R z`hR4M2&3e>uWBJli;jjU+lS(qEMe2epyqxPLTz9|G(mYdbq{9sWL6;yvYZj>BA_wGSqU+EHU{0KicxIsAOZpgju^pCs^Ehd+xNX7wf)fb!4RS$ zGL8@!z*_4FdmjZP0$5}EVMr6`q^Z{JK$d(r0OQ7o-4H~ek1={i;-+$&&b!JE{SaBu zn83^|&JYU3$SFNm0MObPSpXtu%2%yf0SSl5C~S@K%pvDk=aDcG9juK*h!F`4n#hcR z%uLSSTrOXKc(=KC@(;iGcpLe8)!jWg*z9&9bays?{pHjD@T+J4{U3ezi;J7n+2r|l z_v%Y$jirA$zrG25ZQbdlzTNu9&`U>?%WeOSqvQ7`v&}G^Z?}uu%^UY@+qsncMtEk8 zAboJVZY8d^@p{$S$^tVJ?uKyORG+N3&%6G3Qnx-dmD>c_29AMu0>UtF>JHcwVF>@H zckZ8WI*$0w`K%fEomY;}H{ER?T0dO${@2%AFZ{K)qv)XbH~QDF+PthBCk!V z!2ZLwH`W?~iKC6k2ixu{FcWT>w<2O`N-#sZ5P0tS$BXHGG9LzSMUIRU;l6AC^rgFh zd2@RY%`0{NdEX1!hG<~@J0mfv+rpC{JVs!Kv9a}R@3`ucJnBvGZXE68I>0qMV zw+fItK5G>c1rv(YK^faoID%!F1T-=j#%yvJWp{tJebv$vmwSI#>iqF)z%>yk+|IrXsD@OI|L%E zD;GUS03Ra}r6uon@C@Q8Pc$(y3!|YJ*%;G$@0=sRAw*+oh_N!JBJv?jDn~@U_xXMy zSTbAh4G|GE&P}Y{M7e$X{7?SiJ5OKSazDI$x;VdF9W?H!sR8k~UtYie_|7L^T!8Sl z54+g6Ls+%l_27F3=WJ;G>C{C-pWN)eb$Z$W{^EA|qTg+hE3^w|1L8|fZOL4$y7%rL zS~BG2zx&R+pWm*3eY-oJH>4*DO^|jgL(|%Y?C(j3t2rTk;-JtOE z7;lLF;(E0roS^w_*IhGLXlpbT$eR;qg^!%=KrVeWhbJ%Rlp+nR9oaiI+|j8y!?J3! zR{EBtnaj+f_J#_x=65kkjWx5GaMbga!TKvju)O{1#+e)2@=G}_5b7ET>1JwHpq@S?N z?D~1Locp)KTg+w(u zctwy9Id3_p-9jR0oS70$49={%KXqplGpigK<0j5;dy(k-o_&CB5RPUtv2|rCM*zdR z14pj0#4&h3^uDTWh!Ig@8$yf-?E4`G#>gSEs|*4WONapheLvK*$$Gs7XF`l!+q-GA z+_v+BSsZ+2EfFzu6lnW?KB>vk^}4l&UfgV~bCn@$X|?U^%H3?+b?@uS0OGa_U@)-! z|H=BVCfSlCO%PLJW`{Tlayd($MZ0KMbF$sjy}L6D>;NN>6+rNe7wjLy1KuEj0E4B2 znVufZY?G?$s;;c$d2e1C)X8CHriuqMJ08_2DapJUHzM5qxF0puPe0;)6a+RVNECrF zh+!Iiteg?il(|4ef`DnSAW%CKq97s}icyI$h*)a_z$6kCjB`N*iM%8{L=2ONdI1Cw z(A^Z@YK?u<4|m&kHw2@&v}Re`hUx7^ zgOiR?u2qTg*><-!+LOi&<9N4mn`ydJ+4ql^|J|Fbg|iyvWk2jBvyEFj*=M&vlQaX8{h*a7s>qrDAfMFoQCm0TXGF@E}O~PcHt} z5+fm4Lnu-sE(wn-b7JUr>sGgp*l^hOK!ApgwJ|L%n_8wxCHig*{XmsNjX|IvI}{~r zropf7o=%${K)l4RpU_g@4NY6C%6{8(Z7;93&8qFUdv2@C>s`vnO_r!}niO>kA(il5 zKRIU+!PV8K8^$TDmd(|sS7Yj?-t_~j)^#N!Nv^SK+(kbv+sY8_N6(CkI{Ju+Y|P+2 z6E@a(AESyfhA>M}gQy|$AtDhYM$rHgMH$gqJBAQu{cBF80A%tK5-M$wk|7o%3el@q zm*0JI@Ag}FfBpH>JFDe>^nLI_RRAvf;qiL8aMT6;-s3wjFLw8Dt*pT>ull={yVtgs zdBOa3*T3j?i^?vEZ?Ber{#)<=^wa02wYy<#n3(WQH$;WE?w&lm?rd9K^;6@x_a0Ea z=+~R=|KYpuUR>-WQeBxxi~0!7kO-PWPj~w#_fH1YPv3OEf9I$LJ@fvv&hG&#Vga2b z0>H+5qw-GU$kp2zHbF%7uEX!P^==B^SuIw?pAFObpMmeq3~c2h*aNhAUe!N0vQug7qKTroc~ ze8R{_r;n4CPeoF{GjEF09V+>q$})ulD9haYz)WL+8B#nH_grcVExOPrvWmLk4(Xw= z2!cW2&iqj@0qm3JTzd}MR z&tQIy^CE7hekA+1w6~N(3(Q4IZ@hk;ZIl6iv%Ad4Q10Ke@5f=c36R)h`TR*_iL7#- z+t{)~EcEbHku^JaXa>lOZ5|fs&|zCZL<#d{Vj^ZTiL#c;cUlY_ILie?Vj?X}4=mZ0 zsW+cpSTlA-#ks$XDOi{}2?{GmwnB-uEmo*A-#JUe6G)!0aLEp(geuusMumx}e6ulZ z6dYsCbnMKD<2ws?(pIi=e%ra)g%HVDAToZ5I87mf_u%X_POt)lA697ekQR3yUU$nJ~^iu>TMjoaqr~uL!q^TSLj6N7*LJ(16 zGR6!(5OGjJ$h&DI3L=TCB)J3&0H%E)8&2=5ltVq{MF$`OGxx!dF%kkAd-dY-FMjVk zAH8^G7R}|=j*W>?c0Qi8H5uM?{=@r6KYMY$tjul<6{BhEcaD~4UH8k)?$y|dq4#ee z-M9A1>H4hqpPXGghL>G$+184iDC<@IJKucgKYo5@3_E~v3SIO_cs2UBZ>>K#IeKxi zyNt0H8Dq2&{;PK$f47U(ReBk)!`w2z={MP#E6h7)FQ2On4d+sLz@KG(%pWQjx zMBNCk4F}}UhLMj>AJ6;f;?o;j+iq%s)bEG-)aXS15-%l z0UR#OJVZcZqNGF1&2XWw<%U&thA@svVb9^?bEB&>El`r759A<3I5eJGe))W` zWCdOxmdna7H>Z+QMRH}4EE1nPQoy`)ohO4L)}T^l3THTv7XmuFu9a~hlbyAoOf!wG z>?M!<_51As*7 z`IK3s*zD!uEdMVx-6>9`py!xF^am%YE;)$)2`#Df~eEJ zYu9ULBg2SPU z{pdgb?BldOySrL^_}1NLuP(jlaZ?>Hn~%=UkzfjPx?KF|y@y|Y^?En?FZcVlHpa)5 ztB?S_AizGvVG8TYjUhIculuoeb#2VWwnwJjFa^-$qv8zTU9_i1%TJ!4AGOV!VPJw~ zRejaiYg)eDkewm_~pY1PqO1c(iCV$cb}a;GKoLTk}ehm|u4B>9%7-j&VN> ze}3oW%WnGjXZuI3yY^wDa@)ApnPZlRmHVjojWbt6xDdJ9G%VW1_|Q3385O=BM$d?< zJ0F~}b0 z^3+@iZ8=Ju3c{g)bRZENAb^9Bg-Xs$NkD8-Lno#N0jfqRAY|_NL`uYz9H_o?g@u9_ zW1$0ph9$&I?oR2*rT@)mJ2B^A#(dBr;~~a?2n)x2 z2BvP9kru8_f}~SNjE8q3AAZT3{{X2JQ8{J(cXk5c!V*s+Z$_-GVbBI)Y4F(5sWp%9 z9kB$z?Ok1wDp^iPSscAt)_xc<#$mUo2(HtEX5!c8Zp#0f$~5gSgZzKW(x7I+^#u)Xn zGIXnQDEN=BFYm0H-iNDce7kL)td^T$y1S}>`uxpjudjb_@79;wy%3+eY82i1DS$p` z?6KpgS6lFM+nP1A6S(c%tA4m!xo-5mNFSz8&-!&+UkuYWMj`T1V+ijqnq5#JY6<`N z_R;exydK8NnCr>cM0?e%AeLxknp7=N6fp!1)3uL^^6l0!$nURe4B=eV5L5z~s5w2aF!N@;wUkOF0Ln~r&zJpI zRf@~BC~7Q^W7zgX=e;0WM3W=ekPUZZ7@~v_ zBC54^Gfb0ch++sWQ#y)7fC-s_h-32N2Sf$K#!F6K20&{}h$^7SEK%8D6cxZ2MFEvi zM3@LPif)|CSQ54b!%!<*5AG3oxlx2%_S{btwgf*f1h)-Fb!f)G>h!)fcDcDs|K^}XZ8^Q*m= z*!jsI-LGpujllGB)329pDkhiHBtUl;i|xMqKmQlMy?K4{qdT|Xxp(?MeERj$xjUBo zNm^^7#uweVa^}e7d8&xbmO0(dQnbY?Wan5`JG-Rx?Tzm>| z3;`MF5-pO>bYv;aV@i_q6m04MDnaoBNaYd>^pAjylyg>c?s&eK}KIQns#>P0(_ zgR$&=7)LK6U{HW6XM&Gt&<7O|b_Ns$z%ViMY-iK^U=1hxz^naa4F^?3G(_G9KoWo` zV421cdmoY=STa^lXw(=Z07j9Si7j6{@`DM&eI6k@>eCHz@Qq@UiAH&+(U!Glkb#c`+^{efEQmG8S+V_*<@uEIqVLzT6HT)f##+Os#WXM3{G8(V#Qz zMMd$ruDPI4OdHN;2%%GVuNeuX3%yH&3 z|1h76iH1DCz}Xz*Z~#E&s{nwCAh9#nu+7wy!c)mq5QJG%4h8H^hey(+10+W)hVnT` z>cny;Cay&~9~g2}ZM=a2dh!`@ODc(0JJ-L$SKOk~;$;`~>EI`GKk7te@A_6d= zWm1uv*`ijcD}!sx_nLZ9u`@`b7-QV-Mbs`D@ggw<9~1EE7M1v@2+XG64akhDIUyCYYlNwRRu;s zlTCUf004@X;Naq8Q#q5mo0I}>nSB&d4T(pDn5At*WD;v1mvw}Avm2*T!@a5ofU_}FXe{FWMI(UUefR#GZU5z)>qp0{6Sk)n zgXM;)HhgQ{UJk>5^@DHzhmXHf=PbYuyeeRX-@1MBpsmikK@8tr)|>q#gtawB^hx9X z@EZ^Q`pcKgx+(50d8)Xg{A z#kuzp35Z^e)3@rXCVV!9H8T<(Sv&5>Urqi_RY}l~r@+jw$8oRf5Z{cUa_+Jp`1s_l z0vcrNdeNBr#~8yr`4$GUmaok2cX=I{bO>2Zrvfm0y8&hsVLVLM1=C2HF_HB9^Fj0`*;wc3DQR*m?eB7T}k zB}_5DQp4S*GtNfuHA>fy{WOUvp=wk#JPy98EE>a>#wnsT-utN14G|EOaNCcfaJ+7j zh!w%$Za>tuo4kzUgoe(pJFa;2af~9U3fKpoBm$x{3<79y7-J&OurXtdjwxjOSyjXk z)fk?9K&B{~E@>2D!>WKxgZGl$zol^V0VtoNz(f*-2vsyC&k~Yg^3TheaS|h0bNS}# z&wlvLO_YEAvyb{9fBIV=M9>!(yFYyY@h5N2*G*#x$q+|s=Q_ z6llrZG}XzX{l~NO{n_T;$?<*+-??)lo&WI8sUJgA*{ji?Humq{T}2*jrMp^G61 zT#Qrj@?_mUyX=o@_w{}VK|VaL|L$^YY6GH!*ZWoV>)qZOdZ(`b;^EzY`{v@kWorN@ zz+2Ydw*3CG{x@I0ez0!O`ssBDN6u8hR@7AHYM7RWM%5SN^m6h8!wKW}P8KiwiHr$R zEZ_&r#h1Hbhf1nn4C5N<(W3sF&E66(Ri>yB@ZO?68$*Nk>nQ>=h(rKl9HQKEZa+Jz3g`-E6lDxLJB()LhvzWP#bAK7k{WZe}`|cw}H% zh_d}gkw>QkMDs@`7oc)L+-M$W-~|f)k~w_kTq#7h!%8vll=4v`P*8>&YrPC~Bv=`X zND}5fUs2hW6QURs(;?F&lb5rCIg=PvZ=OSGomSkD3e29qvj`fC#j8SzE68VO`PO6y zT>PpM=1~N`FfU_qj#o!R>}<^`;^fdBA2=|Cq?yx_ucK(@UJ!8}h03Ir-i0JfDZG)a zts$c9Ra{&SXTA?+gXYW~EL#8U09B%M@;uIo4W0d3m5bWmP=acKlOt`~5JGleVnPE@ zGc^VqfR^yIac{qMSG$1>HK#}Y)lLD%{oWm|Lkue7y@13x1qBraQSCQ75Q$SjP#=P8 z>gc1Z-59l9uVV1X6rxgPjX_o!rm3IA)HM;s7{{QduBLu;N$8;(ePn`W(bkoNC;(^; z_5HXRrq@@yNB2&4-8898Ap%hp5yM36qcpV>zz~%QebgZ8gH+CnCgw$M56Wp>c0EKs zMn$yD0+7ZRTiFm|!s}#awIkW5bc@-oheXo7(DV~AJVKmOex z{KFT|9-OTI_OoYa`;H7>57Y6=ZGHIs;?fg>=no#=db1x0d5HS-^4e5(7a|(78K#rd z)tjsB*H_(gy;|4qZ0M(M{Nba!8y}u-I|V(iDjV_6vfWBl5CZ6ZyuD}$WgNX_e$`K% zsx{b!`0jeqfS!l=sBRuE+Bc(LRqot-QFzcU{{6+}wc;%#2C&v%iazV7N%SoEHSvkF zfLI{`(V3sFCqII8F&tZ489I+LMfroH)ej$>{_OSUR@0CG81wnA--1w-Ke=^s9lT@Q z`|x2~?Y+FWa{CzHL_HrR0xk_3)Trt#O(HEK$H>g-Ug6_gPi|~yXfDvD4bA1OP-P3x zV0N=S5H3oPN}ilc!NO!}k&AEY?G{&33G~7dcP>U6=j@6Gg?iBBb1*H7U4{($TOgo zR~hrBIYG|}wq_;}0TGdycAJ!B<)VfORTz*FD2Cc_4O$}{S$ufBpefYOO#40#qirg~ zW*jEFtm8P5F^C{Ry4}uCo~@N>0w8LftIYO#w>WB}53Y71;8yk3*|lZtcLO<7HTCX# zuRzOXt3Dtigcw1YbieHoU>Lo19AoUpV32^|&FhVGYSp-5 zjA+ejQD5!)Su7#ct1?FH7Bk6>tBDdS}lM7!zaJ| z>cy*VR}pi?|LMi6jrX^jx@EeK@_f7h@Obsn)$Vs5oh}{US(}E z>qT`vhNqkUy<11!7+!Y+JJX3yLGQKo7u&%Zy5sD#!8gv`arXJP|I7QQpI+@6XOZY^ z3IX79oX-5%65Oq;K6oG&rH4)Zbl?5(X!YB-j{YwnKhvt(kD;=rQd}ACW9*{<(#vsr zQdh^!3(K#)o(-WxBMJ-4OT%051L2^$0K_Ox^})hD>&LY*UX+hd-_kriW2R5sz)9z- zmlkEScjQc$Mkv8WxvCt@)$-w5c7;W;J(HtAOBhu_gC)^C4p?(>zRygHLid@w+Ke*K zU0sH&B~5jZpEDF*K^DH`nV_g?O8YNb;-Kd4Fa3KyY|2Y1Jv9gx>;9q|FGip96`Hql znKn1ayBx-pY^sR`$H+Z?{)NSSJ2mM^SCV;#Wd#WHH9YX(FbURXf2;Dgfq9x5SJBlOL$9V~C&{6r+aC`36w@7+qtpH@i5D zf&fkR)zjB)?RNdRA4XHV{cdcI7FXN7Yiso}dXI*Y$oF0h^xkWXKs5Ncs2oJS+V+ZA z)z#TmXDb_`sE<*hvJ8qrHAHDGt6)>v(FbDkF*4KWgE2-V5}>hWKX?T+OhbqOAOccC zf3c8Ji#M5uviDX1!mC$DLAaXTK&-eY_hj*6Ew;$a4_|^GtH~#Kh_v_02 z?N?8|!ktC)c-=nV_8*+CS8UG4(TlFFd9SUvAg@Q?d3dw!pR`RsOkZ4&Z@TH*ch*s2 zH~8AJRRk5a*o`r&E}4wTKFaOJ9TD$hn4(+`Q|rt$c`%5gQ&_s)eNH?Q*arh!IjZ0TSVr@1uF&DCt=k$p{zD^occ za;n`8FY6o5o_V4__?o63wm=Us(+kTm0Xj#n0L-2qI0u)^%A3OwFh_0ajL`thDcXlo z01kt~!Tj&WPH%?6i=;83Ooe_9poNc7erY1KVS&C?RK>Ad)U9P(f>bhaOGrdqn(^cp zh~-G4#HLapB9rSZF1s0=MW)X!HY`$PQe*bqgbXGll@k)rX^IuF$89gF?*~8iPjh&V`f7OSYx6_ zQ3WD{oNcfW3DFo9fEc4S9HXR1G|VB!s7Tx0U;gmJPu`sW>h;wRKX~$wPhVPNzrNc1 z(YM||Ve{a4HT2`N{jdw8B@&`Xb^AA8zkCy7>)g`t7iX7W?7OCNPO-IgzVDo|L-6am zu8fg@mm%(A`2Ovq=iBby(c-JKy(ZgP*3UQHi(!0g(TGH^QX`I&M1)(8?z#Grf%n(# z)6KXG0SsM>c!VRv3unH(+7C=`d{_YzqpG~cE&z<)|L@*@^w;Nae!AHa;^+lX@3i&0 zvM>7)iC4_W&YbmAMfjwxpO0Pv7uEp4(waMM?PFY2whNH}UJt>GI?H2>1~DN{d(@rx zr&To&j{?8w_rKj#ygq%LyDU{uNu44g5s}1D8rU?UXHIUij?3)V>>ydJ*{N*2v|tTD zpf?=EHRlJGvU(sM%zuleQJoixnoCikPs}UIf{5SjNi$B89y1fqGFN!sBb6>UeIbXn z>fE!Jr=Cy{I$+U~jWiE&A&@IIW{zE|x>V9&XYC5&JPE)Yl2X77Or7Il4VX(Rly)+K zFmUsDo6oQr3o5J%D1g9Wv#F{~T-&C0HE1RprY+o!rZ%$>=ks7jmeSxtOnKrdHhWmG zh~(fpvvX!kJOxOqYn)5}jWuEdI+EQAXU98&IlmDOW+R}*p()Qpc~GIlOIDM*XaLIN z7BT@N5CW?q*z5)}6r%bFqpDZ+fMXC8Z5p>9{JtM6XMB)^^8^*|LuHL&N}nVyp4w?!kc8Yi zuhuDvULoPgXUA6#pY0>9=7D4#^bZAu8-;}?0j4|)tg;EL>W=8`^i*x^71F| z-23YCy0LZvc-fEl7VWw=&o6fW{KF?d-)>$H;q|t=Rxr+02tWjix1H4(|NPGBi+;4C ze|l&2>E-5D(^Lp=E!*>5|BKD`ERO%>>8*t^Uk=^3m&+XxDZW!xoygTNE&(n>a6|~W ziIPkbHbY+#j|vuv0eU~Z?!&-_P$i0Gz7FA(;FdL)lmE-r;)$^z`EU&|Dn?K~x%C8# z+fG)ZARFRG= zC4}GwKw^lf#LlKE&kuu&RE?7u!B`+5#(uX$Yg7bO2%*~#lb_n7rA*QL0EP^rM46^Z z5SzN%?DtA!tm%E|rs;gs3t&}Oaf)PV7()uMCWhL|H~hII1;|vbnhH3 zLyY@Dl;UO#6XT2P?YB=)F1P)&>m3>1#n2jVh2LH-PL}om>sOyqZDY{4mdm46^TnHM zC5(oD`SrzvqvesaChC*BNB_g;&yQPozp1YG`}dDl{pjC5S$%P_b(Y5bK_Tm1rX_4+PnzM13B3PiOLq8IHz90VN_Q@{BzuIkI z?)oLu2m(TX@Zio@+ughC)xY}AJOA@nuf$mfK_9<+Qa4D~ilDlUF(L+uQ`i63-*_ZZ zucmP1%qIAs9M`{d+Wy_eUWndaHKPxc#JkS!f)C6N`9kze;K-O$>&{}}qm#!oY2i0- z{DY2|a{hE7Mifpm9oAFDJfZZ|Wr(F@Ep{lXss%hL6~92KP-Lew%NMe>sTL4g=j^L; zgcQwOUg#CqX}qBo#7qu=Lh-&~Qjzr?^G-6g9fiG@a`|rl?L4HH5}H8_L`b$q#rRYt zmp%Z5gGVctBwWm7z4Z4=1qKGl4soSFFQ*9&Tc^h@C;^}~3~)V+yAXiF zN9UIv#O0!TdcAAdjHAEp%%X9xcB872GgfiZxao!pa3|u}R0NM#?LO)lVnsBJa=K`1 zgMEnSyMZfvJ<0t=a~;O(AcOZmK3YE?{fO2|eCMe7dOJkXx7UkbUucHr!E=dD@+) z?<@#8;1@6#o5MkolRk>(I;CQ3>1XFsn7cUw&>VA>!0UOg&oHuR_EFBk;i`Fsn6=l3 zUsz6}vTP|zWIepzxo0n{NX&k7TF4xrhyrt$Kj#e0uX7$?a!E~~hGW~-3r3>{EnoS4 zbGTwrCxI$4LxCW)&=zwNYjV_>mk1ezNIh`!JS$R^^3KjtVZ}TolWGW(=TTlQ7PrHk zGBf}7bm8)rwXh2_Wj)=}0O>)QIr#^0ZlY8CE-|4&sF*EjMOY(nl-mn;|74AmZ(0|p zs4>P6Vgy8pQ?M>2JyA#yz7HZ%05!(Zdt|cC#3-h!f)8G_YU|C#wE(uqi|%q?FDoHZ zulsH=jotPm5xLrpUWmy@*>+>wR8`}G7Z1Aa#-l}Zd9^=2US4iHMDz-%(2JZNEw|e) z5ROAIhS;!m*870WhG@}N!{{xWgiWSrsQ|=em}68$2%?A>MSOZ^%i*4;xCj8uF-jCc z;xWdkLWXC58OH1xS$Lj0)GmA62y>v}}L>^yJ~9QP5ZW z0RdOFohE4j!s^o?h*Gfpoyd>xt>0a^UvK@xqc%p_hv_Or57_tqxN>#Wg=MzpbvKOwguqe%n|B{R-FB68 zs(M^kug7sjJ*uniFj!~0puc#tvDWN;*!uXBTSwnpFOASg+ueWv^~HdAF^zwIvUq>d z{JZnbJF6utOcLI2tSD#MqB??3-0a_e8Z&)^$ zI#B*drOF;!Zp_trw!W{LJUw<=hzN4|aMfA{uXK-Dd;8-{`Q*zA=p;Gj6IgA)Em7ZY}ao?97Jw8M+j&8 zK^eO-{N!}`q;Xd1-DN}0yxR63E*D?yyBFQWOc&!|4KnaAw*A^xZ(DOdO|1C-YS9IG z;@l5c?cZH*kCxTT!8fWuSu~aEcb2si<@M>~ndMM8XMAYL64-RZ6rVDLK}D~C^ZM>! z>sE*aP#Uq^jF$T}mAqomk)iKYF%L_+43MNys9YM;onFvwm=}imxgNO81rOImKfsKK z5FwyQRFU!oCAa_OfSD--<&&1mXclT8tf-M^C0@qgp}1q0);7EHm!Tq?i)5uG{swiV zbk?b*mZHbWCs~$#7yd)}bKz-})?9&!>#DA7jYuMr*Pg0#kaPKI$X;{v?2^3PHN(nj z0hkm+Q37S#zkIZU0;fcRau8zfwX)hTM~5XRfV|41f_|4lBz;gMskDWd6HJiVn7(oD) zFriv&hjFrw_udDQ$p~^QMwNbvaX!@wTs-OndU-rWm;F%A6bn7v|Z}5xZ21#s2Gck8GnP&?Of}L`GBBjdL~S6=mUvlvAXLFY^;5Iz&q9%_o!p zo&ixs382WA(h(xrRuE?mAu?uuUh)+&bL3#5k>t~*3}R)+Apl^AL4lYxW_YbcWkI19 zDk6fYFu_# z3k48AcvMwK*PE`bZH&RP>AL|*hRG`+6A;tIethe}$>p}2qWBQdaG0XT2!{GGAgD3y zqmDjIAplYBj6`9=E(AoZn8(Q{|3S&i8c38ZB#I%X3C#PD7URr3D--|}2t-5@;x7t_ zLBucz5HCW6Q3Vl=G3k!=ArcZZCGDZLCLIGRDv30W$)Fe{{SO!~f=| z|L9!x)~dSY+~0on=DqtTC(c+)Z+88~wrApi&*r%ly+FHt@`J^@j(d~v;HLwzJ5 z`03MUfGh%2jJI8N<-?$A4Q-+)LSPJ2T($L^VQ8Eiqu#5`W|+X5=wpa+Gs&~vB!u4k z${8>6PE+4ov_Cz+K3%o7F`w>w)cE^%PtUfyeUz5)C%2ZrJlpi5A9ek&FE_W_Is|>x z)|P2EjrSY-w2!^YPVj_iVHgaJQ96xb?5LFKvEzDFDeJw5gZGMnps+W1(@Uh7>a!jphTg#G7a@ z8b$z!nNS)Z@>5W)?a`2^}Dx@jfx_k`|0aZNU&!1gePsKK^tdV zGREKw33nSei9VnFYMa3dsCenLI;fDw+>B%>YG4 zk3c~*|KT9KOx=1q$%F{!ajo>!sPhX~MNmqX8fD|ksHWgNGk>Fmz-F;O6EP%t8!$m* zO~q_b8cRo&SpeK#)kn3(5Zl)IZeq*ES&5N|HF1Y9lTXrqQ&jxqZDohix9g*ByCr~e zmxh5-r1s$F|A4rnd0jPVpP#HeyuHK&9P z0F5GsQ?`~+-Y;S9hBUKrtTR5uK|r_LzxdI&KYsD@%ggN-S6u-0idE}W;A*?yZ+0iA z>uwkuYas|%wCnxd+pE9+Sfa}KY`|0_vyR%#xQKL{Z``}&WCXx3})>9XD(XXui z&i&I*o}T@y@4xr?cKd((*~fqR{*!SGKRv&^zih$~K5X3YA20U|-53osA)Z=?4nMu# zi|C)ceectY?GW*-AD$1>t;(G3hpMp+;S}OOZo7tU6j&NwxAnH0*iZoc;<`t+K)RXY zZQ!4rE?h|U6-&;46$N)+reb8VHsCv}}!M}WT`XA0V=RkGkV$h2*ES%ko zzV1D*j~|tOIolVO?kOL(nLbe{(dqtF&EeLwJA0HkC*!5*R|-~GehO}A=Vo&yD5T_! z1DD(rD7_NQ2S!oF6}?oUtlfa1Z}1y1!x%He1K~H?#&jEEj!!K$BK6Oik(QtJoS8ct zNoVLbyYH85zeDJD{?~j&;Y=~q{8DK?_wt#MtEpxQ*!-+AMm{h3so+I3v4Rpoq7>%?-y${Z3bR`@&W2Ht zjX-AzCGRV4@bjPuN+D(S&h8@V$Tv(NS#+8|Z^;SSCMuLGZ&=Rc3#2MTk_n5S2hfjJ7ht3o~83el3KeFBH~QPF#rf67IU6RwT-55thj{yV zvF(OYbX7ZRc<2KWS?4zWczm?ncVo?{sFRQB3cJenQ7}<62@SIsA*84pM28T8NI}OC z*swJ`c&`bYj*_Ty0$ETNrBIHF(Fzl#SU5~*2_YcG^tq+7d`kCHNz3HqU!}@SF-9mb zHvlLIfSP23hrrC6UH4>Bz5C#H^#0b#(N#B8Ohd%|FieDxSF0(6E`(KOAb|LAvTiSX zzYn3Q9Hgvrf=G0yu77a2-k2Gs&p z*0h!l;=@IK+PY~99~>>8_v52>5xiK(3u1$K+`0vteS~kHxCgcQ;(B`_x+hw)T~_Y- zG@TlAJ^C@mZ?2jLb#>uoZKx)geE51del-ao8ibRkZmkJHwqbg^w!AudoJtkkU^G*4 z?}aj&&V;etw?gJfrSU}%R!XX>sU~abj&rj$_q=(XHy^e|7&4R03de2kZA+e05xyLd zZYXOs%@h&Le7;1Kf&)cb=kLxCd+L|xGL%~&oxAA*hnEFhKALWnEx@#6RL~+?&agyT zj^m-o=1Z4BRm}_;l*5^n1mCH3mBNTh&ojF*s|X=guBz+S7&jMTl|l(GM^H(Y(u~n3 zOtP%p44cy7<^x&MNkd$g3Yvd5pPmFNS!tI3pAgQpz00Y!d63GQnj-Hx03PMYoS7#D zbWR<8HciEufQX1lA~E}W;W)*rw)iQjA&|LV!sC z2_OUydcNCNbwz|;#aUNd8>3jpVTecT#o3$ds;xzJnEY;<6x0$DQV3!Vf$A8-7$XpE zw|nbM-;afFh=^ba>$;jo4~YBGvo$e9XYAIGsqzL@WCQ_&Xp9LVV!2Q%ibTXLF*0*n z*AsJk*O)nkXqbIWuCN4%fxstMQcc#riL`@B)nM5|LdT%EDgwqt^vNo7P*pBFcS2+Z zw&rTn|MpvVhZukL>e5P!e~YBNmX&T_Hc?e?m>F-Fh_cTOIhtoPR&WFCSzMnvqR zs_Mh_;+HQkwj@ik_a5H;^!ckNw~t0Izj*O_J4`=*bpLAC-CNYl%Kh#;4`1$vTgzrM z`O9(o{o5zsy>tBYp?@}o4_C`Kn|=!#qf8=AWp5MRZ|hwQewviYgFb31hvqWqkMH05 ztFJHjf^RPu-58kJDQ;0|45w|iml%fd?s2>K)5`LvSDWRc6~-$szjJc**}j_qyD6MH z(-?lSCykjX@H`k%`P{=bmTHR4Ve2Ju5p3 zQd`8~1#2kx6ctgK9_mqXw*){0$;njNB_PG}9ipS?hC#5*^p1J4R2r_*G0*L7>FPB5 z-~Z-ekPJo?0P?1>wDZNwzYxHYl8feHhgf!^`KqKNEG@q?9q!<997~-o7h%?09-ejRq$cHi~22WyJ&Km_R5JvHa20+Y48M$0$T3+t2v+xDx;{nRLwQOzk8ew1V>LtxF$S^D5fcK9lMjWZx8F~|R5?RTag58> zc`x1MZEeSH$g9QCqcuYxx@l@x^*Bs>AEIagV8g=@nzmBKaSDsZDFUH}DUu;;?P}G& zx!N^NJ^9f4FotL?Pd*5!Ge#5~F{lO+Q8Y|RwJjpd%!WlWK&=YQCiCrb$3;;=MJj6% zpww;NOhH z95N#&|AjnD-%^ zPoo7mSu{(KeTa{n=5Aa4V%s4R6GMo%+L~8K4+&;Ya3&g;bkV|0FUjjVZw5;c%yOtj-(ARHuq`60(~$9!l+u*Xx0{!OXL)bKV3XmVC#| z!;w_=w4_s}o0-X|3WyRBsuW&~f(n-fW&)ZICkNmG_kgr2$TE5;bno(Fvrawz#d8m=6n`ff1R zn98aEs(A0oamo-2AyliSAHA(CnN+DTNQhC1(2pJssc2O@BkC%Sh|x#!(bl%x4O0k5 z$Ln7`eSWlFT7n>Itz9~!G1$t!zT7pHRir422;CTsC5(aqqp0_Oj3TNrY7&(UqYom3 zkKTKwB)rxrTHFLvQ5NTH2+^>~cMt*~1|ReGHN(VYiZy$>!)wbvhO`&a%yvhKVu?tz z3`w&%i&O-YI7k%{*_sd|A_s}7b4md>E~)%g0U&|AILogP_Wk&vao>3IV7J+Arw}Ce zDx1l_vuGc$7q7Ov>%o8f$^9-2AHBL>Ae}bVqST`eB0oBP&G1}=BwpFDc-^=|)fpT5`u1d(AHkJ<`+c(WgGEf%Eu*3kkHfA;2b z7yZ_IHXM9#hOcz`dg#Bt9=>^U`r{|J|MkaDE!jKE`eFztP4#NGzYcyc{`J&vV+cWB zj?*?u407a56m^yj@$K6Daz9jtrx^PXSJu=FE5!Gf^$m zD)aev_d^0#Zo+SlMN1CWjozyqe8UF?VlLLTnHI&PiELh@uf@ zT%^DMVK=amCl>eAqR)@r!$(2rNbwj*JU%K zZpHveQAC7@+q!A$mf2|9%;YVkCJea%$O+ZRJ$O0=OM>4GcPHYEYZPV$6j&xzzf{c+ z7*~PP5xG3=(ghblyPV}EWb45G$#nSC-AjrbgtB-{7fys}MyQfyQYNq%W<(UpzWa%a zpelwbjbF@ZGo{)5G{njo7G;%+sU~8TRb`CC2RH%$ddIt6PudT$MjkJYkO5lUN7 zL$0a%-Cm+7A{!dIVG7f(8$;5_h)z=&`#y@A%B9cN7-B$;V;H9pL7ig>stO*o>xO0H z81V9X&y^i~>;^vue|g>AI$lN4i|fu=Ubod0V+xvDv~>^xBxfl2&@^?$K_?%9FocNd7%mQFiS8y*EgJy{qM{H)tg(m~ePqKvMpY1mDMmmv z$f*iYN$;CGa zFTelf5mxl~&t9IcmuzTB{Qjfc|L>oFETYERg*7Vjbhis3Hnsiv>x<>04jS*;>f3it z_ulWPFoxiqeSW$9-rM&-etGss?>u_7>9DdA;@UCz_>+6LuealmAD-5V_iru#=EcRM zRkI!ZMFKmi4sLvU`2 zdS*ynkq%(`jdfr;J&A}6oAf4Ts3Wr-)4@Pm;*rYaRD1+~({V6;Zc>J!CK>=BArd2J z7t_oK=6bcc(mBWmQ%E7#^$_yS`UrMu`j6hxd;D?I+KU zSF1a0FE{&%^u@MkgZ&sLk*Am2wyJj`A6;+zDUibbRr6*T`xs+{2>5KXzwX9*QC;ju zV@+#p1$b*wopn<`>7PD1eYjpc-A`ePFZcWV>-NRAKeFbVC+)}E;dWcw7fB;}qh1$II&>-e1_-@@L&}VtAawCqoz* zKicn)E3>e+wPri|pPa0>-MFsJ0_mu7e01{Y0FOd}A)%lI8LY@h=-h#(4iWQymgaYU z{#gVFF(ynoVG%G>=yZm;XlDJUf{~l9vRc$kGCOpmph8&aXr;>J2h0t7Ax+P|pnwO* zXJROB9|a#MB1y<0hh@z;ziyZZ3M@d++ykahHRR|JJixB0x*$=erfH#)9B9ZY`SePZ zy95pZAQRWFuImNm`EtJIT9R0WDCzEyh`4k~SU5CgWv4kEAzvCQU!N~#TD~)8g=D@( zF=q!Cq6TDb$jn74xm1F}!PJk< zvgwBj4I{04T<~7^7ER ztrwFIU%k2XQCwAB?z=npPOq=_%ViA$uHxRu(MOBaOYlBMk?rVj9knDtj>G7^$`Ex3 zkqDV`TA-go03gH|1ptO9hD}TWxPl-C)nu73pfO~EnHA|z{PQ&xhCz|866TiE*$ zMUhEC87U>C1Qlc>=~obiD1emU3WmHHHb_ZCZYZ;@0bvR;KND4BO!oCJidmcP2&8@p zcO8H0!R??xz3k58mrEKE2wX4Wnff5u(WJZa6YF&CN&7Y-a7qa zH*CBPqyLk~Cm-B9`cGe9M?*nj7v%Z2J6cr>q=&2Kj^SftKHCog_-Yu|Y?M$5R?dlN z0Q%9%s#p2E8`j2ttD&O`UU;|)P$TmCOjLtJxS4 z70Jg;`N?8sQLez@Tq$`aP!q8=Qp zHFZ_<(sTZ9P)$Zx1?E!DX3?1~0@ z06OcVtbl5!7FGfO-xXjM5ybW0RbP7N=jibgrm<^dsSS_PwsM9H#~v`=T| zWKIhJgo)9JjM=zORhifrM#QK}fYz8q43olpO?v@K7+?Y|MI+(9n?AgK{9^Q-~Os(+!UB|MD7~zp8nHqsE>(cRhq%H~A#O~fljV`M zudn-;-EirnkD?0u(eEYHNFN+8f4$xBRYxBX^vm5afxhU*3h{1bE=He{!#=<6{`SSy z$+F%~J|GSn7KDA2OW#MqTeYhj_r+Cr;r)jz`=|Gh{^rfDah8apKvh*2{nSl?2!Xik zhWiUXAH$0=Od@=I>#fuXXM%y0QU$1}iK{ik4hL=kmL$_z0|ztp4G&_FGP7qrv}S#p z&gF9kzB5}Qqro?sS-HH5Xv!TgU#2mmNiq0p;n3WrV$>C$8t z2u1K<5v6%}r}@v>k2oR>*X`vAo1mTc7r9~-M)}#QS&bYQ|E87?{h3Ypm z|A2V@{==A(mNqkBqRA0~2>?a3Sco9b9CJhkVrD3rjYv5yJBvQ%=Sq~hSqh~?7r-n( zoJS})+}-(z$AWASb7r=bWCrBJ7BT&7?#ffoQv%y*=GD*LLbATp{8pnX6EYJjp-Sbf zL1a+Ngc2R1At2SIGslgoR8H3|svh6J%?cWVYh0K-7})IwHmp(7MpR-}=S+jI>IxKs z_iVXon=nppvDohRK}1z9F1F4gSUYrssd%&9TV@X$LX=5eW4pnx+iL7bVIriqsV?_j zTU(iAh`|RjL?ALv{^V#8M16$1F<09Wok7BJiV?{g1BB<>-iHXxQ6y$Is!mZxABm}U z7J?E{MD-!Gm77A0Acon87}M)YP$E-I(*~j{_!t!si6sq@%+nN8E0D6X6ad+HiITXC zh#_R#gCZOQO|i2?$!FJxfRxRWVvPC2k>07|YnaSxqNu3Wh7Dm*jpOvk-+cS?ug`w# zt$Sx(cRlz94S;MFdLLEdt#$MEYVnItzW!{}ef$3DnIE5C?`vzX_x)QBj(_>Z>;KIk z{OIpK`SRV{r!P0#XO~w(6bXiDI$bos_rat8`Bz`uIXV9Jy<1M@-P^~%e0}-&XjucC zRy=7GM2)ht=I-h8YTMtf-AQG;$w!qVXHH!G#q|z>?k=0le*D4x<35Baa;K?-;EVmZ zsLgH)JJrXHeXpq&t=s#sm-xM=xm8u4TBxFON>M&+1G{N!K|cOJ&86D#=i-OgDKH1IV0N%#A!T5r|NEcueqkT3f{g zgQ!7H2$De{khZc7b7RS>9ye}j>A{^N6y05I+sY}3pF+KA#Rp?q5m!e`-%l}06pf(7 zEKyADtmP=tSQ~v9$1#kPtIargMs=0l??%_U%Wa2*>7cHRiK3Ilew=D&rhW_v$aJ;$ zw~iNG?|szuss_R-Dg&^g7$YbKPz4yLSXE{W3JTVlpu&K|6xNHzaN5Iyvu2Eu0m(2X z5}POhkl}vvm^3B;vGDbg&_^L6B$`4Hz=V(ps5Oj0l4YO-s$`6UMooYU8zL_;H8F`p z&Bn~1v-A!pa5>FDHxeV!5R2sCvKcmH!`U$=snu0M1r-=^GfgL!zyIhyIQsI<C4N7;W5OsemvXm-}vdXt8MG7igrO??0ZG3tr>hU#N!kOgv((< zfEaZv@}O<|!LJuJscya4%6@j$dq8Kno1!INHTLhX_LbuWzlgt1=H(^+(%2hn*dJC{<0(rFgz+Kg-*keU34B;U(e_l&8`xJUAA zDo2A#iUccYK!FryI(Xr}K?b`I@p=dBW@d3HSYw-NQMm>H(>SPw6R0Ygn3$L?DLec( zqG8e?PO3Urv7ES?eOxv1Fqn-VsN-2NMsrb4(lX7l=o$UUZx$BhBn=aY2gE;pF;75* z*)?>o!c58RGyAM!*%BuNnVD1~W~OB(W&K?(GUWS-4Fqc<*A>5DN3}4qYtR!D(gqTyV|Lt z-L_k;S^y!&7=$gw2(GGJ#rxf$0LI#W@{8J?U2I)teF#2?ZR|7!YpCz1ljC-CwMWq9 zq9TBcZKtUHIPJzDLHZcRp#9{L>8hWO7nN5fCa1v6Qw+&u!$%N81=1+Q)ccsaSye@* z-7paoTkfZj^r%#}cIl&>y!n%Zhh-B*eTdfBC=x|W5FNy*X{L!HOcY`?#-!#c%VdEF zG2{nFP&9^9`W+Kzc7Y0{_CHH0@&PYd@B&mExQI&dCgNt-|M**P|NQmE-@iO>tUbTp zfBVtxZuAe17DtNT{ovt0eEh-~>W1mt4^BQjJw9Q&+qRoAuAOTv_Zl}-{QkYuN2a#M ze(Ub-c47YefB5t}kMI25^NV}S=ADzp#b!8iR*6n(d*Vz8)8lphpmy7F`f#-(!Z*RU zj(%|K=;c-S;jN=DHv5G&@2?lv{Zv__B1EumDn=fC{PD^9?WXh+nIAKg7U>-s*%A;>pwtuDK8YFsJ~L8P0e-+gerHu%}5>lJ@-)ql9C?pF58>#nZY zSnho!z^@0`C?LWe!o3f?K6;o+QEDg4W(yCzW}RD4#Vq=oAr6^4I4tEheQq*gYsPu! zecNnHUz$IrOj;n!dL&}bw&1y=PmVuDxd5dX&pN|{h5u|%mxYS?{K$PZA!XLlyjav( z2$Q;UO)-7x%T&?;V1CY-JBBlhZU((mlL$qNUPysb&}iV)@`)fowLUDSo8csyhK=$)i#;dZ|VAsp&VQMKdE= z!vshwOn|DDA!|rOTsTuBtQ%vIjKrhH)iy;si3U-CdeM&i4h>4wsqedCs@nSe>~ei_ z68e#iK_Wi}W7#<;8rfAtKLX+uLWs(a-@LgDnBQWHSUZaVkx2-_2jghBAC(XQtIF>7 z!xRG>@?+R_gSEy7NqMhr>o)t|)vl?mpS&{lgEy-Ek(h`ey|Wx*oDnlb3^5cbuP|{!5CK3m5^)R>0Ej6_%C7}cjiAs^-~Zsj-+un} zd-rY+A-+7n+DzjZ#ai>LuU~gD+-e(uc(-cKc6%Wl20to#5ewL>h`^KO;`Oe(#P}b- ze(`#<`}W<_FR!*E$YvNjA68_(d9pqa<8JW52Se5CxseAKpLty6?AwUgCF7mW06TlgCiH zMx2f0X5dyb-8W&LrTxwKbTOh#P@$B~7^d6u@H?dxV?sibm}6K|3bcsMG^|3sg$%Zq z>RT}2cqm$>wVpkY3PBC9aH+9)cj%!GrJs=-+rpWXQHphr*GyIZ2H2fzw-l%V5~TzV z5{wLD;Oy&6SVp~6<;faXyV}_*(^vAICMzCi0AEEDJ)9C>kZ#mVD7)8L*)g+Nh_cN; zbNU7(f}A}Fpfay|Fa@CGnKcWQF!#Utm`gmxWR8*ApILyzTrB$~M*ydlUEz(u%$&iT z%z%YRo)?2To}-`{s=|P(iEa$ZT?zyMgqaA50U1$Mt+6W6AZd&hQ=R;a0S&^kG7TX~ ztcXy>5w4x73{|xQh02*QPD9tTr7(CAZR*-o45F^CP~zAPwsKw9dx@YjjlQWWM(Bq@ z2n@3|H1*!qt{ePv+4kNKldr6eQ*3K@)%DKVb=&OwFu1t z2+o?TO=p~)yi6e)Vjm+A)s@|k9tqF)gR!=DwhLj3Du6@qODIZylqh zfAh)HMcsV+-mRZMdv$Bkc*T$2T%qA^3dXPnytSxaZu{FutL+$0nwmtEG zLp*Jo0m8%8;)|=j5`MMa>|-QEEAU4TPCwag#VZk>_dd-WA?k;x%V)d(I`|31MtxYh zw~yLiTx^zgeLi_2x(eZ!yY9O4504ffZFgiXFnzi0z?g0HHQ_kLYcIS$d6<1D3vC$@ zlL11$(*V=9L9-`hLNc;>&5fc82ax*4UJ+z=gqYc9T22B0iEvPoC-v~bkfBthY|Dw1 zTHu*}P%sdj8$f{k2}ldT!}<gD_bbE{jRlk{1SXG8aT4xMk&3MvO$8DWJd}B=*y))#! zCpIxgSJ@DvOfg0A?yq*HGN?!Z%X_yd%^L_)*8`IeVhuxt5TmJV#WaO*)entx1lot7 zQGgj#EdU@n=hFEycwbq=i2H8bYEi>UyfLX_T+;>{G}P0Lv+k)ebns)jfva-?a9P$?4xO52Vks>H;Agb*kH zd*68SkFT$RU{v|!?8++I+Pu8n3Stm{Z{2>i*(1{wB^eNs$o)G-#=Q3NQcUbQI!!dr!avc!WhIOzUcdJ+&g`Cx&6lNqqAMd)?V$0 zqqhF_(=)D|G4$)J?Sue?p7d%M|McGJab3OXhg(hk4`O*&0sAr ztiA00KKLKsT7JIUubi2De6}C&EStTLZ>H&5eDA}3=YDeM)_-_&{nqi~t-9F`!%uD< zHBrB{SbVFkJMmxlo>#|@a#@fpQbxmE& zLm7)Ini3$9OCHRwW+j&|lSvY;O^0P&!YgKU0&muh1KlLE9VmT7=EggD5|s^EYMJ2% z^Mq!%3Mfc6X8P?+)XTi);=5LMpZNjil7|Y$a9uUd)=Zp^8Gsw)#A5$n=)F0G1j=Tv zh)(E0uuF@V^wP?9b7mqIDn({qi!bV{5zA4XR7iP^nNjSrV4O3*=hHV|$R8EO%Gu;POGuUoR19Io z#DI>mHdGm=5Nkt?We`~!UOArnaC~RYhPD?QwgwPQ-9!Lnn)=Z?Gmf4ONf031_k(qo z4PCyuvUT0>djK*yV~h+CR6~rYicG`g z5tNyIluTvToKO{_qTwhaB5X{G=CQ`4`6wY9G3~AkmX#5y*K7pFWN{x8d1HAX#}KB4CtQ`S5$+e)n&_c=__Ge{#Bd z|Nfm%UtavdHy)pDw%h3I2u$Ro9ye78N>Sf>eCJos-jKEX!N&+u)tc$bPl6Dm9y><{L%`c@J&0cRV-UQxtO4Q8w*T{QJ^B1<`@Ely z$=q2k&POi_sPdze(-+%rQPszWpLP3y^x1abIOBT}OQJf6EDS7}yAWS=dm>&S?h!f> z1Pumjpg+2OdNG8vDU1jw&H>2lezX8xkY2(Uo4p5I))lYTk6`8$gQoWZ5=NnO!A&Yq zOdo7hA6gkLDsHyn^Mp6+{WdcG35oehIY9_1d7@Nvcj5YI}qt=G7SXDBHIQpus zM4<2b)#>{DS(!OWBP7fESs}# zxV>8RQxF9phyo!-L$qkzz8|f%qT;<+!>TZbkiv8baEc*W`x1c{6~vgSb*X$K5JZtE zq5xi^VG~3&BTFe;6E%a*IWQ=7C5e%#@!%~}df~+KO50it5|f)_(oZ1*0|8=;q2zK& z;@vYbBLX55i%Qb!6QUtbEz~&p`;B{W@7CWxeR<2ZD&b8(E}cCx=JoaNFMj(Qaf%S+ zmbGu)K55(b^H*o@-Mw0ES2;G_|=J#^c(( z=|>>C*VIv=^Wn~-x$6DW@V&aShF)Clh`<;dqQ2}0Wd5DIClchVeZQZ=QSB~bd_9eQ zlo0g0w~ik)?#P%ybsxO328n{|J?Ff{Uj2Xj&wuCdKYj7NJ0~x?evpXFz33@gReqsy zr?4PG(XWQyp>{#uNc^hn_Xt4&0C;ush>)Rdq6%&GCcse2K{3ss(z9jz=7frrt~oIT zuq>f6+Cc;=h*XwGhy7GJYGJM~hY|j;R7?yLD&v1)XcY$F+`{YZtPjcaFh7B!p`+5{ zrXRUcD4`HdOTJfOjARO3L4Q+~JP2aug<8^5qk?7A){DAoFlBMgT$5R?4uwsaY(%pZ zFatfwwjw%F?Fg>wh>ClOqylQ^5J1E#3~0ffqzZu*@Kl z2@Mf~S|&$W5iTlIF^6$FYN~bPaPp@|OKWXq*qDlqkuatBiN$*1CvOlXh(sYGrPLx) z96cHiAzEw6kRM`IId*2(kHO2Lanm?eO^vF-M>5o`S`qDby{Sx^!D{Cc`^BqDj8h1J zaMIRaUtBHPnpDRaL6i(#?Z@rtH{DRx6$4)PQ`GDxlYBY6hyrZ)gEh=d!!R0ag6QM} zu_2}qC5jMJ5FvmVfvpkIl+#!|nz>9^C{U=Mh*<2QiAfcLL{T+t6d*>SVz>bcM4axs z#Qi{k7$bAZ{7x%eCL)T-Z#tnI>0SWfJa}u`WF#mi`2is2y+VjF2~B}%*bl$`_I(n) zfA8eumlw!)j$Cn#m{=tLO z-QYK)KXE1koQ+{Kh02)+b-f?PC1P#t(wS>NO$v8Rbsnd$Hr>f$(I`2E$8Gz@Pha#y zP5k0)I|RHO!sX<@b+r6)+dr(`!^PrPU2lyg(QmiycbAK=cH8fstk=vh#%bl8Q#@wU z7!-pWf=f;9!Qya zmj9%_N>m7D8hd`)`RW(m-^_u_#z{B_CFX*tBAGo|RE0$fpLUul<~g_0>2h;PFE8|) ze4P@SS%~zM@$L-Vr!Qq+|MW$gYq{og$fT$gW{8oQ3~FVK0W|;>V?}5HEa-7tSD%Oh+nhi+29H(iD_fM8xKecQ&>Ma>XMZ$*c zi(yKByO!`(-#y#!Pn-G|*SnX~R9TCN2H>}jSIeNhK7N=BYas^|(Z5 z$cKj>yrco(Yy>!ahGY^;vIjdTGDK!6SA2J6;vhy zg!xKnqEV#ZOT>8zIUmO5qk_4fW(r;Yl>~?>92yV^(yl4B-y)z&1YGcxvg?m&?1gd` zW@pPBd4-%d)iELf6QV>Cs4ZIpgU}eNtx=6EU{#l`vxMuaLIx(15Cvf057Xoc$yqX$ zRRBNw_GsPhcgtlncH`=J+3$OaN|ke!8@oQadJfYxj=`D?oil+DTy8of9O<~oz0Z}3xE$ZY}I>g{ALy8O-fL4oY?_&^^ps|>Tr=TxY z4N)6sV^n8ZLQG=+%tHk*Y+_oms1V{1Jmoc#5aQ$`AX;Nm_>robVm6ydcubt8#gH7M zieXM_;fXM0Dp77(5+)X-lnoFhd6uF|mi4288a6R`=7<#fX?7%2&?u6~U&)(AMN-~$ z5DhU3z^)LJx|JKQRKTeCq;=Ma3FJ520`{>rkFVCvVU2UhJ za=fS=0E$ea5Y<_pe7Ney@7=!@5k9`WezoiFpR7h7ucq;A-*v(7LBaXK4^OFu5j{`=>zU1f(5Zn^s1b$d4Ul{H4-(@oPb}{wsLEXnOKTLNX4(Tv)Ry#d zZ9du#H50FoAHl34hC)(PDS#so^G)DCm9Saomh00zq~CxkvEYd?2SfoV2;VF^gpf^w z@`*5OG0OORgQtP02NgAdreh!>o{Yt8NOSv7MTxGeu`C;NWlZ|Kq&=M}CvSq_v~$fW*CfP79MELrI|pE#gtc{OveGR~etk5U35=4*u6 z2wRhqZN5H*whmAtDhsxg8t}~FD^mLdqhO|Os3wjb7{v5E4Pj+SVyG-vmet3$G6tYE zBq26L4C)YWFB_dgyR3&PfM!V)h_r2uB9oUmjSysrLa1@_Kr{|xRB)9um2H~3-*rLM zRaT=00%AfDVvI3L^g|cW86pTixTfj)kt>tlI|*?Vd2q76-VesIv(^|#QAO2N=3+NA zO${*;avuPwKv%zIm;y0x_aic0?8noiHiUqR{S=cG78w%27~&)`E!hKrB2FO!f-x+j zQN>GfCdmX!Oett4C2fn&>)epya;_sOG)!+wJ~R}-u<^mCR%On< zAYx2;-joy!YOF~lXEuz0QG|)-AjG1`N-9&zJoOkT<c?2>T$L$s2|D?;St8+5+)|g?q8>9VTVvs(B%V8MC!CB{sh9HmH_Oz|{9?)qRHua6#Pd_Vz;2rajy zY5I=gpQ6&g(TmMgh}@q-cJqKDvChWfFdNGqG%%SwL1m?&<~_HwKu&0JHbb?(h(f@L?$={{jCaUnsnUBD>^rxw{-LXJ>%H)!jgM zb=Q%VnQ^(hnW-ut%*-P}Km@3+&LhIZT}}1V?{IzBccFJKfI_8bH27oTu{>yY;iMQ} zI$|r9_EuOLrTr`u#*)$q#fPl*;0-`n8reE-)O-E(E38H3*6^XymTEnEA0kl*27EeV zD>r#5*+tPu?bB#eNVz#05gTeJ-Z*`=J2v}+rn`N@CGf7^0gJ^Y8Db%IAeNWG?Pzn= z*-t%}$U!vK;1IK148fUBQyO}|W;*Mf0}YNHv1XZ5CP$WqnBtsM7B__HFf!t5GsNRO zoDC@p0U;TJy?wWhF(TPGB|i)ZN`P4{c(>mj*|Qo9tB%zq%VxcrrYNSWc6%5DJ7QG9 z6tnN#-C;h&G-n~lYI?UlipMcq@112;qGXC@&U5fQrd&V*c23mBlpWEW^OVFf8bB6B zq^uTt7h@K)0)3R>N5NtiL^4E>qRGOnC7Bzu8i09rNwTW>LX$t)Hej1sO#z$_g`HXM zL&q#;(rErU3m3l^TUbvF)egys*$k}gq$}UKhRPG7NG?XwXb_w;GZk4dz=b9Tpun7` z`Ja5}ga7GoKV$C?WBTm%oqDz`&xiGGjBoej`MOV8UtFw@hsk%&0M9%A#nQ@p{N9y*(cP@r%d%IbMeDm!aY8-=eQ`WtRXO1H1=sxC;HY*RfC;Qj$cfAV){5Q9|i=i6;5b^Ks4$k|dNb`ek zTY5lHDE8Y_?Wih^YEh^FV&T}iuG zpjmEQw z51}VyfXdluJeEamgY9MD=^1D>zAzlhxC#wEUP@~@!>}sF@nM_)z>f(ZLK)3BRhQK7 z3NAPHcHJR9fP3l|P8&ZGh}!Y2W=u}YhQC^XSKtA3v5s13a1|dZA95kdwPUheh$>8I z+g~eL8YPAZmxYCG6ETN-k~WYti0<9d(SA^4*^8ZJa=FmJTZ36 zIq7QCgPKWBSyGCIu-)y^Gb0hX(0NS~=a|iieLT(=*XLtQ^PGGjRNYU>cbroOvneHZ z?1}G>M-VyhhuN(2UB@`(yz2aB=#GbYx$eXurNoYzsArBQ+r!-Vejr};;pTq7S`P*` zPDv0nH3$VD zi_pS8YAUu$Wi*b4^#gA{eA>H&m{jm!XzYI4HSYY*0F(YCy=#xyuVwDmB8 zlWow0!9i7n^FtVd56(HT3ducehiR$Dt*%)EQ0mE4@Omwve`!Flz0q>UEJB?|cRBHb z7ed5>tS^>Quq@D9!L9EE7Q!PG)AVYAf|V~-d61>mE}Sbm?If3(q39x^iCHR}n%Xv% zDn==wid0=e>NHlVbp{B}=QDD6ahhrq~RRmS1BzK3S0_%Kcq2|zv3yZscrL&WVA*Q+jy0HCTVn1~{SsO7AEa4Is1q%5N9y(1to;J`Vn zf-%z+BLFaQ7A?qlR#mGD=PY7o!3U8dtTzBiB1CoDo^oE`HRqg)c<<{V-Da-^`6>cb z)0#TO1=wj2C76mzQ!k;aJ|zBY%b%~$!U<9XD{)+egiMGGfM6-gpMCP|=Wn)u_tkyq z{oQf82)+;QcAgI@cU|zzYfoQnkKXxbSDT@ChuskgulhhLpFDl^_Bj6jZUGCs6o^h+oMVP3B7|8cs--m&Kv2iwPscPEF8cI+P z9iwuaRn%j7!L6S^5k=|>r4R&}_`!LzutlsI|JP4o;q5_XyaAw9?)XtvMR(7EzV)YIz&tKuymN};4?`p)A zkh4H8u5JyD2?-6&xQcwh6ZgS;EJp%#jssQMJt3a2`xTON2uaR112JPgj0rtK7DEsP z&D!^V`+ny=vGduKy^mAWEErsz6EGkEJNNo-n_~*WW!0hYB* znA`)tzcI0B1EHGL4HJ3iQcBjWwV*k-l!l-bgqr`LO07{ZuCql|$b`PE4a?A00MYu~ zE^8F%f>#kxgOvXC1ud4j#sPxW$=0&|IO=nN7IWgZ`@Atcj z_->Bx$NBMQ;IqpY?Rr&4YU4N7!fW;FH-f2S8|w z(z4L2l!l50H_QPD8ex8+Qq-1w*&JenHVQ1-Zls0v)q2TBYilTZqZ?~0vZsm;#l^AA z0$T5!&iPfh3a)2lt&}@@aKKdfqEhrn zFSPoZEK@+;ub$Y%m9Y3=)PP3LILTXx+A;vJZCn?(q{e|MqX4u%xA`vBZ9e_M*>L)E zS>)M5fu;r1ubauzLeJE*3vSkc*)*GdFv>JS}L?>!@;3Lz5c*$_O^;8~M+ zhK|vac(n6A3`3WvjG`g@b_5!g@WNt^4CNu0t@@`@;l)o3nn3X^wdq z!dLI_gL9kSk7Dzj+3^^&h=~dUu7W#^aTXJ7h{ZGgI^92PQHxCam7g4{Q$#q9x6AzsQTqokz7mqpBj3V=gWOpii%Hyys%?*-u7XXlqg|Ha*Iv+mySrhoYI@y#^fmHjOk z5x!V=DVsX@YKos7jvMsI^!e`idYsImnto%oy32ACrz6_^G5-0}iwX5Nx4Xpn!_Dfu z!|HGEkKi1stb4bcb0YU|Znk~b6|gb{e|MPp?DB)96@fEpQhOQo1vP3&>28Q!QGJxgw0?qZ zM+!B_sX(ijYYz|wT3N+|^Sx(duyIL`+2_kY`(^gkM;^MGFg?MLX z)us-BtH?(^v50uj1fU{;nE*OZ8pRVjv%d2T5C}HCzZ&{g@PvpWNUS;g&K>s0a_u_r zcK3$_DoMI7xZo}4n1q8H=g3Uc9MQWtrL2NrNz4c&2{~e76LrDmBz+$gj1Z0~JMTS% zL=mu@#iBTX5d1jju=3k6f>}!01>PRV40b;BDdi-pXbPZ(Z*Hal$0&}`GiFuE$_~w7 z)w%s)RsiRmh%r&hLQG_#3L;6tPt?Q|8C8pSV>3N3 z(j=s!O|p`c!p>T_Qrr21cTUZcAeC9nWj4Ait|I;6T>$hLu^yFg73g(>4svvsaIYxZE zUQH=~bhi1{)%kDV-hSh3{c_#?ub+RF9lzZ4Uu=(8=R+^xnBFD%=Go?u)5`mG=RV($ zfw5y6Rj!Ob9)>T|G@G&5ruU!j$1jiLC+BDPNv`|h>v7r}uGM~(^8GmfaNXa=_^kH^ zFsY%zb@0J4Z!SJ4PjwA_D@bU4wrl@O3(VRo?*a`kvr^l{7V`uEKrNfM2iS(1U=uVq zG+?3V8Z8Yiz{13xA`%&oXnge>_z`TZ}hrut=q*EXP&xH^9oK zSg`E+uIXe$`T(}ZHh;ERr(LazdtfWzbzxNJR3&DlCUz%6qNdsyZo0R9SYZ77kWfLSTzwEC4hSxdu{s;*#WBwt@uesPLd+2%gOxT0rbQBf=&) zCUc%sOl)w~c}Q8Zy0S6rgNbO;0yPc8fFi@D&wyy=J1ZcuxpShjCtc z4~&2$Iqzf6YQ5tzrbCiV@0D;nr?Ox$q!4JFr2EDKI;&i2RvK#U)}D= zutS_x9pl{;A8&@+ao!$|AD&%mlG~I%d%t^jxgPi9cAQ^cUV4LXJvsmEcDJ9C&sh-y zAx5qh_a*UAURz zYcY?w>HPbc|MB(a^WE41{bb$0pR{A1KvPcB9C>s80xJHjH7bc}tWz^~x9V+FN~h|g za;u*vEj$@);A53^EV0&DSngK+=8A$rn>YeaVH{L4D$y!5ToLNF<0>_6@w#iZvEDcJ zzH1x8!thvzw*?+KfxNcdONIcB-Ovp~SUGkGOYC9cH~fLWUIVNb#-CM`xv-d9x#4A1 zT0q`9UFy=t?)BYvR-Kcs z2R_U>I1*80cQ=mc_&Ck0p;J{9jdKpcO-VZM-rVkozVn^mkMVLfB(o^m2e%(%a6B+) z(?iT2sPEmJ#Csoe&T3%bJc9v(i9%G#QhB0G1c+l!Oy%XHbFRS6OjL;GT%(Pf-U!S) zmqok}<>6(=3q!f`tB@2-L9wA}DuT_?t`+gB2Ub-mF1}^aYYNSC!|HaC5l{ijD%)y* zP=qbq*joTCTytRo8bQi><=w|GpZ>qE-aYCE@7&$?@K1mI$(!9Vc#1K-pXQF;Mc==j zrpKH0*V}z&d^5*v=DnMfbj%F!>HY45tMlV=KE!k}guCNBfq}wLK72Ng^Yz&ZW4^rH ze0ezh-MhPIt9}@||NGtTI`|K|?*IJy{Wq@8Zl`#d)4KOx?Z^9UKYo7ox9@K=*fz!& zoBo4grD8i3R(LUxUYF(by5F=7mu)@)^x<9JDyf)57>Bp^^quNrx&>jG?3og14GO)4B;FR97Y&zA1 zkvktk=z2d86pzHlawA?yhm~SNq`EZbC-AzV6STO#HGMf$Rzq#k>)I3A;(O^#mrN6+ z)9=CuF!=&eKX8Jr@8tBrEX0)h{)yYXS+9?kOn|anq$dA>C z59Sv`W~}EEHySJvp=zb&st7X{ol8-zRYf?WW6D{u>_|l#&|!q&kQlMF*bEpvfdM0| zIyWBY&aorx2|XwPY=ZYh=$!9EoMu&_Zdh&l+nZZ+u6HakW!2yqO>ggyzW4KS>eoG* zBB1xo#K*(5UUgD@DnuRN{eHaM3~^2|=HonNgPgMW!~~wn#7rd{^uhHZfZC|pF%8}w zrbNuWcg0tIdzkw^FcB*3V{|@5gOr89f_GWWGu@AKk#CMMi@}^yR&m~&K~`J#2f;D4 z6lGB>y_HHa0!`c zx)u5QZv6Js^W&_4@4FxVU%&dI>%-mg_~iWT)&21-g!?3)oS&zZ)w?I&^$2IZd(3VU z8v%&vIHqHO+gU30+i}Tg{`XcoP9D;K#eYkhD>UiatvUOve7I?6G1t3H{|Q+5ugF)~r`9QrWL*;F$i5_aCrIrn{# zq#)qEBeY4hV>e~lj`O37^?r&BIOU`UQ6(9;&YM}v0$_92WZ;Mo^E`AurJS-E+IF0y z+T~`*B2&)Jks1W&%Lg{+oB@bP%wkM^*Qu%ZA)94Y$5ieOXdCzv#h_-)RCNS3`~oXD z1~nqZL<_{>ycg9Zxz^$~{b2L!qVl{GNzMR3?8{jzDgfY|Gf`{mbH|K`nuSVAQbSH^ zqgL`IF>!E*kdA3z~b+_$1 z4`!+`1V7KIuy}}Ih1>=H;QI1|i?jdm>h2Kccl%RM1GK4kr zG3!w>GHs`LeK!1de}F)D$CLm7)O-4JoL?WO?_XcMN$HU7vG*VLUb0>i%^*GCV-Ek} z-5qZ(Uo-*1Qf(^i+n%~b5_pF8!j!b{{b8Po=s2go^I9V%0Th=TX-ZKIms+zwLbcOFM8||kqIyi}gUxWUUjHAz`QneBJ^tPM z`)$%6zIYZ@fA#uiweI)hoXqwyU91NciGZEs>#qB=AAJ1F-@jwW?|1vv;HRT}=jr*c z-rp(Fc?j#3|7th(gn#<->A(H*t?$BNiiEIU`9KV67wrG!+101Fd+gj{lB=_RN^(r; zkYdkn%|2#%o1$lA;tx9i$OZI#m*YY-r-WfMqP z9J{GmUe%q|vI}cx_DOzCfc4)aLanoPu&(Uc28b*TZ2h|mmo%+PJ2Kj)u0m{8FkPo7 zTi^$5ooR4g?}t9Dob%RpR`uklt-{IU7)})IiWxMsL~Js}c7Y20uH+fEOcQAKMpa!> z@4n^GDDS*l5iM*Z22!ulMd8CNkvQM5N>JS_2EMSutWRe=L|6?V@5=XDJRLDbLCw#;&DprO}9VH3O00I&WR9}&=bx{`c+S8sDJ{S=dUCJ_zJZRc5xCe_1~d~h*kb}l9jo|3|xW!;C)xpKd% zKpi0wnQ6{u0M2nrGN%N9Nd%cqg_+f$*b^3ANLl!+8WO4jl`It%QRC__1{HlnKqise z8o+ggU)=3|pfQ?rG|$<4+UGo}nAsm)ULIBMj?DBRgkUXhAS*`am zfA4(rr3i1*rI7^MWpD8&48e>l@}IVYf7Qff147&@y05g zU4MT~pslN}wk}(k&bCNhPUh62CyM~FE1FodD=UBv-TTQY3LqSa`uCxq3UCu`2m ztp~2Uj)hxY?`b9~?fEIeppe2V6LINxq0yV#*s-upYT&9B^cHdPNIETGORI}5eoRlH z_w5A&U^~&Yp;g)-z6X}D)<#`hFpk9(;X&Wr(u5J)d0DOyFd;%cOUgPPtNZIhPp_Lx zCJ+%O05fD%F~su80#$i8G60v8tu$7Z6AfrKI2Q`nPeerr&*yyr(T=G@V6;^qrsHe~ z+_)=_X@8v7XTyFs9;P{`$j*h{=O|N3!)k?Q^B6JkewxhSY`r1`Vl)5~Ln0159>$?_ zhzsO`$D9!GVm%z@beQ81LITLD9lMTDL8EF+f&iP&dB>B;;CVVmL!6T+TFll5&SC%{ zst9(m?vHaMrX-30Vm2qK0uunq;+#9g1PCSuWXvrvlQ4-g)#%~iToNgKC9NL{R7NQv z6PJdGJr_Da^Gj^~nJN*Hb2;alaa;|AYwkNznZU?Y3(wVx1hlUHKt$ROt85+1(MxS_ zjn%-mc>h-~Gg=f0%{E{m34x<3SNB8&kO2mOZ$G*EAAbJ3t_yjVdCo$3GtO5%e{!+D zJ?4v508Iw&>SFlf?e^v4^)KG-J~&@JUxl;I8<}MNaI^LqfBdZ%SDi1rlS9mVun2a+ z8~{AIZ(p3fizxwku^nVpbY@rUu#Zs;JmTXJp1AJy@%Y=_&X{OSfBDG=|L*n8@8`KA z?2RtG->0;4J|_KacQ{{l9n#A2%`|`aeD&uq9{u;f`TeTv?&kc9ar)lSe}CwIaX8*; z1~pMp1CPvS7aw5lfM9U}g<66Zxj|*Q!{SiZkO_OR>{_4ze86jNT;(d#Yov+h*}f1$ znx>QEF%#rIu-}}z7YYWCYyf5p{0)twufHi!e zt}p?$Q4DKmPGwc7jT%hwfJ_QVL~N%@UK@- z%?3{a(Gxj>AvlwY=^u|HIJ$qo?>px@pOY}V6cZY_;L|iCpzjYWXTb>1g zWn}^AWT_Hwuz~!nTnRB-)W31wisrBsO?U!bR)zvWMiWVFgX>G zsuTrjlr17MQ6{cg#wyU#nXp6yQ%!(dG>3)LSu|;<2Sg1K9J2!gQxSQ-?)%QKS3}qF z`@@)3?{i!Q?*O~b{o?hV^X{VO-@d+A7goWe$yeiq1|MBr{LQ<&IpeJMXx;tt_cxQ- zc8rtR$9>m%{PceR@x|Gnee&$DK6~@+XIH~K4*t!8RT)-znkK> zHtUS=tNUFaxXkLG?hi+hQSyGufBfjuRPX2bGIYC~rw&t87u@?pe6||C-W`HL0wjWu zR)b{e6n0sncOVLm4pIK|=hugv`TXLA!UM^59ZHI*mlpoT0`EQ$Mz?Xe9idRqW5Sbq z1siCK)foPP#;>;O)G*DZeXV6k+g`ZA9_34#%B|c|hK4}YFoSZ}5>>L%Dh$C5T)7Oj zaMhnwgJEgm5<(LkSI&bqpZj{KV~rbV{dEl}VQO!x3>MAExOnGl$(gS%npFhS?4eYb z*iFFY&}e5+v(PXztBygsoM&~prxmzwY!I`G9-9)jjLxQ_eGpY^<+CY4DzI?)3Y=o? zMF5!T$%en}IV+$K4{3501TSB^08m)z;Rb5XIVpz4i;cJ{m=ZCl5+Vbd8UqAhI&&j4 zPv8It?>qqmIKaTT4vs{7#~s4TQD9sTK1b<-TdjwAihxLj!>U6@@4Un$V&o{!0)$-% z`@@)2CFyDiqIR)aGgF?k^DbA$0$rSMj^kYBYbJo)2lrbJa46ib%IzA6JE zQ(;BbN8bwBs@$BYri9#f&SjGfW`$)UrMD4L!~#qc7JS3Nv1=xawS6k$05DS%M~pRj z)?9JRP5Eb^Jp1+g{qOGftIp3^Ho@IZ>6kSTJzjT*oIk%mJUQR2i0+Sb=lS)1{*#ZM z?p40LKfE~WT@nJ~&h3to9o~*JFpMdA=DTD3{LL16e?Lw@;5@y3bN{GwzuO-YnlXGl z^e;C3r@P}#meGhtwMi2FJ2%y@nNjB2i3@vf_Ld7)T90~Z@Bq7lkY zxsa`Qe6X~#g=^Q!u~t1&A>Jvp!m%{o3lv(LTBzW*HS3IagwzVr+U*)7+U_9%GXnC? z51|jPL!=U%{E$gz1_oOC(gLQ}hO~IXofsme2sQyqtH_N?s&zSMP-%I{4WFeYJgnTL z+PI%(!)d3Ob85A?BF+n7jP2T&zihU8iwkJI(A9g$mXD=w!rIoZ;Cn5}4A47Qe35Dg zja2|yl!Rzj9sw|=N{A-|bIvxeU7 zkE!pRDFD+LQ*fNcfYAUPQO;7%Y;Mg#64jGOGa;g6tr-YVO)9~W>WST z?IAD6c&V^vTEwmZz+9CnVpgE%num#?s#3P-xGV`v*U;Mhrsrm+sv3uul>ybS(L2}R zHL@ytvN8d*Glt3-E&v;%7mqLh`rYjr`@_L>>yWM$$Lzde)et0pYIj@3y z;rkBg|M}{T5T&TA!H-$)4zmL8X6=K!8RK`a&lBK<4i@$y3f zAh56845~_#CUFt*r6YU@Niy~3g*(binIoLq$V5old7*23N94g=@-fuS=H5gMq8 zEql=ges7BeL@rWHf|Ar_%gI1YU`CBpTb=4E&$F!M}kYHEeTL0|~bGdZ))abR?S9?W|@TlFNiUWIAQh4bYc8^V~R_udhWG5154 z=A=bUA$E4L%5ws>n6n{zCRIJe*&{n9B{&>L&s^vbVzwRU&AQtj=BTDZ>Kf{i3PI@$%Vs?b8m_*`*u@ZYTU0mOwwR8x?uLqL1v z@j;ITj$Lh#ix;CAl-?Ymq|X-kf`}{aRjUmXoF13jpktF$6xvB+jbU92w>n{!v&#(< zAu9s$COU|7`p%>Cv%X*V{+RODcSlvYUI%9ggpY>)J5R4}$LakTdxPg)w^x007$qE><^(c(n@myXm}hgLA*T-4Cm9pM-&u=+B;B-;HrQ=YRgK57Ruq zo2IX(IHi0Q{GYve`q|Chc^782dlg2wT&+I6x!Z&=r97Gd!p(8)J-^ra-G01Bn{)ol zmrs6mx9@^Kro2X4BW~Cm@}0k3w}hy|A| zKb>N;p`r?Hb5<*|EjX!JIkw?cOq!u+g@SRJuIkuK$hdrH<&|hjysXRRCc3D)YR|57 zT_1+vd#v;PNi4nu6)%gp0(4ad7~0Z%E^~b&cQ@=!rFm9BTdUxM0$*v@r3RIl`dZmf zaPrBsX+glNQAa8LCF)`^rK-JY?WPT^d4it2ANiq4O?zQjQwB~%5g@4irjW}nb3>@9ib;GYz-6jh`|w=F&Y6n0>>EVL-4#Cr=jZ% z6bRRSH^=0HTXjK9C1xLJcTB?&UhT$B-=P=>pVgw6@4{}Jr#W`aYN`eyco9u1s>qI$ z8WE+e$kcf^#hlEtns*!}vvVPM$$|*LIH$~xiwR;`h!iM5&75N*R*`a(W|0!^*9>X^ zh_Xl-;HZ5-rPXDwF{CAZtICtwD!kb_nCK}8wA|rU15u=;xD3{{auk@c8Gjd&0TpU& z)u}a+RC`Qy8Pn^mMs?4bRT4m&=AXTM@-Kh$>ZthQYV82oaBzMT0w8+eF$z2U@ZxNL z7`LM2c++{!@bUGzQ#+V$W2WH7tn1F*P4oB9*3Vb{m)iq-7tBcDY}2da^Udn@b~iEK z9FA|sxo1ZRhnzQE_t|bgiT=s6$M?JA-sGFB;V#Z!r_7Fanw_VV?8$m{6XR~4|GS_5 z;BQ~Q7r=Q+2ZRi^%kjeyo;mly+3M%}{o9-$h45+{zntdD>@LP5BcEM7Uz*b@@Tt8Q zH67k*pI@mO?P{9U!~_5{$I~V~xsiB1S_4(V$s>Hq^9I)&Se9;)TgjiNKY{jf@R{sX~|*jauFQ z&KLkwv0y`_LRuzP8tv9U`V#0REeRA^7SSQkNA!ZcmU zV%BuRsj-FE*vZd&2|B725vzDbE!K5y2xQIqrk)2aDg1%wN!9u`grqGs3R8**0J3C4 zbk#cE8v0?3=$RY>f_WxKNM?b^L<7U%dF9!I5dy2NhcGY)#G&_|c<8%nnk9;ur7Zo> z0lFz2n|F$$8E) z=WIwxbxh)zlL#`ISr$Vk=a`A2h?r&3qMk=YRUoER@~{!eak*6;yR4c;iq@;t+;XcE zF#%=Oh8)(Xv{-g9w~P*09@oa2s=mTxCAYiQ+@<#@O?0JNFKm2jGa%KNw}yL^hofzw zmS(>+B1~LtvzW;mXkK$i5wnVzAz(J0L^ojHee!7Vv^~Uczqrbi5Yvh|0GX;Ws^Q!1 zaecP><8MBdFiSPdX&HdF`ANTRa_2wW6CVg>v z_U}Ib>d$}h$?NSdFtr%=g(`i zx>TP9NIr2jPGt@jeadNdShT#nbg31-jX&}=E& zb#2Aog`r<}p(iA0MjWL#Ty|LtWYG$=Eq|z4-&DA|E_tUZQ4~Gow*Fs_%hHS>}|P4jXH(A_nEwFXm(m0fj0%shk0zdJ;4i zJfSw!ZTZ?BI>Kru)@)81Bf1&#wc`QJV2N`kIvsc|00kS;15J%mH;08APIYVCPI%K& zhzliv8W~eHd+!_p8M>N72p&;H9bw1BXsEJs?7)W34~|*QA$Hz7MCTY(eF)w==LwM1 z^l&(az%dIVhQN}wh$K_YbIhPRto!3}?tAapcSE?@?^8^!aqO6d}jV zn34zpIhsWgvG*<;?S-5LQD-|?8`2iKQ@=(qc$2~IIiVgd$0 zS@nLJuh#v4{rA5AuYd8o*ZavidfK_iXZ`QCy99LI`+nBcHUza)F*J4pW{; zsx7lFGr=+{6~M+ZsR*@Ojlj740j=0i2ghVOcn4~Z&;hJEPbFgkaNP$76gq!APVC(r z)A2ZaAJ`G7dFOoKISMi5n3Ck+84>y}B$2~0e(=FlBna$`?DlR)!4*Yv7A3Ttq#Jr* z-0er-c}z+Ks^*zG?+i2ptcP$M(-hOiW>CQQXrMsG$YhTyz&T?P_zQFeDv(mzk2oVtKB$sflXhm zhRG}`Ty*{}rC)#X?%NlefAXCdzx?|CH{0D&Z4<&*ha($)@9N^;zJB}Ye3QX$kJB8} z`Ko(&7@w?%k5~Pe?ThjFeu{|D1^3gJ&;R4sZ&$%*vDx%Kr>yY$INr`P0leI-pY}Zw z1$KXQeQ^`xFYk6h(0Lx1C`oYcA3eXm+fBf*ozt2rAwKU{@5cF{+Id1CVEAH;+mz3o z|KY{@=exs=DX-6-RgQ#elkhK?v9&t2@GRsVnZT6mkTE%ruIkI#CU|=R~Bdi7YQmLM5uL}bu(H}Fs3r)7&GJPxHYN}N)ebtaO{}O)B$>CkI)hJjy-`Rbb#yN zEDHh*op*%muIoIjXf`9nu5;*|m=bc%IjW_ckK?$TW{5;`wgfGDXTqNt8BIp;)mO4&I^ zf;r2ar5I}vVp$oQK{MkAD1R<@Ce^!6Ov$;FGhm^qC|4A#&ScBk$;?o>$)IM)RGJJi zV`fGal>$o^$hf#25V4w;o6730xPc@^|I*5WYH>HTB3CJlcy_f-HdR$!njR*Sx;kb+ zPy-XK(Z&b>kX0R{nLS?*pFF;pVjjG^T6f=l`Q)#D_a+m4biTTu5<85F|L)aI-}xt9 z*dF6SG=oj5KfAiV_U`qVKHKfid%sVT1^)Q@>L$hxD1jz{mGixGDZ}sH-ePb7u#^0H znx3!v?Q!m$yY7Zj@|g64N1IpM!))+(@9*z&>Vr4S?~m!y^FR3b`rm)`?(6;Bb@b`} z_^9un58-Cc4*5A!!r`uTpG1r%*k)HEfc(w#9C59h#J-7Tz`)GzL+d1gTlnLyLJW z9HM#z6}O5;P+K^9ClBie+gaU!>k{7DZu*)SiKh$u2k`}M1717l29C7DvbqaZV}=u} zrV#te%TXz(2$f!@eL9U}TX3y9>#8nm%nB@(L0d)9s_jwv4Y(`}ip~YfQ>wg(*Z>?8 z0Wd;v460<{iI~Wt1x8nSc<2xvBZ>_j8-PPxIoAg_PsuqF6-3+#klS^GH|}+kJ4p z^Eu`@+vaSTro8Gy7R{>cXi5@1$0RA|EIM>9Aa44g2r-MPido94C7OUJiN++}x!sh2 zX}#+152JHlOhfRhrYc!Y45ZS`SyV+tR6^&OsF>0fk+g^cVr2vsHWYy4n$KXB*+%UY zr<|oU7d2Y2VR~g3#l$&Rfl^)5nK7|~m1Cgn^hIRh3su_n@*H6u9}DfLx$l{Gj))81 zT{?DXe0*+}?#uTs>&7xw7cq;d_Rf1EGniBU!L#f0zI(MlygR1DG`}BHBD&j6=e;`` z66?p8=Xr*oyu7-(KW3@7;A8I8^V5sfc8>Qk{p{J}F(m~ggXf#|lP+BOFnBj-Sp|PF z^xH9s>ZmEA{bwIOyFbkAyW>2E;8r30;>~Rcymlct`sOfjAC zA)#F&Sh!;qpe_G|R)Ku85Z|bJByMlCHo~VZ zngt*P2J9Rc%Lq^-f!7-XS#6Zw7`NFCOaD*o62(yV~F*PyEU@7Kd=v09aB;^n| ziv?y+WC(Lk$2mWFbQY($-H$%7Bt=94BZn#~S%ly)CM0AcF$<386xC?EKd$<2N+o%t zVpB0I2Z~Xm6NFd-x@^J9fXS8CqhMU^*=dyN z2RNs-g1k%yB|O5)Vxw^gz|0}2*^21pv+IBT>8tNOJ|Cwv0jME&w4LUSb5~uz4xG^b z`qk~T^Zw&Uo1fp@2coAJ>;L)7FMj=g`;%vn&xn)CE@c4v;ghTX=jN6&TwScb+#Y<% zuMttxZ=Cg${V`a60|4XaE0`BjFZ_lJ6@eXYwX9x@Btr`BqQdw z-?DbjRSssQF`Q7{2c2G{OybF~7?%nRC)dNer8O-!qU70iq3?Xp#EnP!zycX6k=B~3 z7zs^Ju6WHlqY^4xVnCzZF1wLhd@9qkiIUsuqr~O`0BF;lwkEnAW2M+Zjby936&o~Z$%FH;T)_(WYb}@c7y-a44z$LrYuA~`@T8)Ly85h)RKyCI zER>o`&!*;J*SN|>ve3}>rW0r#4S-Sm(~^2#GkHPF;X_!_c2GmYB62B@HzSyeHlbsw z%Vqcw6;IeZW`K^-Gk7921+(5Wc1)}Fs_Q)0r74EakH;ASl|W0P zh+60ZGLiu?%~6J-OEHN8JM6q;#;iIF!~Je5@!^gscz1soonwtT1YY<37*jStLiW5H zW7oO+!x*!Pi1Q8&i0yo{n#Y(;6JVUvs_#aFLrNa8_}c+u7IlP44T&Vndg!N|Cs9>6 zU-xrNB@6)xr<{{28Wv0YLTA>NRFbkFVaifisfF#DL;#V=E%rF2cd_Mo#u^gbsGDHR zeh6z4rh!p0k1h{I=@_alij^&WQvfU$V_+@&mxzk?O+=V*VKl2si+qL!hpa|G#ge{S zbYUZrfi(JQ`yN}gZv|F$>=+@&e7#wHcs}gL`R!lFAvAp+wlfqjGG~d z=*=8G^Um^J&XY+LU9+3ab}6q0_hyX$@bLw2E?$(UwQa*rWx^`&-Wu-NrrNeitJkum zgj9DnXp25=^su^Wga#DUpI?6jh>96__QCm~8=Md2v}p3Ss*_&Mk76f{50YUktX%cS zjbpu_yu?uJ*U34$@#9bXxJ55WCnjD4C7Ne4HR2{LxmI)%`z^)QDkKNhD4FUSZ0Ub% z|Jc9;t2jHg0^R`b|34e1_7zLoK>4eNev_$Kg>#y_leTvX)l;^i?OK||0yi#AH8htF zFm=q;g`>!7N)Sx#fRG*@z(r?N|6E$UCc$hq0Es}2ptj7Ox!Uk6Kyl(HAOcV2FO|R? zp^7=ib?-FGx_4w2lQ>UcU@<>^cIklCWVfG~3C)~$5;HSSDQ6Kt%Oat3%q}H4&Jmc0 z&b@wfyET^Fh_3HCT6ug_VtT)5!+xyOY#EMaRCRghZk`Fe6wNk=EXp zeYzt=!kDv|5|>0sNFrdsOroZh5>u(^rj@T+=ugccMNe&5$&=Mu5S?>Y6JSeF8A8*K zEb3w;N;#FRvbGzqs~~HQxv6rwudD?-=7pf6^ zWBrzu4j>|PC4eGgoYQxop1177k~1tFK%`jP}DH+e$GF9 z@@S0mo4dos`RcQ;Z=PJ8eR;S0-m|MuZ|z1FUJLlM{6zrI>4 z?jQBvR|koPjB90Qmj+oE7WC5UE{!3!lfujp`2==77}+Wy17NV=-C(9r-H^3$u5m%i z77)80A=q3}t)7C^%=60lh9!^6PCCp54k}%+6$I%IT6kFcqX!dEUCUH;NyTj%`izK% zO^2=R@2|XdI^ud7{s`Q>6nq=!d=a?UDhE3=0?hfQg)R;-La_8clq6z`!ocmQD zQ`)T8;}pTHTlJ=Twi@p44*d|OdEV}i!_b4uF=i&*A4hhM!BP?5jcS3aas*M0341pAdYiZ)q=EW77>l2xBElK?3s2`Ed8L=d>NSMrE1@9EC{BU+ImnNE78__7|OibDkv88*_ z3R8lZLBTGHK?<^4C$*T3EGx6yuoI3m%RQJmx_&#oNe zHs%E20RH0Hqn+eWkK-EYpKq=Z;Hxxi)F1Y%$@H^%dhGmdMrO+dO(Y!S^^0kKn5}SFX2*uW6o{iv zjTKXC;jo3(e}a$e-_`3-UTC?(#R0<#znGeWriCsq_I?IXJcX7ry_D>7<1S}QdpyeA zS;#q9DRmtlITmDJTlCUUzZI>~Koi zF?#Qh^PE(JcV~U@-e=Vqv#No^X^v~}oafDY$Wb!b{c$Ac4r5d^&qzq#^EgFjOi|YB z)$RSx4V@Z@WK}JBfXuWzjKu7mJLUw21*t3v6`qL*IXKU5oaep|W&e=@vO;hS2JF}n zO*N~TftX3b>qOPc&P|a>O^el0O(!%=S)5}4R8UnbEN=sADO8P0(Ht1d{HLdYq;{$U z03frgsKQCyP?`2jBrO)JJWpH)$>z_~ni_2OUX8$42DzF#)FO{Ts#^;x7}BC;7AhFaOo=-_1tHIsN13*J!v^^?=(kb%ci2 zvqOY=jDz>*Av{_4+c}0V3;-)fC1?G7)&G9C-(`8+g&$vE{rc@~WVoBtpFX;zfS(_x z-i43WD>k@KX&wBVX$+3mA-tN$U*2y2{-eh?Dc+^z5fSilAMU68K4&AEVt%<9g!Gr& zqZxF>LG7L9L?~v7;CJ)n9V7BDwmW9x&H3|Mw;sfG7dlttBvzVg`#+!s#XnKp%3#7! zLKYf}Otl`l%c_kDJ-Nw z5_O+YHyrf@YUR>Ym0S(nD$_&diW)AESFKXousW#x%nH5Mp`n6dh=@>4pW7^QD)!WJ zWJ>L89wQ5|0853f9OQ})6+QR@_8A}nGPZ}IV7|7vC{)|~%B$fbPd^E@@xfo#h=WFe zr@~x5y_^%s(}aXegdiXjG7=Gk30mbrVx3nOg+0=#Ndi|FT*ZgeN^%MUFhIqRtmpFK3@-(G7)G}5s4~&2q?LDf-tf3ZnNp9C<^2~NtD&lr#ZzWfFM!G z(UcOXrL6tXImdUmTLBZ*an9%GD^ErC?L*%kkJI_8Cxy`a-ToLHJLm3?6T3n$0;I`V~QzHDIrldyVwkxa`7oW zTMtQ8M2ShQ^)ATF%tTpo@s=Y1Q4_7SbZlG1y2Mu_CKV0diwabZ6B4o^B*}FPg4o(s zeDFlqF}J3DBQfEFK;*hJY#3b?=^B&-+=mLE>~W3ZBi zR#Mg)%b+?4G^Q(6)^;siY0gj9>sQD0gAbovGhcV^x7%Gr%vmmn{^`YPKEzeueSN$A z7k~DH?cM%9<#~*K$Dp=Z`B(dK(}i>2{pR+5aPDl~ZRhzv`N6l|-fs_t-|B}$l%L;i ze|UX)Go_C=n*?%moF~%*%5(0?Aew%sTR#c@%{&F94sam%!P)AoJdXkaY4Gm#IHf2* zeRlb3Hy+Wx&hfqEbM{vu{B|Cnb^aUu>ORJ2T|mI1B!ou#Hha*K!P1&R1%dIz{A~ai z)l@oYV}7G{ECLV%00MH%!3Xbq$F3Ohlww=}tj4!nP}f%AnulyXlZh+b+^Q%yRIASW z#lpvQ5qz8g<<>jbbyDLbRHOQeMz^iqY2;c4|2m*mK0iFvJwoG!TjNJm^U_j}7W8eo zAlM*|Iys(7d%Jp#UtDd8>H%XFt5GF1YQp^D5l9Oi4{N?jnK9`P4E_--vW$yPav`%X z^be|w(h4k=gbt{aP}>(m?VAb@4+?rxInJOWWsX^Bh={<1MFWNgj)(y~A%eQ*Y}Yv^ zFca-P_l{ypedidU^X!RC)H$*&q4SQ337I)`zF&36{m7jI!J!L|QAL5sRG3`|ZaU1N zccK=vYR=xflyZv6IhRFZ()GF{vy_E8zS~YY=R;1xF(GV^Q`h;kp&!SYnWrgxPxGAA zfE;fRlk?@sA@4mDnUok28sY73?0dhR;>Bhan0GN2{_hy$dI)MZrM&68%XR;LKjtK& zn$<)ttHmVdNJQ1lIVWZ@wZtjFlaGGsgv^LYMOxxfW4R#`p{Nou)rpNt(sp%SX-`At z(;_aS?5ekJ`D(yB(bW&eg@*`%gB*tL^wYMg-U} zgQFKK|4*J={jaayO{Qz$C!x!t1G(VoF2-+c&L)v;`p4&+GhkkyKd*}gXaqZKo*Rvv z+bA3j9$lEQtu8gpc$teE>k@zo1AFI!BS+-4nr+s_(L(Hi;;46`>d;AI-jfgVh&#lDQyb}YisJ#`cSs4cb zt=jvw8L*kpoj60-^0G}!KeOC*?Y>>M)D@xr1C`L}UpGQ@MZKFQXqgzS`B=A;rnXqR zSh=50#Jb#MqCcQlw_b`GX!wT@>gkqgdq@DR=2xD{)D+BspVtwCv??H9w{m^%yx(b}Kis;$U9gZ=ns!HfXoUIXhcPmN{muvXsDxhr^5=Pca8(QFX*)$^?+rlBxs563(r5e;5_P5wUlLEhnPM zbYWniK{jQkrRyjqthn1%%3Ag1peocv6jN1m&S4WiayZGCnNJXr2TYW)yG(=jh4quhtpl&czrH`*&y zi2+sel|ao=b5uWZQ2ThjT5ndr+3r2y5d09jam=IY^`^f+Oy~V-#kMnLb`W*dyJ!ju z-nkDpt1s>kA8z_D_i5-|=LiIU^7PS{`=jgJFJIj-(zA=z&)@F%F>kyJj(&eQBtTKw zgz)t|_QY!X7azU2KgQ3F)4O?oaj|+mPD9@XM9=VkoQU}*#!si|pFX-Gv7PFGyhEK+ z{>EzaSNGeksSx~pw|_60*XPgcQQWv`Z6~&r;Ko?M)0VFtUDV3Tf?bvmNs*}YzV{)x zj@c0`k!1^Exv>Tw3`uRn+|bolu9~MrIcN*3_W^C<^i5ckH>+u9HCHV9*4hu3KnH69 zS7naJrl78^@kzkly6;6hgMT0{2dI>zr3SXXg&HYj87)sOc5$Dk1@x90w4Tp+)M~=``n3m8>1G9A(2qEVzYL1Dto~`=Q zM-#$k(}P;*!)h~V6k->gmy%BnAZo`kDIkC*(~cYhcB}C2ZYR)8oE?i8C?FFtWzm!+ z<~(#Es@(4<1L`^l0IOAhw>_AnampcZ&O***u{p^w4Ckxv{r$lQrvLzCV0VY(K4v2@ zfUF8&&T&jRiFTcjB7o$*-%fE#()S^o#VqTg8)HtUs^FZnpVT{76JT#v5BTuy6@^j*LoM{N=mX-EeS%Am?CqbKUuwr8?UteG%6Nmtt~oX zq*RB$ax;|5UK!kG?6@}Hl}=ZL;k7H%X5Y9VZOdVUi!%WtHomjCv9-`>sX%i}!f zJfZmz?hpBopI+V`=ewLy?JvLi;^+6fH*>l`z6#xKOpLJd?#nz~pnu-=x0*a4ihZ;m zj!AfR{=7Xqi{Wxv&{y?!lb4K)*XL4V2?mx|dW)8dD)3{T|%!S-W|q#IgV3O19~wu*<6rvzpW zE4#)!E~Y;vIkceI#kCF#ivytxo{0iMM|83721nDJ`py+!jw*NoAVx8fB%(?P(>Rwt zaf%rkH0gevRMk0TN4NLK>+>}!3`0L2XZFrJcEo1TuR=UVB`9z&LV|XM9ct|%&a;`QZD2QfRvTEBtlfAxv-`Ip@dg~8ZwF2R6`;H1u7qF zacC*w=BI3Lt@0B>Ea51KGGPd>Q3AM@Ke`QV(w`he4bbwAJ)C5==|i{}U8qJb)}2g)b4 ztRE}b(q83*qPW!3Iwcg++hW7HEW-+VA!P}VEhrFh;p$gc4On3I(=5>>8`bU>7cGj_ z?yu3z+mz5sdh1|IKfVw=TH6lA+jGJ68kriG9ep_itYyMt!yoJKr^OMycCTg6`k+UK z>Q!S+Ucc~dmb0OZEQoN*Z?7+-zQ{!>(-_~-+{XX`3dl%JN=^WQT{#*VilnnWroy8lfR2d(yssmVCz2@MvGY8QF?7BQz6(xF?(PojmG_?GoK=ibgL9Y-9UZ4w zJ~LN1xn{@xvluA4nxzb(`9;f7871c}DniGfsL~~5sGE>?oL4_JzH65i_ zsEOUn`C944NDrL#YnNG}L@_O3bVIzLjLrZ?%f literal 0 HcmV?d00001 diff --git a/Devices/es3c35p/CMakeLists.txt b/Devices/es3c35p/CMakeLists.txt new file mode 100644 index 00000000..7ad1ad01 --- /dev/null +++ b/Devices/es3c35p/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRCS "source/module.cpp" + REQUIRES TactilityKernel +) diff --git a/Devices/es3c35p/device.properties b/Devices/es3c35p/device.properties new file mode 100644 index 00000000..233a86b9 --- /dev/null +++ b/Devices/es3c35p/device.properties @@ -0,0 +1,27 @@ +general.vendor=LCDWIKI/Hosyond +general.name=ES3C35P + +apps.launcherAppId=tactility.launcher + +hardware.target=ESP32S3 +hardware.flashSize=16MB +hardware.spiRam=true +hardware.spiRamMode=OCT +hardware.spiRamSpeed=120M +hardware.esptoolFlashFreq=120M +hardware.bluetooth=true + +display.size=3.5" +display.shape=rectangle +display.dpi=165 + +lvgl.colorDepth=16 + +storage.userDataLocation=SD + +dependencies.useDeprecatedHal=false + +# Launcher clock and full-screen wallpaper +sdkconfig.CONFIG_LV_FONT_MONTSERRAT_48=y +sdkconfig.CONFIG_LV_CACHE_DEF_SIZE=1048576 +sdkconfig.CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=16 diff --git a/Devices/es3c35p/devicetree.yaml b/Devices/es3c35p/devicetree.yaml new file mode 100644 index 00000000..f7e2389b --- /dev/null +++ b/Devices/es3c35p/devicetree.yaml @@ -0,0 +1,6 @@ +dependencies: + - Platforms/platform-esp32 + - Drivers/st77922-module + - Drivers/es8311-module + - Drivers/audio-stream-module +dts: es3c35p.dts diff --git a/Devices/es3c35p/es3c35p.dts b/Devices/es3c35p/es3c35p.dts new file mode 100644 index 00000000..e8b79d4d --- /dev/null +++ b/Devices/es3c35p/es3c35p.dts @@ -0,0 +1,141 @@ +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/ { + compatible = "root"; + model = "LCDWIKI/Hosyond ES3C35P"; + + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + }; + + ble0 { + compatible = "espressif,esp32-ble"; + }; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + /* FM8002E speaker amplifier enable, active-low on GPIO1. */ + amp_enable { + compatible = "gpio-hog"; + pin = <&gpio0 1 GPIO_FLAG_NONE>; + mode = ; + }; + + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + clk-src = ; + channels = ; + }; + + /* BAT+ through a 200K/200K divider into GPIO8 / ADC1_CH7. */ + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2000>; + }; + + i2s0 { + compatible = "espressif,esp32-i2s"; + port = ; + pin-bclk = <&gpio0 18 GPIO_FLAG_NONE>; + pin-ws = <&gpio0 21 GPIO_FLAG_NONE>; + pin-data-out = <&gpio0 15 GPIO_FLAG_NONE>; + pin-data-in = <&gpio0 16 GPIO_FLAG_NONE>; + pin-mclk = <&gpio0 17 GPIO_FLAG_NONE>; + }; + + i2c0 { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <400000>; + pin-sda = <&gpio0 38 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 39 GPIO_FLAG_NONE>; + + touch@55 { + compatible = "sitronix,st77922-touch"; + reg = <0x55>; + x-max = <320>; + y-max = <480>; + pin-reset = <&gpio0 48 GPIO_FLAG_NONE>; + pin-interrupt = <&gpio0 47 GPIO_FLAG_NONE>; + }; + + es8311: es8311@18 { + compatible = "everest,es8311"; + reg = <0x18>; + i2s = <&i2s0>; + }; + }; + + display_backlight_pwm { + compatible = "espressif,esp32-pwm-ledc"; + pin = <&gpio0 41 GPIO_FLAG_NONE>; + period-ns = <200000>; + ledc-timer = <0>; + ledc-channel = <0>; + }; + + display_backlight { + compatible = "pwm-backlight"; + status = "disabled"; + pwm = <&display_backlight_pwm>; + }; + + spi0 { + compatible = "espressif,esp32-spi"; + host = ; + pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>; + pin-mosi = <&gpio0 11 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 13 GPIO_FLAG_NONE>; + pin-wp = <&gpio0 14 GPIO_FLAG_NONE>; + pin-hd = <&gpio0 9 GPIO_FLAG_NONE>; + cs-gpios = <&gpio0 10 GPIO_FLAG_NONE>; + /* Accommodate the driver's 1/10-frame DMA staging transfers, matching + * the vendor LVGL port's full-frame refresh path. */ + max-transfer-size = <65536>; + + display@0 { + compatible = "sitronix,st77922"; + horizontal-resolution = <320>; + vertical-resolution = <480>; + /* 80 MHz works in the vendor demo, but produces visible QSPI corruption on + * some modules/cables. The component's 40 MHz default is reliably clean. */ + pixel-clock-hz = <40000000>; + backlight = <&display_backlight>; + }; + }; + + sdmmc0 { + compatible = "espressif,esp32-sdmmc"; + pin-clk = <&gpio0 5 GPIO_FLAG_NONE>; + pin-cmd = <&gpio0 4 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 6 GPIO_FLAG_NONE>; + pin-d1 = <&gpio0 7 GPIO_FLAG_NONE>; + pin-d2 = <&gpio0 2 GPIO_FLAG_NONE>; + pin-d3 = <&gpio0 3 GPIO_FLAG_NONE>; + slot = ; + bus-width = <4>; + }; +}; diff --git a/Devices/es3c35p/source/module.cpp b/Devices/es3c35p/source/module.cpp new file mode 100644 index 00000000..c4652ced --- /dev/null +++ b/Devices/es3c35p/source/module.cpp @@ -0,0 +1,9 @@ +#include + +extern "C" { + +Module es3c35p_module = { + .name = "es3c35p" +}; + +} diff --git a/Drivers/st77922-module/CMakeLists.txt b/Drivers/st77922-module/CMakeLists.txt new file mode 100644 index 00000000..6d6944eb --- /dev/null +++ b/Drivers/st77922-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(st77922-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_lcd_st77922 driver +) diff --git a/Drivers/st77922-module/bindings/sitronix,st77922-touch.yaml b/Drivers/st77922-module/bindings/sitronix,st77922-touch.yaml new file mode 100644 index 00000000..cadd3337 --- /dev/null +++ b/Drivers/st77922-module/bindings/sitronix,st77922-touch.yaml @@ -0,0 +1,37 @@ +description: Touch interface integrated in the Sitronix ST77922 TDDI + +include: ["i2c-device.yaml"] + +compatible: "sitronix,st77922-touch" + +bus: i2c + +properties: + x-max: + type: int + required: true + description: Maximum X coordinate + y-max: + type: int + required: true + description: Maximum Y coordinate + swap-xy: + type: boolean + default: false + description: Swap the X and Y axes + mirror-x: + type: boolean + default: false + description: Mirror the X axis + mirror-y: + type: boolean + default: false + description: Mirror the Y axis + pin-reset: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: Reset GPIO pin + pin-interrupt: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: Interrupt GPIO pin diff --git a/Drivers/st77922-module/bindings/sitronix,st77922.yaml b/Drivers/st77922-module/bindings/sitronix,st77922.yaml new file mode 100644 index 00000000..681ddead --- /dev/null +++ b/Drivers/st77922-module/bindings/sitronix,st77922.yaml @@ -0,0 +1,47 @@ +description: Sitronix ST77922 QSPI display panel + +compatible: "sitronix,st77922" + +bus: spi + +properties: + horizontal-resolution: + type: int + required: true + description: Horizontal resolution in pixels + vertical-resolution: + type: int + required: true + description: Vertical resolution in pixels + mirror-x: + type: boolean + default: false + description: Mirror the X axis + mirror-y: + type: boolean + default: false + description: Mirror the Y axis + invert-color: + type: boolean + default: false + description: Invert the panel's color output + bgr-order: + type: boolean + default: false + description: Use BGR element order instead of RGB + bits-per-pixel: + type: int + default: 16 + description: Color depth in bits per pixel + pixel-clock-hz: + type: int + default: 80000000 + description: QSPI pixel clock frequency in Hz + transaction-queue-depth: + type: int + default: 10 + description: Size of the internal SPI transaction queue + backlight: + type: phandle + default: "NULL" + description: Optional reference to this display's backlight device diff --git a/Drivers/st77922-module/devicetree.yaml b/Drivers/st77922-module/devicetree.yaml new file mode 100644 index 00000000..a07d6f33 --- /dev/null +++ b/Drivers/st77922-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/st77922-module/include/bindings/st77922.h b/Drivers/st77922-module/include/bindings/st77922.h new file mode 100644 index 00000000..47762b4c --- /dev/null +++ b/Drivers/st77922-module/include/bindings/st77922.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(st77922, struct St77922Config) diff --git a/Drivers/st77922-module/include/bindings/st77922_touch.h b/Drivers/st77922-module/include/bindings/st77922_touch.h new file mode 100644 index 00000000..0c038a12 --- /dev/null +++ b/Drivers/st77922-module/include/bindings/st77922_touch.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(st77922_touch, struct St77922TouchConfig) diff --git a/Drivers/st77922-module/include/drivers/st77922.h b/Drivers/st77922-module/include/drivers/st77922.h new file mode 100644 index 00000000..2cb119d4 --- /dev/null +++ b/Drivers/st77922-module/include/drivers/st77922.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#include + +struct St77922Config { + uint16_t horizontal_resolution; + uint16_t vertical_resolution; + bool mirror_x; + bool mirror_y; + bool invert_color; + bool bgr_order; + uint32_t bits_per_pixel; + uint32_t pixel_clock_hz; + uint8_t transaction_queue_depth; + struct Device* backlight; +}; diff --git a/Drivers/st77922-module/include/drivers/st77922_touch.h b/Drivers/st77922-module/include/drivers/st77922_touch.h new file mode 100644 index 00000000..b4bc5779 --- /dev/null +++ b/Drivers/st77922-module/include/drivers/st77922_touch.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#include + +struct St77922TouchConfig { + uint8_t address; + uint16_t x_max; + uint16_t y_max; + bool swap_xy; + bool mirror_x; + bool mirror_y; + struct GpioPinSpec pin_reset; + struct GpioPinSpec pin_interrupt; +}; diff --git a/Drivers/st77922-module/include/st77922_module.h b/Drivers/st77922-module/include/st77922_module.h new file mode 100644 index 00000000..73ef420f --- /dev/null +++ b/Drivers/st77922-module/include/st77922_module.h @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +extern Module st77922_module; diff --git a/Drivers/st77922-module/source/module.cpp b/Drivers/st77922-module/source/module.cpp new file mode 100644 index 00000000..30051012 --- /dev/null +++ b/Drivers/st77922-module/source/module.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +extern "C" { + +extern Driver st77922_driver; +extern Driver st77922_touch_driver; + +static Driver* const st77922_drivers[] = { + &st77922_driver, + &st77922_touch_driver, + nullptr +}; + +Module st77922_module = { + .name = "st77922", + .drivers = st77922_drivers +}; + +} // extern "C" diff --git a/Drivers/st77922-module/source/st77922.cpp b/Drivers/st77922-module/source/st77922.cpp new file mode 100644 index 00000000..f640bb9f --- /dev/null +++ b/Drivers/st77922-module/source/st77922.cpp @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include "st77922_init.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "ST77922" +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct St77922Internal { + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_panel_handle_t panel_handle; + SemaphoreHandle_t draw_done; + uint8_t* transfer_buffer; + size_t transfer_buffer_size; +}; + +static bool IRAM_ATTR transfer_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void* context) { + auto* internal = static_cast(context); + BaseType_t task_woken = pdFALSE; + xSemaphoreGiveFromISR(internal->draw_done, &task_woken); + return task_woken == pdTRUE; +} + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &SPI_CONTROLLER_TYPE); + const auto* spi = static_cast(parent->config); + const auto* config = GET_CONFIG(device); + + GpioPinSpec cs; + if (esp32_spi_get_cs_pin(device, &cs) != ERROR_NONE) { + return ERROR_RESOURCE; + } + + auto* internal = static_cast(calloc(1, sizeof(St77922Internal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + internal->draw_done = xSemaphoreCreateBinary(); + if (internal->draw_done == nullptr) { + free(internal); + return ERROR_OUT_OF_MEMORY; + } + // The vendor port renders a complete frame in PSRAM, then copies it through a + // 1/10-frame DMA buffer. Besides making full-frame refresh possible, this keeps + // the panel's GRAM synchronized when animated objects invalidate old and new + // positions in separate LVGL regions. + const size_t bytes_per_pixel = config->bits_per_pixel / 8; + const size_t rows_per_transfer = config->vertical_resolution > 10 + ? config->vertical_resolution / 10 : config->vertical_resolution; + internal->transfer_buffer_size = + static_cast(config->horizontal_resolution) * rows_per_transfer * bytes_per_pixel; + if (spi->max_transfer_size > 0 + && internal->transfer_buffer_size > static_cast(spi->max_transfer_size)) { + internal->transfer_buffer_size = static_cast(spi->max_transfer_size); + } + internal->transfer_buffer = static_cast(heap_caps_malloc( + internal->transfer_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); + if (internal->transfer_buffer == nullptr) { + vSemaphoreDelete(internal->draw_done); + free(internal); + return ERROR_OUT_OF_MEMORY; + } + + esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = static_cast(cs.pin), + .dc_gpio_num = -1, + .spi_mode = 0, + .pclk_hz = config->pixel_clock_hz, + .trans_queue_depth = config->transaction_queue_depth, + .on_color_trans_done = transfer_done, + .user_ctx = internal, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .flags = { + .dc_high_on_cmd = 0, + .dc_low_on_data = 0, + .dc_low_on_param = 0, + .octal_mode = 0, + .quad_mode = 1, + .sio_mode = 0, + .lsb_first = 0, + .cs_high_active = 0, + }, + }; + + esp_err_t result = esp_lcd_new_panel_io_spi( + static_cast(spi->host), &io_config, &internal->io_handle); + if (result != ESP_OK) { + LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(result)); + heap_caps_free(internal->transfer_buffer); + vSemaphoreDelete(internal->draw_done); + free(internal); + return ERROR_RESOURCE; + } + + size_t init_count = 0; + st77922_vendor_config_t vendor = { + .init_cmds = st77922_board_init_commands(&init_count), + .init_cmds_size = static_cast(init_count), + .flags = { .use_qspi_interface = 1 }, + }; + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = -1, + .rgb_ele_order = config->bgr_order ? LCD_RGB_ELEMENT_ORDER_BGR : LCD_RGB_ELEMENT_ORDER_RGB, + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, + .bits_per_pixel = config->bits_per_pixel, + .flags = { .reset_active_high = false }, + .vendor_config = &vendor, + }; + result = esp_lcd_new_panel_st77922(internal->io_handle, &panel_config, &internal->panel_handle); + bool ok = result == ESP_OK; + ok = ok && esp_lcd_panel_reset(internal->panel_handle) == ESP_OK; + ok = ok && esp_lcd_panel_init(internal->panel_handle) == ESP_OK; + ok = ok && ((!config->mirror_x && !config->mirror_y) + || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK); + ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); + ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; + if (!ok) { + LOG_E(TAG, "Failed to bring up panel: %s", esp_err_to_name(result)); + if (internal->panel_handle != nullptr) esp_lcd_panel_del(internal->panel_handle); + esp_lcd_panel_io_del(internal->io_handle); + heap_caps_free(internal->transfer_buffer); + vSemaphoreDelete(internal->draw_done); + free(internal); + return ERROR_RESOURCE; + } + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK + || esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { + return ERROR_RESOURCE; + } + heap_caps_free(internal->transfer_buffer); + vSemaphoreDelete(internal->draw_done); + free(internal); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +static error_t reset(Device* device) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_reset(data->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static error_t init(Device* device) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_init(data->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static error_t draw_bitmap(Device* device, int32_t xs, int32_t ys, int32_t xe, int32_t ye, const void* pixels) { + auto* data = static_cast(device_get_driver_data(device)); + const auto* config = GET_CONFIG(device); + const size_t row_bytes = static_cast(xe - xs) * config->bits_per_pixel / 8; + if (row_bytes == 0 || row_bytes > data->transfer_buffer_size) { + return ERROR_OUT_OF_RANGE; + } + + const size_t rows_per_transfer = data->transfer_buffer_size / row_bytes; + const auto* source = static_cast(pixels); + int32_t chunk_y = ys; + while (chunk_y < ye) { + const size_t remaining_rows = static_cast(ye - chunk_y); + const size_t chunk_rows = remaining_rows < rows_per_transfer ? remaining_rows : rows_per_transfer; + const size_t chunk_bytes = chunk_rows * row_bytes; + memcpy(data->transfer_buffer, source, chunk_bytes); + + xSemaphoreTake(data->draw_done, 0); + if (esp_lcd_panel_draw_bitmap( + data->panel_handle, xs, chunk_y, xe, chunk_y + static_cast(chunk_rows), + data->transfer_buffer) != ESP_OK) { + LOG_E(TAG, "Failed to queue color transfer at y=%ld", static_cast(chunk_y)); + return ERROR_RESOURCE; + } + if (xSemaphoreTake(data->draw_done, pdMS_TO_TICKS(1000)) != pdTRUE) { + LOG_E(TAG, "Timed out waiting for color transfer at y=%ld", static_cast(chunk_y)); + return ERROR_TIMEOUT; + } + + source += chunk_bytes; + chunk_y += static_cast(chunk_rows); + } + return ERROR_NONE; +} +static error_t mirror(Device* device, bool x, bool y) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_mirror(data->panel_handle, x, y) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static bool get_mirror_x(Device* device) { return GET_CONFIG(device)->mirror_x; } +static bool get_mirror_y(Device* device) { return GET_CONFIG(device)->mirror_y; } +static error_t invert(Device* device, bool value) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_invert_color(data->panel_handle, value) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static error_t on_off(Device* device, bool value) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_disp_on_off(data->panel_handle, value) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static error_t sleep(Device* device, bool value) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_disp_sleep(data->panel_handle, value) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static DisplayColorFormat color_format(Device*) { return DISPLAY_COLOR_FORMAT_RGB565_SWAPPED; } +static uint16_t resolution_x(Device* device) { return GET_CONFIG(device)->horizontal_resolution; } +static uint16_t resolution_y(Device* device) { return GET_CONFIG(device)->vertical_resolution; } +static void frame_buffer(Device*, uint8_t, void** output) { *output = nullptr; } +static uint8_t frame_buffer_count(Device*) { return 0; } +static error_t backlight(Device* device, Device** output) { + *output = GET_CONFIG(device)->backlight; + return *output == nullptr ? ERROR_NOT_SUPPORTED : ERROR_NONE; +} + +static const DisplayApi display_api = { + .capabilities = DISPLAY_CAPABILITY_CAP_MIRROR | DISPLAY_CAPABILITY_INVERT_COLOR | + DISPLAY_CAPABILITY_ON_OFF | DISPLAY_CAPABILITY_SLEEP | DISPLAY_CAPABILITY_BACKLIGHT | + DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME, + .reset = reset, + .init = init, + .draw_bitmap = draw_bitmap, + .mirror = mirror, + .swap_xy = nullptr, + .get_swap_xy = nullptr, + .get_mirror_x = get_mirror_x, + .get_mirror_y = get_mirror_y, + .set_gap = nullptr, + .get_gap_x = nullptr, + .get_gap_y = nullptr, + .invert_color = invert, + .disp_on_off = on_off, + .disp_sleep = sleep, + .get_color_format = color_format, + .get_resolution_x = resolution_x, + .get_resolution_y = resolution_y, + .get_frame_buffer = frame_buffer, + .get_frame_buffer_count = frame_buffer_count, + .get_backlight = backlight, + .has_capability = nullptr, +}; + +Driver st77922_driver = { + .name = "st77922", + .compatible = (const char*[]) { "sitronix,st77922", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &display_api, + .device_type = &DISPLAY_TYPE, + .owner = &st77922_module, + .internal = nullptr +}; diff --git a/Drivers/st77922-module/source/st77922_init.c b/Drivers/st77922-module/source/st77922_init.c new file mode 100644 index 00000000..6c68a0ef --- /dev/null +++ b/Drivers/st77922-module/source/st77922_init.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "st77922_init.h" + +// Initialization sequence supplied with the LCDWIKI/Hosyond ES3C35P vendor ESP-IDF demo. +static const st77922_lcd_init_cmd_t init_commands[] = { + {0xF1, (uint8_t []){0x00}, 1, 0}, + {0x60, (uint8_t []){0x00, 0x00, 0x00}, 3, 0}, + {0x65, (uint8_t []){0x80}, 1, 0}, + {0x79, (uint8_t []){0x06}, 1, 0}, + {0x7B, (uint8_t []){0x00, 0x08, 0x08}, 3, 0}, + {0x80, (uint8_t []){0x55, 0x62, 0x2F, 0x17, 0xF0, 0x52, 0x70, 0xD2, 0x52, 0x62, 0xEA}, 11, 0}, + {0x81, (uint8_t []){0x26, 0x52, 0x72, 0x27}, 4, 0}, + {0x84, (uint8_t []){0x92, 0x25}, 2, 0}, + {0x87, (uint8_t []){0x10, 0x10, 0x58, 0x00, 0x02, 0x3A}, 6, 0}, + {0x88, (uint8_t []){0x00, 0x00, 0x2C, 0x10, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x06}, 15, 0}, + {0x89, (uint8_t []){0x00, 0x00, 0x00}, 3, 0}, + {0x8A, (uint8_t []){0x13, 0x00, 0x2C, 0x00, 0x00, 0x2C, 0x10, 0x10, 0x00, 0x3E, 0x19}, 11, 0}, + {0x8B, (uint8_t []){0x15, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x97, 0x8E}, 9, 0}, + {0x8C, (uint8_t []){0x1D, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x50, 0x0F, 0x01, 0xC5, 0x12, 0x09}, 13, 0}, + {0x8D, (uint8_t []){0x0C}, 1, 0}, + {0x8E, (uint8_t []){0x33, 0x01, 0x0C, 0x13, 0x01, 0x01}, 6, 0}, + {0xB3, (uint8_t []){0x00, 0x30}, 2, 0}, + {0xF1, (uint8_t []){0x00}, 1, 0}, + {0x71, (uint8_t []){0xD0}, 1, 0}, + {0x66, (uint8_t []){0x02, 0x3F}, 2, 0}, + {0xBE, (uint8_t []){0x26, 0x00, 0x9D}, 3, 0}, + {0x70, (uint8_t []){0x01, 0xA0, 0x11, 0x40, 0xE0, 0x00, 0x11, 0x69, 0x11, 0x00, 0x00, 0x1A}, 12, 0}, + {0x90, (uint8_t []){0x04, 0x04, 0x55, 0x74, 0x00, 0x40, 0x43, 0x27, 0x27}, 9, 0}, + {0x91, (uint8_t []){0x04, 0x04, 0x55, 0x75, 0x00, 0x40, 0x42, 0x27, 0x27}, 9, 0}, + {0x92, (uint8_t []){0x04, 0x44, 0x55, 0xC0, 0x06, 0x00, 0x07, 0x05, 0x90, 0x27}, 10, 0}, + {0x93, (uint8_t []){0x04, 0x43, 0x11, 0x00, 0x00, 0x00, 0x00, 0x05, 0x90, 0x27}, 10, 0}, + {0x94, (uint8_t []){0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 6, 0}, + {0x95, (uint8_t []){0x96, 0x16, 0x00, 0x00, 0xFF}, 5, 0}, + {0x96, (uint8_t []){0x44, 0x53, 0x03, 0x12, 0x23, 0x24, 0x06, 0x05, 0x94, 0x27, 0x00, 0x44}, 12, 0}, + {0x97, (uint8_t []){0x44, 0x53, 0x47, 0x56, 0x20, 0x20, 0x02, 0x01, 0x94, 0x27, 0x00, 0x44}, 12, 0}, + {0xBA, (uint8_t []){0x55, 0x94, 0x2D, 0x94, 0x27}, 5, 0}, + {0x9A, (uint8_t []){0x40, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x9B, (uint8_t []){0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x9C, (uint8_t []){0x5C, 0x12, 0x00, 0x00, 0x10, 0x12, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00}, 13, 0}, + {0x9D, (uint8_t []){0x8A, 0x51, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 8, 0}, + {0x9E, (uint8_t []){0x51, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 7, 0}, + {0xB4, (uint8_t []){0x1D, 0x1C, 0x1E, 0x0B, 0x14, 0x02, 0x13, 0x09, 0x1E, 0x00, 0x1E, 0x10}, 12, 0}, + {0xB5, (uint8_t []){0x1D, 0x1C, 0x1E, 0x0A, 0x15, 0x03, 0x11, 0x08, 0x1E, 0x01, 0x1E, 0x12}, 12, 0}, + {0xB6, (uint8_t []){0x77, 0x77, 0x00, 0x0A, 0xFF, 0x0A, 0xFF}, 7, 0}, + {0x86, (uint8_t []){0xCD, 0x04, 0xB1, 0x02, 0x58, 0x12, 0x58, 0x0C, 0x13, 0x01, 0xA5, 0x00, 0xA5, 0xA5}, 14, 0}, + {0xB7, (uint8_t []){0x07, 0x0A, 0x0E, 0x06, 0x05, 0x03, 0x2B, 0x03, 0x03, 0x42, 0x07, 0x10, 0x10, 0x2E, 0x3F, 0x0D}, 16, 0}, + {0xB8, (uint8_t []){0x07, 0x0A, 0x0D, 0x05, 0x05, 0x02, 0x2B, 0x02, 0x03, 0x42, 0x06, 0x10, 0x0F, 0x2E, 0x3F, 0x0D}, 16, 0}, + {0xB9, (uint8_t []){0x23, 0x23}, 2, 0}, + {0xBF, (uint8_t []){0x10, 0x14, 0x14, 0x0B, 0x0B, 0x0B}, 6, 0}, + {0xF2, (uint8_t []){0x00}, 1, 0}, + {0x73, (uint8_t []){0x04, 0xDA, 0x12, 0x54, 0x47}, 5, 0}, + {0x77, (uint8_t []){0x6B, 0x5B, 0xFD, 0xC3, 0xC5}, 5, 0}, + {0x7A, (uint8_t []){0x15, 0x27}, 2, 0}, + {0x7B, (uint8_t []){0x04, 0x57}, 2, 0}, + {0x7E, (uint8_t []){0x01, 0x0E}, 2, 0}, + {0xBF, (uint8_t []){0x36}, 1, 0}, + {0xE3, (uint8_t []){0x40, 0x40}, 2, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0xD0, (uint8_t []){0x00}, 1, 0}, + {0x2A, (uint8_t []){0x00, 0x00, 0x01, 0x3F}, 4, 0}, + {0x2B, (uint8_t []){0x00, 0x00, 0x01, 0xDF}, 4, 0}, + {0x21, NULL, 0, 0}, + {0x11, NULL, 0, 120}, + {0x29, NULL, 0, 0}, + {0x2C, NULL, 0, 0}, + {0x3A, (uint8_t []){0x01}, 1, 0}, + {0x36, (uint8_t []){0x00}, 1, 0}, + {0x35, (uint8_t []){0x01}, 1, 20}, +}; + +const st77922_lcd_init_cmd_t* st77922_board_init_commands(size_t* count) { + *count = sizeof(init_commands) / sizeof(init_commands[0]); + return init_commands; +} diff --git a/Drivers/st77922-module/source/st77922_init.h b/Drivers/st77922-module/source/st77922_init.h new file mode 100644 index 00000000..a80e7def --- /dev/null +++ b/Drivers/st77922-module/source/st77922_init.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +const st77922_lcd_init_cmd_t* st77922_board_init_commands(size_t* count); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/st77922-module/source/st77922_touch.cpp b/Drivers/st77922-module/source/st77922_touch.cpp new file mode 100644 index 00000000..920712e4 --- /dev/null +++ b/Drivers/st77922-module/source/st77922_touch.cpp @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TAG "ST77922 touch" +#define GET_CONFIG(device) (static_cast((device)->config)) + +static constexpr uint16_t REG_MAX_TOUCHES = 0x0009; +static constexpr uint16_t REG_TOUCH_INFO = 0x0010; +static constexpr uint16_t REG_TOUCH_POINT0 = 0x0014; +static constexpr uint8_t MAX_POINTS = 10; +static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(1000); + +struct TouchPoint { + uint16_t x; + uint16_t y; +}; + +struct St77922TouchInternal { + Device* i2c; + GpioDescriptor* reset; + uint8_t supported_points; + uint8_t point_count; + TouchPoint points[MAX_POINTS]; + bool swap_xy; + bool mirror_x; + bool mirror_y; +}; + +static error_t read_register(Device* i2c, uint16_t reg, uint8_t* data, size_t size, TickType_t timeout) { + const uint8_t address[] = { + static_cast(reg >> 8), + static_cast(reg & 0xFF), + }; + return i2c_controller_write_read(i2c, 0x55, address, sizeof(address), data, size, timeout); +} + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &I2C_CONTROLLER_TYPE); + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(calloc(1, sizeof(St77922TouchInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + internal->i2c = parent; + internal->supported_points = 1; + internal->swap_xy = config->swap_xy; + internal->mirror_x = config->mirror_x; + internal->mirror_y = config->mirror_y; + + if (config->pin_reset.gpio_controller != nullptr) { + internal->reset = gpio_descriptor_acquire(config->pin_reset.gpio_controller, + config->pin_reset.pin, GPIO_FLAG_DIRECTION_OUTPUT | GPIO_FLAG_ACTIVE_LOW, GPIO_OWNER_GPIO); + if (internal->reset == nullptr) { + free(internal); + return ERROR_RESOURCE; + } + if (gpio_descriptor_set_level(internal->reset, true) != ERROR_NONE) { + gpio_descriptor_release(internal->reset); + free(internal); + return ERROR_RESOURCE; + } + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_descriptor_set_level(internal->reset, false); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + uint8_t supported = 0; + if (read_register(parent, REG_MAX_TOUCHES, &supported, 1, TIMEOUT) == ERROR_NONE + && supported > 0 && supported <= MAX_POINTS) { + internal->supported_points = supported; + } + LOG_I(TAG, "Controller reports %u touch points", internal->supported_points); + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + if (internal->reset != nullptr) { + gpio_descriptor_release(internal->reset); + } + free(internal); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +static error_t enter_sleep(Device*) { return ERROR_NOT_SUPPORTED; } +static error_t exit_sleep(Device*) { return ERROR_NOT_SUPPORTED; } + +static error_t read_data(Device* device, TickType_t timeout) { + auto* internal = static_cast(device_get_driver_data(device)); + uint8_t touch_info = 0; + if (read_register(internal->i2c, REG_TOUCH_INFO, &touch_info, 1, timeout) != ERROR_NONE) { + internal->point_count = 0; + return ERROR_RESOURCE; + } + if ((touch_info & 0x08) == 0) { + internal->point_count = 0; + return ERROR_NONE; + } + + uint8_t data[7 * MAX_POINTS] = {}; + const size_t read_size = 7 * internal->supported_points; + if (read_register(internal->i2c, REG_TOUCH_POINT0, data, read_size, timeout) != ERROR_NONE) { + internal->point_count = 0; + return ERROR_RESOURCE; + } + + internal->point_count = 0; + for (uint8_t index = 0; index < internal->supported_points; index++) { + const uint8_t offset = index * 7; + if ((data[offset] & 0x80) == 0) { + continue; + } + internal->points[internal->point_count++] = { + .x = static_cast(((data[offset] & 0x3F) << 8) | data[offset + 1]), + .y = static_cast(((data[offset + 2] & 0x3F) << 8) | data[offset + 3]), + }; + } + return ERROR_NONE; +} + +static bool get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, + uint8_t* count, uint8_t maximum) { + auto* internal = static_cast(device_get_driver_data(device)); + const auto* config = GET_CONFIG(device); + *count = std::min(internal->point_count, maximum); + for (uint8_t index = 0; index < *count; index++) { + uint16_t point_x = internal->points[index].x; + uint16_t point_y = internal->points[index].y; + if (internal->swap_xy) { + std::swap(point_x, point_y); + } + const uint16_t max_x = internal->swap_xy ? config->y_max : config->x_max; + const uint16_t max_y = internal->swap_xy ? config->x_max : config->y_max; + x[index] = internal->mirror_x ? max_x - point_x : point_x; + y[index] = internal->mirror_y ? max_y - point_y : point_y; + if (strength != nullptr) { + strength[index] = 0; + } + } + return *count > 0; +} + +static error_t set_swap_xy(Device* device, bool value) { + static_cast(device_get_driver_data(device))->swap_xy = value; + return ERROR_NONE; +} +static error_t get_swap_xy(Device* device, bool* value) { + *value = static_cast(device_get_driver_data(device))->swap_xy; + return ERROR_NONE; +} +static error_t set_mirror_x(Device* device, bool value) { + static_cast(device_get_driver_data(device))->mirror_x = value; + return ERROR_NONE; +} +static error_t get_mirror_x(Device* device, bool* value) { + *value = static_cast(device_get_driver_data(device))->mirror_x; + return ERROR_NONE; +} +static error_t set_mirror_y(Device* device, bool value) { + static_cast(device_get_driver_data(device))->mirror_y = value; + return ERROR_NONE; +} +static error_t get_mirror_y(Device* device, bool* value) { + *value = static_cast(device_get_driver_data(device))->mirror_y; + return ERROR_NONE; +} + +static const PointerApi pointer_api = { + .enter_sleep = enter_sleep, + .exit_sleep = exit_sleep, + .read_data = read_data, + .get_touched_points = get_touched_points, + .set_swap_xy = set_swap_xy, + .get_swap_xy = get_swap_xy, + .set_mirror_x = set_mirror_x, + .get_mirror_x = get_mirror_x, + .set_mirror_y = set_mirror_y, + .get_mirror_y = get_mirror_y, +}; + +Driver st77922_touch_driver = { + .name = "st77922-touch", + .compatible = (const char*[]) { "sitronix,st77922-touch", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &pointer_api, + .device_type = &POINTER_TYPE, + .owner = &st77922_module, + .internal = nullptr +}; diff --git a/Modules/app-module/source/scheduler.cpp b/Modules/app-module/source/scheduler.cpp index a628f406..0d4f168c 100644 --- a/Modules/app-module/source/scheduler.cpp +++ b/Modules/app-module/source/scheduler.cpp @@ -32,8 +32,10 @@ constexpr size_t APP_INSTANCE_ID_THREAD_SLOT_INDEX = 1; // Matches TactilityKernel's Thread wrapper's THREAD_PRIORITY_NORMAL. constexpr UBaseType_t APP_TASK_PRIORITY = 4; -// Used when an app's manifest doesn't request a specific stack depth (0). 8192 bytes' worth. -constexpr size_t APP_DEFAULT_STACK_DEPTH = 8192 / sizeof(StackType_t); +// Used when an app's manifest doesn't request a specific stack depth (0). 12288 bytes' worth. +// Existing window-manager app manifests request at least 2400 words (9600 bytes); use a +// conservative default above that baseline so a zero-depth app task cannot exhaust its stack. +constexpr size_t APP_DEFAULT_STACK_DEPTH = 12288 / sizeof(StackType_t); // Task control blocks must stay in internal RAM; only the stack itself may live in external memory. constexpr MemoryPolicy APP_TASK_TCB_POLICY = { MEMORY_CAPABILITY_INTERNAL, 0, 0 }; diff --git a/Modules/lvgl-module/include/lvgl/fonts.h b/Modules/lvgl-module/include/lvgl/fonts.h index b2d8fdbe..27bd428c 100644 --- a/Modules/lvgl-module/include/lvgl/fonts.h +++ b/Modules/lvgl-module/include/lvgl/fonts.h @@ -14,6 +14,15 @@ enum LvglFontSize { FONT_SIZE_LARGE, }; +enum LvglFontScale { + FONT_SCALE_SMALL, + FONT_SCALE_DEFAULT, + FONT_SCALE_LARGE, +}; + +void lvgl_set_text_font_scale(enum LvglFontScale font_scale); +enum LvglFontScale lvgl_get_text_font_scale(void); + const lv_font_t* lvgl_get_shared_icon_font(void); uint32_t lvgl_get_shared_icon_font_height(void); diff --git a/Modules/lvgl-module/source/fonts.c b/Modules/lvgl-module/source/fonts.c index 96812d08..a4e863be 100644 --- a/Modules/lvgl-module/source/fonts.c +++ b/Modules/lvgl-module/source/fonts.c @@ -13,8 +13,30 @@ extern const lv_font_t TT_LVGL_LAUNCHER_FONT_ICON_SYMBOL; extern const lv_font_t TT_LVGL_STATUSBAR_FONT_ICON_SYMBOL; extern const lv_font_t TT_LVGL_SHARED_FONT_ICON_SYMBOL; +static enum LvglFontScale text_font_scale = FONT_SCALE_DEFAULT; + +void lvgl_set_text_font_scale(enum LvglFontScale font_scale) { + check(font_scale >= FONT_SCALE_SMALL && font_scale <= FONT_SCALE_LARGE); + text_font_scale = font_scale; +} + +enum LvglFontScale lvgl_get_text_font_scale(void) { + return text_font_scale; +} + +static enum LvglFontSize resolve_text_font_size(enum LvglFontSize font_size) { + int resolved = (int)font_size + (int)text_font_scale - (int)FONT_SCALE_DEFAULT; + if (resolved < FONT_SIZE_SMALL) { + return FONT_SIZE_SMALL; + } + if (resolved > FONT_SIZE_LARGE) { + return FONT_SIZE_LARGE; + } + return (enum LvglFontSize)resolved; +} + uint32_t lvgl_get_text_font_height(enum LvglFontSize font_size) { - switch (font_size) { + switch (resolve_text_font_size(font_size)) { case FONT_SIZE_SMALL: return TT_LVGL_TEXT_FONT_SMALL_SIZE; case FONT_SIZE_DEFAULT: return TT_LVGL_TEXT_FONT_DEFAULT_SIZE; case FONT_SIZE_LARGE: return TT_LVGL_TEXT_FONT_LARGE_SIZE; @@ -22,7 +44,7 @@ uint32_t lvgl_get_text_font_height(enum LvglFontSize font_size) { } } const lv_font_t* lvgl_get_text_font(enum LvglFontSize font_size) { - switch (font_size) { + switch (resolve_text_font_size(font_size)) { case FONT_SIZE_SMALL: return &TT_LVGL_TEXT_FONT_SMALL_SYMBOL; case FONT_SIZE_DEFAULT: return &TT_LVGL_TEXT_FONT_DEFAULT_SYMBOL; case FONT_SIZE_LARGE: return &TT_LVGL_TEXT_FONT_LARGE_SYMBOL; diff --git a/Modules/lvgl-module/source/symbols.c b/Modules/lvgl-module/source/symbols.c index 56b5103b..efd29e3d 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -26,6 +26,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lvgl_module), DEFINE_MODULE_SYMBOL(lvgl_module_configure), // lvgl_fonts + DEFINE_MODULE_SYMBOL(lvgl_set_text_font_scale), + DEFINE_MODULE_SYMBOL(lvgl_get_text_font_scale), DEFINE_MODULE_SYMBOL(lvgl_get_shared_icon_font), DEFINE_MODULE_SYMBOL(lvgl_get_shared_icon_font_height), DEFINE_MODULE_SYMBOL(lvgl_get_text_font), @@ -494,6 +496,12 @@ const struct ModuleSymbol lvgl_module_symbols[] = { // lv_image DEFINE_MODULE_SYMBOL(lv_image_create), DEFINE_MODULE_SYMBOL(lv_image_set_src), + DEFINE_MODULE_SYMBOL(lv_image_set_scale), + DEFINE_MODULE_SYMBOL(lv_image_set_scale_x), + DEFINE_MODULE_SYMBOL(lv_image_set_scale_y), + DEFINE_MODULE_SYMBOL(lv_image_get_scale), + DEFINE_MODULE_SYMBOL(lv_image_get_scale_x), + DEFINE_MODULE_SYMBOL(lv_image_get_scale_y), // lv_anim DEFINE_MODULE_SYMBOL(lv_anim_init), DEFINE_MODULE_SYMBOL(lv_anim_set_duration), diff --git a/Tactility/Include/Tactility/lvgl/Lvgl.h b/Tactility/Include/Tactility/lvgl/Lvgl.h index 6e8bfbec..22d72a5c 100644 --- a/Tactility/Include/Tactility/lvgl/Lvgl.h +++ b/Tactility/Include/Tactility/lvgl/Lvgl.h @@ -1,5 +1,9 @@ #pragma once +namespace tt::settings::display { +enum class FontSize; +} + namespace tt::lvgl { #ifdef ESP_PLATFORM @@ -12,6 +16,9 @@ static constexpr auto* PATH_PREFIX = "A:/"; bool isStarted(); +/** Applies the selected semantic text size to the LVGL theme and future widgets. */ +void applyFontSize(settings::display::FontSize fontSize); + void start(); void stop(); diff --git a/Tactility/Include/Tactility/settings/DisplaySettings.h b/Tactility/Include/Tactility/settings/DisplaySettings.h index da0e0ba8..d86d2cba 100644 --- a/Tactility/Include/Tactility/settings/DisplaySettings.h +++ b/Tactility/Include/Tactility/settings/DisplaySettings.h @@ -18,11 +18,20 @@ enum class ScreensaverType { Mystify, MatrixRain, StackChan, + McpScreen, Count // Sentinel for bounds checking - must be last }; +enum class FontSize { + Small, + Default, + Large, + Count +}; + struct DisplaySettings { Orientation orientation; + FontSize fontSize = FontSize::Default; uint8_t gammaCurve; uint8_t backlightDuty; bool backlightTimeoutEnabled; diff --git a/Tactility/Include/Tactility/settings/McpSettings.h b/Tactility/Include/Tactility/settings/McpSettings.h new file mode 100644 index 00000000..4e614536 --- /dev/null +++ b/Tactility/Include/Tactility/settings/McpSettings.h @@ -0,0 +1,14 @@ +#pragma once + +namespace tt::settings::mcp { + +struct McpSettings { + bool mcpEnabled = false; // Enable MCP server endpoints on system web server +}; + +bool load(McpSettings& settings); +McpSettings getDefault(); +McpSettings loadOrGetDefault(); +bool save(const McpSettings& settings); + +} // namespace tt::settings::mcp diff --git a/Tactility/Private/Tactility/mcp/McpSystem.h b/Tactility/Private/Tactility/mcp/McpSystem.h new file mode 100644 index 00000000..8ba0d95f --- /dev/null +++ b/Tactility/Private/Tactility/mcp/McpSystem.h @@ -0,0 +1,77 @@ +#pragma once +#ifdef ESP_PLATFORM + +#include +#include +#include +#include +#include +#include + +namespace tt::mcp { + +struct McpSystemState { + std::mutex mutex; + bool overrideActive = false; + + // UI elements when McpOverrideApp is active + lv_obj_t* drawArea = nullptr; + uint16_t* framebuffer = nullptr; + size_t framebufferSize = 0; + uint16_t displayWidth = 320; + uint16_t displayHeight = 240; + uint16_t drawWidth = 320; + uint16_t drawHeight = 240; + int drawColor = 1; // 0 = white, 1 = black + + // Audio device status + Device* i2sDevice = nullptr; + Device* audioStreamDevice = nullptr; + AudioStreamHandle audioHandle = nullptr; + volatile bool audioBusy = false; + volatile bool audioRunning = false; // Used to abort play/record loop + + // Video streaming state + volatile bool streamRunning = false; + void* streamTaskHandle = nullptr; // use void* to avoid freertos header inclusion dependency + uint32_t framesDrawn = 0; + uint32_t tcpBytesReceived = 0; + uint32_t lastDrawMs = 0; + double lastFps = 0.0; +}; + +McpSystemState& getState(); + +bool clearScreen(int color); +bool drawText(const std::string& text, int x, int y, int size); +bool drawRgb565(const uint8_t* data, size_t size, int x, int y, int w, int h); +bool drawBmp(const uint8_t* data, size_t size, int x, int y); +bool drawPbm(const uint8_t* data, size_t size, int x, int y); +std::string getScreenshotPbmBase64(); + +bool playTone(int frequency, int durationMs, int volume, std::string& error); +bool recordVoice(int durationSec, const std::string& filename, size_t& recordedBytes, std::string& error); +bool playAudioFile(const std::string& filename, int volume, std::string& error); +bool playWavMemory(const uint8_t* data, size_t size, int volume, std::string& error); +bool playMp3File(const std::string& filename, int volume, std::string& error); +bool playMp3Memory(const uint8_t* data, size_t size, int volume, std::string& error); + +bool getBatteryStatus(double& voltage_v, int& percentage_pct, std::string& error); +bool setLedColor(int r, int g, int b, const std::string& mode, std::string& error); +bool getSensors(double& temp_c, double& hum_pct, std::string& imu_json, std::string& error); +bool scanBleDevices(int duration_ms, std::string& devices_json, std::string& error); +bool writeSdFile(const std::string& filename, const std::string& content, std::string& error); +bool readSdFile(const std::string& filename, std::string& content, std::string& error); +bool downloadSdFile(const std::string& url, const std::string& filename, std::string& error); + +bool startVideoStreamServer(); +void stopVideoStreamServer(); +bool getVideoStreamStats(std::string& stats_json); + +bool listApps(std::string& apps_json, std::string& error); +bool runApp(const std::string& appId, std::string& error); +bool listSdFiles(const std::string& directory, std::string& files_json, std::string& error); + +} // namespace tt::mcp + +#endif diff --git a/Tactility/Private/Tactility/mcp/minimp3.h b/Tactility/Private/Tactility/mcp/minimp3.h new file mode 100644 index 00000000..9002b68d --- /dev/null +++ b/Tactility/Private/Tactility/mcp/minimp3.h @@ -0,0 +1,1869 @@ +#ifndef MINIMP3_H +#define MINIMP3_H +/* + https://github.com/lieff/minimp3 + To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. + This software is distributed without any warranty. + See . +*/ +#include + +#define MINIMP3_MAX_SAMPLES_PER_FRAME (1152*2) + +typedef struct +{ + int frame_bytes, frame_offset, channels, hz, layer, bitrate_kbps; +} mp3dec_frame_info_t; + +typedef struct +{ + float mdct_overlap[2][9*32], qmf_state[15*2*32]; + int reserv, free_format_bytes; + unsigned char header[4], reserv_buf[511]; + float scratch_padding[4100]; +} mp3dec_t; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void mp3dec_init(mp3dec_t *dec); +#ifndef MINIMP3_FLOAT_OUTPUT +typedef int16_t mp3d_sample_t; +#else /* MINIMP3_FLOAT_OUTPUT */ +typedef float mp3d_sample_t; +void mp3dec_f32_to_s16(const float *in, int16_t *out, int num_samples); +#endif /* MINIMP3_FLOAT_OUTPUT */ +int mp3dec_decode_frame(mp3dec_t *dec, const uint8_t *mp3, int mp3_bytes, mp3d_sample_t *pcm, mp3dec_frame_info_t *info); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* MINIMP3_H */ +#if defined(MINIMP3_IMPLEMENTATION) && !defined(_MINIMP3_IMPLEMENTATION_GUARD) +#define _MINIMP3_IMPLEMENTATION_GUARD + +#include +#include + +#define MAX_FREE_FORMAT_FRAME_SIZE 2304 /* more than ISO spec's */ +#ifndef MAX_FRAME_SYNC_MATCHES +#define MAX_FRAME_SYNC_MATCHES 10 +#endif /* MAX_FRAME_SYNC_MATCHES */ + +#define MAX_L3_FRAME_PAYLOAD_BYTES MAX_FREE_FORMAT_FRAME_SIZE /* MUST be >= 320000/8/32000*1152 = 1440 */ + +#define MAX_BITRESERVOIR_BYTES 511 +#define SHORT_BLOCK_TYPE 2 +#define STOP_BLOCK_TYPE 3 +#define MODE_MONO 3 +#define MODE_JOINT_STEREO 1 +#define HDR_SIZE 4 +#define HDR_IS_MONO(h) (((h[3]) & 0xC0) == 0xC0) +#define HDR_IS_MS_STEREO(h) (((h[3]) & 0xE0) == 0x60) +#define HDR_IS_FREE_FORMAT(h) (((h[2]) & 0xF0) == 0) +#define HDR_IS_CRC(h) (!((h[1]) & 1)) +#define HDR_TEST_PADDING(h) ((h[2]) & 0x2) +#define HDR_TEST_MPEG1(h) ((h[1]) & 0x8) +#define HDR_TEST_NOT_MPEG25(h) ((h[1]) & 0x10) +#define HDR_TEST_I_STEREO(h) ((h[3]) & 0x10) +#define HDR_TEST_MS_STEREO(h) ((h[3]) & 0x20) +#define HDR_GET_STEREO_MODE(h) (((h[3]) >> 6) & 3) +#define HDR_GET_STEREO_MODE_EXT(h) (((h[3]) >> 4) & 3) +#define HDR_GET_LAYER(h) (((h[1]) >> 1) & 3) +#define HDR_GET_BITRATE(h) ((h[2]) >> 4) +#define HDR_GET_SAMPLE_RATE(h) (((h[2]) >> 2) & 3) +#define HDR_GET_MY_SAMPLE_RATE(h) (HDR_GET_SAMPLE_RATE(h) + (((h[1] >> 3) & 1) + ((h[1] >> 4) & 1))*3) +#define HDR_IS_FRAME_576(h) ((h[1] & 14) == 2) +#define HDR_IS_LAYER_1(h) ((h[1] & 6) == 6) + +#define BITS_DEQUANTIZER_OUT -1 +#define MAX_SCF (255 + BITS_DEQUANTIZER_OUT*4 - 210) +#define MAX_SCFI ((MAX_SCF + 3) & ~3) + +#define MINIMP3_MIN(a, b) ((a) > (b) ? (b) : (a)) +#define MINIMP3_MAX(a, b) ((a) < (b) ? (b) : (a)) + +#if !defined(MINIMP3_NO_SIMD) + +#if !defined(MINIMP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64)) +/* x64 always have SSE2, arm64 always have neon, no need for generic code */ +#define MINIMP3_ONLY_SIMD +#endif /* SIMD checks... */ + +#if (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || ((defined(__i386__) || defined(__x86_64__)) && defined(__SSE2__)) +#if defined(_MSC_VER) +#include +#endif /* defined(_MSC_VER) */ +#include +#define HAVE_SSE 1 +#define HAVE_SIMD 1 +#define VSTORE _mm_storeu_ps +#define VLD _mm_loadu_ps +#define VSET _mm_set1_ps +#define VADD _mm_add_ps +#define VSUB _mm_sub_ps +#define VMUL _mm_mul_ps +#define VMAC(a, x, y) _mm_add_ps(a, _mm_mul_ps(x, y)) +#define VMSB(a, x, y) _mm_sub_ps(a, _mm_mul_ps(x, y)) +#define VMUL_S(x, s) _mm_mul_ps(x, _mm_set1_ps(s)) +#define VREV(x) _mm_shuffle_ps(x, x, _MM_SHUFFLE(0, 1, 2, 3)) +typedef __m128 f4; +#if defined(_MSC_VER) || defined(MINIMP3_ONLY_SIMD) +#define minimp3_cpuid __cpuid +#else /* defined(_MSC_VER) || defined(MINIMP3_ONLY_SIMD) */ +static __inline__ __attribute__((always_inline)) void minimp3_cpuid(int CPUInfo[], const int InfoType) +{ +#if defined(__PIC__) + __asm__ __volatile__( +#if defined(__x86_64__) + "push %%rbx\n" + "cpuid\n" + "xchgl %%ebx, %1\n" + "pop %%rbx\n" +#else /* defined(__x86_64__) */ + "xchgl %%ebx, %1\n" + "cpuid\n" + "xchgl %%ebx, %1\n" +#endif /* defined(__x86_64__) */ + : "=a" (CPUInfo[0]), "=r" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) + : "a" (InfoType)); +#else /* defined(__PIC__) */ + __asm__ __volatile__( + "cpuid" + : "=a" (CPUInfo[0]), "=b" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) + : "a" (InfoType)); +#endif /* defined(__PIC__)*/ +} +#endif /* defined(_MSC_VER) || defined(MINIMP3_ONLY_SIMD) */ +static int have_simd(void) +{ +#ifdef MINIMP3_ONLY_SIMD + return 1; +#else /* MINIMP3_ONLY_SIMD */ + static int g_have_simd; + int CPUInfo[4]; +#ifdef MINIMP3_TEST + static int g_counter; + if (g_counter++ > 100) + return 0; +#endif /* MINIMP3_TEST */ + if (g_have_simd) + goto end; + minimp3_cpuid(CPUInfo, 0); + g_have_simd = 1; + if (CPUInfo[0] > 0) + { + minimp3_cpuid(CPUInfo, 1); + g_have_simd = (CPUInfo[3] & (1 << 26)) + 1; /* SSE2 */ + } +end: + return g_have_simd - 1; +#endif /* MINIMP3_ONLY_SIMD */ +} +#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) +#include +#define HAVE_SSE 0 +#define HAVE_SIMD 1 +#define VSTORE vst1q_f32 +#define VLD vld1q_f32 +#define VSET vmovq_n_f32 +#define VADD vaddq_f32 +#define VSUB vsubq_f32 +#define VMUL vmulq_f32 +#define VMAC(a, x, y) vmlaq_f32(a, x, y) +#define VMSB(a, x, y) vmlsq_f32(a, x, y) +#define VMUL_S(x, s) vmulq_f32(x, vmovq_n_f32(s)) +#define VREV(x) vcombine_f32(vget_high_f32(vrev64q_f32(x)), vget_low_f32(vrev64q_f32(x))) +typedef float32x4_t f4; +static int have_simd() +{ /* TODO: detect neon for !MINIMP3_ONLY_SIMD */ + return 1; +} +#else /* SIMD checks... */ +#define HAVE_SSE 0 +#define HAVE_SIMD 0 +#ifdef MINIMP3_ONLY_SIMD +#error MINIMP3_ONLY_SIMD used, but SSE/NEON not enabled +#endif /* MINIMP3_ONLY_SIMD */ +#endif /* SIMD checks... */ +#else /* !defined(MINIMP3_NO_SIMD) */ +#define HAVE_SIMD 0 +#endif /* !defined(MINIMP3_NO_SIMD) */ + +#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) +#define HAVE_ARMV6 1 +static __inline__ __attribute__((always_inline)) int32_t minimp3_clip_int16_arm(int32_t a) +{ + int32_t x = 0; + __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); + return x; +} +#else +#define HAVE_ARMV6 0 +#endif + +typedef struct +{ + const uint8_t *buf; + int pos, limit; +} bs_t; + +typedef struct +{ + float scf[3*64]; + uint8_t total_bands, stereo_bands, bitalloc[64], scfcod[64]; +} L12_scale_info; + +typedef struct +{ + uint8_t tab_offset, code_tab_width, band_count; +} L12_subband_alloc_t; + +typedef struct +{ + const uint8_t *sfbtab; + uint16_t part_23_length, big_values, scalefac_compress; + uint8_t global_gain, block_type, mixed_block_flag, n_long_sfb, n_short_sfb; + uint8_t table_select[3], region_count[3], subblock_gain[3]; + uint8_t preflag, scalefac_scale, count1_table, scfsi; +} L3_gr_info_t; + +typedef struct +{ + bs_t bs; + uint8_t maindata[MAX_BITRESERVOIR_BYTES + MAX_L3_FRAME_PAYLOAD_BYTES]; + L3_gr_info_t gr_info[4]; + float grbuf[2][576], scf[40], syn[18 + 15][2*32]; + uint8_t ist_pos[2][39]; +} mp3dec_scratch_t; + +static void bs_init(bs_t *bs, const uint8_t *data, int bytes) +{ + bs->buf = data; + bs->pos = 0; + bs->limit = bytes*8; +} + +static uint32_t get_bits(bs_t *bs, int n) +{ + uint32_t next, cache = 0, s = bs->pos & 7; + int shl = n + s; + const uint8_t *p = bs->buf + (bs->pos >> 3); + if ((bs->pos += n) > bs->limit) + return 0; + next = *p++ & (255 >> s); + while ((shl -= 8) > 0) + { + cache |= next << shl; + next = *p++; + } + return cache | (next >> -shl); +} + +static int hdr_valid(const uint8_t *h) +{ + return h[0] == 0xff && + ((h[1] & 0xF0) == 0xf0 || (h[1] & 0xFE) == 0xe2) && + (HDR_GET_LAYER(h) != 0) && + (HDR_GET_BITRATE(h) != 15) && + (HDR_GET_SAMPLE_RATE(h) != 3); +} + +static int hdr_compare(const uint8_t *h1, const uint8_t *h2) +{ + return hdr_valid(h2) && + ((h1[1] ^ h2[1]) & 0xFE) == 0 && + ((h1[2] ^ h2[2]) & 0x0C) == 0 && + !(HDR_IS_FREE_FORMAT(h1) ^ HDR_IS_FREE_FORMAT(h2)); +} + +static unsigned hdr_bitrate_kbps(const uint8_t *h) +{ + static const uint8_t halfrate[2][3][15] = { + { { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,16,24,28,32,40,48,56,64,72,80,88,96,112,128 } }, + { { 0,16,20,24,28,32,40,48,56,64,80,96,112,128,160 }, { 0,16,24,28,32,40,48,56,64,80,96,112,128,160,192 }, { 0,16,32,48,64,80,96,112,128,144,160,176,192,208,224 } }, + }; + return 2*halfrate[!!HDR_TEST_MPEG1(h)][HDR_GET_LAYER(h) - 1][HDR_GET_BITRATE(h)]; +} + +static unsigned hdr_sample_rate_hz(const uint8_t *h) +{ + static const unsigned g_hz[3] = { 44100, 48000, 32000 }; + return g_hz[HDR_GET_SAMPLE_RATE(h)] >> (int)!HDR_TEST_MPEG1(h) >> (int)!HDR_TEST_NOT_MPEG25(h); +} + +static unsigned hdr_frame_samples(const uint8_t *h) +{ + return HDR_IS_LAYER_1(h) ? 384 : (1152 >> (int)HDR_IS_FRAME_576(h)); +} + +static int hdr_frame_bytes(const uint8_t *h, int free_format_size) +{ + int frame_bytes = hdr_frame_samples(h)*hdr_bitrate_kbps(h)*125/hdr_sample_rate_hz(h); + if (HDR_IS_LAYER_1(h)) + { + frame_bytes &= ~3; /* slot align */ + } + return frame_bytes ? frame_bytes : free_format_size; +} + +static int hdr_padding(const uint8_t *h) +{ + return HDR_TEST_PADDING(h) ? (HDR_IS_LAYER_1(h) ? 4 : 1) : 0; +} + +#ifndef MINIMP3_ONLY_MP3 +static const L12_subband_alloc_t *L12_subband_alloc_table(const uint8_t *hdr, L12_scale_info *sci) +{ + const L12_subband_alloc_t *alloc; + int mode = HDR_GET_STEREO_MODE(hdr); + int nbands, stereo_bands = (mode == MODE_MONO) ? 0 : (mode == MODE_JOINT_STEREO) ? (HDR_GET_STEREO_MODE_EXT(hdr) << 2) + 4 : 32; + + if (HDR_IS_LAYER_1(hdr)) + { + static const L12_subband_alloc_t g_alloc_L1[] = { { 76, 4, 32 } }; + alloc = g_alloc_L1; + nbands = 32; + } else if (!HDR_TEST_MPEG1(hdr)) + { + static const L12_subband_alloc_t g_alloc_L2M2[] = { { 60, 4, 4 }, { 44, 3, 7 }, { 44, 2, 19 } }; + alloc = g_alloc_L2M2; + nbands = 30; + } else + { + static const L12_subband_alloc_t g_alloc_L2M1[] = { { 0, 4, 3 }, { 16, 4, 8 }, { 32, 3, 12 }, { 40, 2, 7 } }; + int sample_rate_idx = HDR_GET_SAMPLE_RATE(hdr); + unsigned kbps = hdr_bitrate_kbps(hdr) >> (int)(mode != MODE_MONO); + if (!kbps) /* free-format */ + { + kbps = 192; + } + + alloc = g_alloc_L2M1; + nbands = 27; + if (kbps < 56) + { + static const L12_subband_alloc_t g_alloc_L2M1_lowrate[] = { { 44, 4, 2 }, { 44, 3, 10 } }; + alloc = g_alloc_L2M1_lowrate; + nbands = sample_rate_idx == 2 ? 12 : 8; + } else if (kbps >= 96 && sample_rate_idx != 1) + { + nbands = 30; + } + } + + sci->total_bands = (uint8_t)nbands; + sci->stereo_bands = (uint8_t)MINIMP3_MIN(stereo_bands, nbands); + + return alloc; +} + +static void L12_read_scalefactors(bs_t *bs, uint8_t *pba, uint8_t *scfcod, int bands, float *scf) +{ + static const float g_deq_L12[18*3] = { +#define DQ(x) 9.53674316e-07f/x, 7.56931807e-07f/x, 6.00777173e-07f/x + DQ(3),DQ(7),DQ(15),DQ(31),DQ(63),DQ(127),DQ(255),DQ(511),DQ(1023),DQ(2047),DQ(4095),DQ(8191),DQ(16383),DQ(32767),DQ(65535),DQ(3),DQ(5),DQ(9) + }; + int i, m; + for (i = 0; i < bands; i++) + { + float s = 0; + int ba = *pba++; + int mask = ba ? 4 + ((19 >> scfcod[i]) & 3) : 0; + for (m = 4; m; m >>= 1) + { + if (mask & m) + { + int b = get_bits(bs, 6); + s = g_deq_L12[ba*3 - 6 + b % 3]*(1 << 21 >> b/3); + } + *scf++ = s; + } + } +} + +static void L12_read_scale_info(const uint8_t *hdr, bs_t *bs, L12_scale_info *sci) +{ + static const uint8_t g_bitalloc_code_tab[] = { + 0,17, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16, + 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,16, + 0,17,18, 3,19,4,5,16, + 0,17,18,16, + 0,17,18,19, 4,5,6, 7,8, 9,10,11,12,13,14,15, + 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,14, + 0, 2, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16 + }; + const L12_subband_alloc_t *subband_alloc = L12_subband_alloc_table(hdr, sci); + + int i, k = 0, ba_bits = 0; + const uint8_t *ba_code_tab = g_bitalloc_code_tab; + + for (i = 0; i < sci->total_bands; i++) + { + uint8_t ba; + if (i == k) + { + k += subband_alloc->band_count; + ba_bits = subband_alloc->code_tab_width; + ba_code_tab = g_bitalloc_code_tab + subband_alloc->tab_offset; + subband_alloc++; + } + ba = ba_code_tab[get_bits(bs, ba_bits)]; + sci->bitalloc[2*i] = ba; + if (i < sci->stereo_bands) + { + ba = ba_code_tab[get_bits(bs, ba_bits)]; + } + sci->bitalloc[2*i + 1] = sci->stereo_bands ? ba : 0; + } + + for (i = 0; i < 2*sci->total_bands; i++) + { + sci->scfcod[i] = sci->bitalloc[i] ? HDR_IS_LAYER_1(hdr) ? 2 : get_bits(bs, 2) : 6; + } + + L12_read_scalefactors(bs, sci->bitalloc, sci->scfcod, sci->total_bands*2, sci->scf); + + for (i = sci->stereo_bands; i < sci->total_bands; i++) + { + sci->bitalloc[2*i + 1] = 0; + } +} + +static int L12_dequantize_granule(float *grbuf, bs_t *bs, L12_scale_info *sci, int group_size) +{ + int i, j, k, choff = 576; + for (j = 0; j < 4; j++) + { + float *dst = grbuf + group_size*j; + for (i = 0; i < 2*sci->total_bands; i++) + { + int ba = sci->bitalloc[i]; + if (ba != 0) + { + if (ba < 17) + { + int half = (1 << (ba - 1)) - 1; + for (k = 0; k < group_size; k++) + { + dst[k] = (float)((int)get_bits(bs, ba) - half); + } + } else + { + unsigned mod = (2 << (ba - 17)) + 1; /* 3, 5, 9 */ + unsigned code = get_bits(bs, mod + 2 - (mod >> 3)); /* 5, 7, 10 */ + for (k = 0; k < group_size; k++, code /= mod) + { + dst[k] = (float)((int)(code % mod - mod/2)); + } + } + } + dst += choff; + choff = 18 - choff; + } + } + return group_size*4; +} + +static void L12_apply_scf_384(L12_scale_info *sci, const float *scf, float *dst) +{ + int i, k; + memcpy(dst + 576 + sci->stereo_bands*18, dst + sci->stereo_bands*18, (sci->total_bands - sci->stereo_bands)*18*sizeof(float)); + for (i = 0; i < sci->total_bands; i++, dst += 18, scf += 6) + { + for (k = 0; k < 12; k++) + { + dst[k + 0] *= scf[0]; + dst[k + 576] *= scf[3]; + } + } +} +#endif /* MINIMP3_ONLY_MP3 */ + +static int L3_read_side_info(bs_t *bs, L3_gr_info_t *gr, const uint8_t *hdr) +{ + static const uint8_t g_scf_long[8][23] = { + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 12,12,12,12,12,12,16,20,24,28,32,40,48,56,64,76,90,2,2,2,2,2,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,18,22,26,32,38,46,54,62,70,76,36,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 4,4,4,4,4,4,6,6,8,8,10,12,16,20,24,28,34,42,50,54,76,158,0 }, + { 4,4,4,4,4,4,6,6,6,8,10,12,16,18,22,28,34,40,46,54,54,192,0 }, + { 4,4,4,4,4,4,6,6,8,10,12,16,20,24,30,38,46,56,68,84,102,26,0 } + }; + static const uint8_t g_scf_short[8][40] = { + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 8,8,8,8,8,8,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } + }; + static const uint8_t g_scf_mixed[8][40] = { + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 12,12,12,4,4,4,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, + { 6,6,6,6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } + }; + + unsigned tables, scfsi = 0; + int main_data_begin, part_23_sum = 0; + int sr_idx = HDR_GET_MY_SAMPLE_RATE(hdr); sr_idx -= (sr_idx != 0); + int gr_count = HDR_IS_MONO(hdr) ? 1 : 2; + + if (HDR_TEST_MPEG1(hdr)) + { + gr_count *= 2; + main_data_begin = get_bits(bs, 9); + scfsi = get_bits(bs, 7 + gr_count); + } else + { + main_data_begin = get_bits(bs, 8 + gr_count) >> gr_count; + } + + do + { + if (HDR_IS_MONO(hdr)) + { + scfsi <<= 4; + } + gr->part_23_length = (uint16_t)get_bits(bs, 12); + part_23_sum += gr->part_23_length; + gr->big_values = (uint16_t)get_bits(bs, 9); + if (gr->big_values > 288) + { + return -1; + } + gr->global_gain = (uint8_t)get_bits(bs, 8); + gr->scalefac_compress = (uint16_t)get_bits(bs, HDR_TEST_MPEG1(hdr) ? 4 : 9); + gr->sfbtab = g_scf_long[sr_idx]; + gr->n_long_sfb = 22; + gr->n_short_sfb = 0; + if (get_bits(bs, 1)) + { + gr->block_type = (uint8_t)get_bits(bs, 2); + if (!gr->block_type) + { + return -1; + } + gr->mixed_block_flag = (uint8_t)get_bits(bs, 1); + gr->region_count[0] = 7; + gr->region_count[1] = 255; + if (gr->block_type == SHORT_BLOCK_TYPE) + { + scfsi &= 0x0F0F; + if (!gr->mixed_block_flag) + { + gr->region_count[0] = 8; + gr->sfbtab = g_scf_short[sr_idx]; + gr->n_long_sfb = 0; + gr->n_short_sfb = 39; + } else + { + gr->sfbtab = g_scf_mixed[sr_idx]; + gr->n_long_sfb = HDR_TEST_MPEG1(hdr) ? 8 : 6; + gr->n_short_sfb = 30; + } + } + tables = get_bits(bs, 10); + tables <<= 5; + gr->subblock_gain[0] = (uint8_t)get_bits(bs, 3); + gr->subblock_gain[1] = (uint8_t)get_bits(bs, 3); + gr->subblock_gain[2] = (uint8_t)get_bits(bs, 3); + } else + { + gr->block_type = 0; + gr->mixed_block_flag = 0; + tables = get_bits(bs, 15); + gr->region_count[0] = (uint8_t)get_bits(bs, 4); + gr->region_count[1] = (uint8_t)get_bits(bs, 3); + gr->region_count[2] = 255; + } + gr->table_select[0] = (uint8_t)(tables >> 10); + gr->table_select[1] = (uint8_t)((tables >> 5) & 31); + gr->table_select[2] = (uint8_t)((tables) & 31); + gr->preflag = HDR_TEST_MPEG1(hdr) ? get_bits(bs, 1) : (gr->scalefac_compress >= 500); + gr->scalefac_scale = (uint8_t)get_bits(bs, 1); + gr->count1_table = (uint8_t)get_bits(bs, 1); + gr->scfsi = (uint8_t)((scfsi >> 12) & 15); + scfsi <<= 4; + gr++; + } while(--gr_count); + + if (part_23_sum + bs->pos > bs->limit + main_data_begin*8) + { + return -1; + } + + return main_data_begin; +} + +static void L3_read_scalefactors(uint8_t *scf, uint8_t *ist_pos, const uint8_t *scf_size, const uint8_t *scf_count, bs_t *bitbuf, int scfsi) +{ + int i, k; + for (i = 0; i < 4 && scf_count[i]; i++, scfsi *= 2) + { + int cnt = scf_count[i]; + if (scfsi & 8) + { + memcpy(scf, ist_pos, cnt); + } else + { + int bits = scf_size[i]; + if (!bits) + { + memset(scf, 0, cnt); + memset(ist_pos, 0, cnt); + } else + { + int max_scf = (scfsi < 0) ? (1 << bits) - 1 : -1; + for (k = 0; k < cnt; k++) + { + int s = get_bits(bitbuf, bits); + ist_pos[k] = (s == max_scf ? -1 : s); + scf[k] = s; + } + } + } + ist_pos += cnt; + scf += cnt; + } + scf[0] = scf[1] = scf[2] = 0; +} + +static float L3_ldexp_q2(float y, int exp_q2) +{ + static const float g_expfrac[4] = { 9.31322575e-10f,7.83145814e-10f,6.58544508e-10f,5.53767716e-10f }; + int e; + do + { + e = MINIMP3_MIN(30*4, exp_q2); + y *= g_expfrac[e & 3]*(1 << 30 >> (e >> 2)); + } while ((exp_q2 -= e) > 0); + return y; +} + +static void L3_decode_scalefactors(const uint8_t *hdr, uint8_t *ist_pos, bs_t *bs, const L3_gr_info_t *gr, float *scf, int ch) +{ + static const uint8_t g_scf_partitions[3][28] = { + { 6,5,5, 5,6,5,5,5,6,5, 7,3,11,10,0,0, 7, 7, 7,0, 6, 6,6,3, 8, 8,5,0 }, + { 8,9,6,12,6,9,9,9,6,9,12,6,15,18,0,0, 6,15,12,0, 6,12,9,6, 6,18,9,0 }, + { 9,9,6,12,9,9,9,9,9,9,12,6,18,18,0,0,12,12,12,0,12, 9,9,6,15,12,9,0 } + }; + const uint8_t *scf_partition = g_scf_partitions[!!gr->n_short_sfb + !gr->n_long_sfb]; + uint8_t scf_size[4], iscf[40]; + int i, scf_shift = gr->scalefac_scale + 1, gain_exp, scfsi = gr->scfsi; + float gain; + + if (HDR_TEST_MPEG1(hdr)) + { + static const uint8_t g_scfc_decode[16] = { 0,1,2,3, 12,5,6,7, 9,10,11,13, 14,15,18,19 }; + int part = g_scfc_decode[gr->scalefac_compress]; + scf_size[1] = scf_size[0] = (uint8_t)(part >> 2); + scf_size[3] = scf_size[2] = (uint8_t)(part & 3); + } else + { + static const uint8_t g_mod[6*4] = { 5,5,4,4,5,5,4,1,4,3,1,1,5,6,6,1,4,4,4,1,4,3,1,1 }; + int k, modprod, sfc, ist = HDR_TEST_I_STEREO(hdr) && ch; + sfc = gr->scalefac_compress >> ist; + for (k = ist*3*4; sfc >= 0; sfc -= modprod, k += 4) + { + for (modprod = 1, i = 3; i >= 0; i--) + { + scf_size[i] = (uint8_t)(sfc / modprod % g_mod[k + i]); + modprod *= g_mod[k + i]; + } + } + scf_partition += k; + scfsi = -16; + } + L3_read_scalefactors(iscf, ist_pos, scf_size, scf_partition, bs, scfsi); + + if (gr->n_short_sfb) + { + int sh = 3 - scf_shift; + for (i = 0; i < gr->n_short_sfb; i += 3) + { + iscf[gr->n_long_sfb + i + 0] += gr->subblock_gain[0] << sh; + iscf[gr->n_long_sfb + i + 1] += gr->subblock_gain[1] << sh; + iscf[gr->n_long_sfb + i + 2] += gr->subblock_gain[2] << sh; + } + } else if (gr->preflag) + { + static const uint8_t g_preamp[10] = { 1,1,1,1,2,2,3,3,3,2 }; + for (i = 0; i < 10; i++) + { + iscf[11 + i] += g_preamp[i]; + } + } + + gain_exp = gr->global_gain + BITS_DEQUANTIZER_OUT*4 - 210 - (HDR_IS_MS_STEREO(hdr) ? 2 : 0); + gain = L3_ldexp_q2(1 << (MAX_SCFI/4), MAX_SCFI - gain_exp); + for (i = 0; i < (int)(gr->n_long_sfb + gr->n_short_sfb); i++) + { + scf[i] = L3_ldexp_q2(gain, iscf[i] << scf_shift); + } +} + +static const float g_pow43[129 + 16] = { + 0,-1,-2.519842f,-4.326749f,-6.349604f,-8.549880f,-10.902724f,-13.390518f,-16.000000f,-18.720754f,-21.544347f,-24.463781f,-27.473142f,-30.567351f,-33.741992f,-36.993181f, + 0,1,2.519842f,4.326749f,6.349604f,8.549880f,10.902724f,13.390518f,16.000000f,18.720754f,21.544347f,24.463781f,27.473142f,30.567351f,33.741992f,36.993181f,40.317474f,43.711787f,47.173345f,50.699631f,54.288352f,57.937408f,61.644865f,65.408941f,69.227979f,73.100443f,77.024898f,81.000000f,85.024491f,89.097188f,93.216975f,97.382800f,101.593667f,105.848633f,110.146801f,114.487321f,118.869381f,123.292209f,127.755065f,132.257246f,136.798076f,141.376907f,145.993119f,150.646117f,155.335327f,160.060199f,164.820202f,169.614826f,174.443577f,179.305980f,184.201575f,189.129918f,194.090580f,199.083145f,204.107210f,209.162385f,214.248292f,219.364564f,224.510845f,229.686789f,234.892058f,240.126328f,245.389280f,250.680604f,256.000000f,261.347174f,266.721841f,272.123723f,277.552547f,283.008049f,288.489971f,293.998060f,299.532071f,305.091761f,310.676898f,316.287249f,321.922592f,327.582707f,333.267377f,338.976394f,344.709550f,350.466646f,356.247482f,362.051866f,367.879608f,373.730522f,379.604427f,385.501143f,391.420496f,397.362314f,403.326427f,409.312672f,415.320884f,421.350905f,427.402579f,433.475750f,439.570269f,445.685987f,451.822757f,457.980436f,464.158883f,470.357960f,476.577530f,482.817459f,489.077615f,495.357868f,501.658090f,507.978156f,514.317941f,520.677324f,527.056184f,533.454404f,539.871867f,546.308458f,552.764065f,559.238575f,565.731879f,572.243870f,578.774440f,585.323483f,591.890898f,598.476581f,605.080431f,611.702349f,618.342238f,625.000000f,631.675540f,638.368763f,645.079578f +}; + +static float L3_pow_43(int x) +{ + float frac; + int sign, mult = 256; + + if (x < 129) + { + return g_pow43[16 + x]; + } + + if (x < 1024) + { + mult = 16; + x <<= 3; + } + + sign = 2*x & 64; + frac = (float)((x & 63) - sign) / ((x & ~63) + sign); + return g_pow43[16 + ((x + sign) >> 6)]*(1.f + frac*((4.f/3) + frac*(2.f/9)))*mult; +} + +static void L3_huffman(float *dst, bs_t *bs, const L3_gr_info_t *gr_info, const float *scf, int layer3gr_limit) +{ + static const int16_t tabs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 785,785,785,785,784,784,784,784,513,513,513,513,513,513,513,513,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256, + -255,1313,1298,1282,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,290,288, + -255,1313,1298,1282,769,769,769,769,529,529,529,529,529,529,529,529,528,528,528,528,528,528,528,528,512,512,512,512,512,512,512,512,290,288, + -253,-318,-351,-367,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,819,818,547,547,275,275,275,275,561,560,515,546,289,274,288,258, + -254,-287,1329,1299,1314,1312,1057,1057,1042,1042,1026,1026,784,784,784,784,529,529,529,529,529,529,529,529,769,769,769,769,768,768,768,768,563,560,306,306,291,259, + -252,-413,-477,-542,1298,-575,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-383,-399,1107,1092,1106,1061,849,849,789,789,1104,1091,773,773,1076,1075,341,340,325,309,834,804,577,577,532,532,516,516,832,818,803,816,561,561,531,531,515,546,289,289,288,258, + -252,-429,-493,-559,1057,1057,1042,1042,529,529,529,529,529,529,529,529,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,-382,1077,-415,1106,1061,1104,849,849,789,789,1091,1076,1029,1075,834,834,597,581,340,340,339,324,804,833,532,532,832,772,818,803,817,787,816,771,290,290,290,290,288,258, + -253,-349,-414,-447,-463,1329,1299,-479,1314,1312,1057,1057,1042,1042,1026,1026,785,785,785,785,784,784,784,784,769,769,769,769,768,768,768,768,-319,851,821,-335,836,850,805,849,341,340,325,336,533,533,579,579,564,564,773,832,578,548,563,516,321,276,306,291,304,259, + -251,-572,-733,-830,-863,-879,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,1396,1351,1381,1366,1395,1335,1380,-559,1334,1138,1138,1063,1063,1350,1392,1031,1031,1062,1062,1364,1363,1120,1120,1333,1348,881,881,881,881,375,374,359,373,343,358,341,325,791,791,1123,1122,-703,1105,1045,-719,865,865,790,790,774,774,1104,1029,338,293,323,308,-799,-815,833,788,772,818,803,816,322,292,307,320,561,531,515,546,289,274,288,258, + -251,-525,-605,-685,-765,-831,-846,1298,1057,1057,1312,1282,785,785,785,785,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,1399,1398,1383,1367,1382,1396,1351,-511,1381,1366,1139,1139,1079,1079,1124,1124,1364,1349,1363,1333,882,882,882,882,807,807,807,807,1094,1094,1136,1136,373,341,535,535,881,775,867,822,774,-591,324,338,-671,849,550,550,866,864,609,609,293,336,534,534,789,835,773,-751,834,804,308,307,833,788,832,772,562,562,547,547,305,275,560,515,290,290, + -252,-397,-477,-557,-622,-653,-719,-735,-750,1329,1299,1314,1057,1057,1042,1042,1312,1282,1024,1024,785,785,785,785,784,784,784,784,769,769,769,769,-383,1127,1141,1111,1126,1140,1095,1110,869,869,883,883,1079,1109,882,882,375,374,807,868,838,881,791,-463,867,822,368,263,852,837,836,-543,610,610,550,550,352,336,534,534,865,774,851,821,850,805,593,533,579,564,773,832,578,578,548,548,577,577,307,276,306,291,516,560,259,259, + -250,-2107,-2507,-2764,-2909,-2974,-3007,-3023,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-767,-1052,-1213,-1277,-1358,-1405,-1469,-1535,-1550,-1582,-1614,-1647,-1662,-1694,-1726,-1759,-1774,-1807,-1822,-1854,-1886,1565,-1919,-1935,-1951,-1967,1731,1730,1580,1717,-1983,1729,1564,-1999,1548,-2015,-2031,1715,1595,-2047,1714,-2063,1610,-2079,1609,-2095,1323,1323,1457,1457,1307,1307,1712,1547,1641,1700,1699,1594,1685,1625,1442,1442,1322,1322,-780,-973,-910,1279,1278,1277,1262,1276,1261,1275,1215,1260,1229,-959,974,974,989,989,-943,735,478,478,495,463,506,414,-1039,1003,958,1017,927,942,987,957,431,476,1272,1167,1228,-1183,1256,-1199,895,895,941,941,1242,1227,1212,1135,1014,1014,490,489,503,487,910,1013,985,925,863,894,970,955,1012,847,-1343,831,755,755,984,909,428,366,754,559,-1391,752,486,457,924,997,698,698,983,893,740,740,908,877,739,739,667,667,953,938,497,287,271,271,683,606,590,712,726,574,302,302,738,736,481,286,526,725,605,711,636,724,696,651,589,681,666,710,364,467,573,695,466,466,301,465,379,379,709,604,665,679,316,316,634,633,436,436,464,269,424,394,452,332,438,363,347,408,393,448,331,422,362,407,392,421,346,406,391,376,375,359,1441,1306,-2367,1290,-2383,1337,-2399,-2415,1426,1321,-2431,1411,1336,-2447,-2463,-2479,1169,1169,1049,1049,1424,1289,1412,1352,1319,-2495,1154,1154,1064,1064,1153,1153,416,390,360,404,403,389,344,374,373,343,358,372,327,357,342,311,356,326,1395,1394,1137,1137,1047,1047,1365,1392,1287,1379,1334,1364,1349,1378,1318,1363,792,792,792,792,1152,1152,1032,1032,1121,1121,1046,1046,1120,1120,1030,1030,-2895,1106,1061,1104,849,849,789,789,1091,1076,1029,1090,1060,1075,833,833,309,324,532,532,832,772,818,803,561,561,531,560,515,546,289,274,288,258, + -250,-1179,-1579,-1836,-1996,-2124,-2253,-2333,-2413,-2477,-2542,-2574,-2607,-2622,-2655,1314,1313,1298,1312,1282,785,785,785,785,1040,1040,1025,1025,768,768,768,768,-766,-798,-830,-862,-895,-911,-927,-943,-959,-975,-991,-1007,-1023,-1039,-1055,-1070,1724,1647,-1103,-1119,1631,1767,1662,1738,1708,1723,-1135,1780,1615,1779,1599,1677,1646,1778,1583,-1151,1777,1567,1737,1692,1765,1722,1707,1630,1751,1661,1764,1614,1736,1676,1763,1750,1645,1598,1721,1691,1762,1706,1582,1761,1566,-1167,1749,1629,767,766,751,765,494,494,735,764,719,749,734,763,447,447,748,718,477,506,431,491,446,476,461,505,415,430,475,445,504,399,460,489,414,503,383,474,429,459,502,502,746,752,488,398,501,473,413,472,486,271,480,270,-1439,-1455,1357,-1471,-1487,-1503,1341,1325,-1519,1489,1463,1403,1309,-1535,1372,1448,1418,1476,1356,1462,1387,-1551,1475,1340,1447,1402,1386,-1567,1068,1068,1474,1461,455,380,468,440,395,425,410,454,364,467,466,464,453,269,409,448,268,432,1371,1473,1432,1417,1308,1460,1355,1446,1459,1431,1083,1083,1401,1416,1458,1445,1067,1067,1370,1457,1051,1051,1291,1430,1385,1444,1354,1415,1400,1443,1082,1082,1173,1113,1186,1066,1185,1050,-1967,1158,1128,1172,1097,1171,1081,-1983,1157,1112,416,266,375,400,1170,1142,1127,1065,793,793,1169,1033,1156,1096,1141,1111,1155,1080,1126,1140,898,898,808,808,897,897,792,792,1095,1152,1032,1125,1110,1139,1079,1124,882,807,838,881,853,791,-2319,867,368,263,822,852,837,866,806,865,-2399,851,352,262,534,534,821,836,594,594,549,549,593,593,533,533,848,773,579,579,564,578,548,563,276,276,577,576,306,291,516,560,305,305,275,259, + -251,-892,-2058,-2620,-2828,-2957,-3023,-3039,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,-559,1530,-575,-591,1528,1527,1407,1526,1391,1023,1023,1023,1023,1525,1375,1268,1268,1103,1103,1087,1087,1039,1039,1523,-604,815,815,815,815,510,495,509,479,508,463,507,447,431,505,415,399,-734,-782,1262,-815,1259,1244,-831,1258,1228,-847,-863,1196,-879,1253,987,987,748,-767,493,493,462,477,414,414,686,669,478,446,461,445,474,429,487,458,412,471,1266,1264,1009,1009,799,799,-1019,-1276,-1452,-1581,-1677,-1757,-1821,-1886,-1933,-1997,1257,1257,1483,1468,1512,1422,1497,1406,1467,1496,1421,1510,1134,1134,1225,1225,1466,1451,1374,1405,1252,1252,1358,1480,1164,1164,1251,1251,1238,1238,1389,1465,-1407,1054,1101,-1423,1207,-1439,830,830,1248,1038,1237,1117,1223,1148,1236,1208,411,426,395,410,379,269,1193,1222,1132,1235,1221,1116,976,976,1192,1162,1177,1220,1131,1191,963,963,-1647,961,780,-1663,558,558,994,993,437,408,393,407,829,978,813,797,947,-1743,721,721,377,392,844,950,828,890,706,706,812,859,796,960,948,843,934,874,571,571,-1919,690,555,689,421,346,539,539,944,779,918,873,932,842,903,888,570,570,931,917,674,674,-2575,1562,-2591,1609,-2607,1654,1322,1322,1441,1441,1696,1546,1683,1593,1669,1624,1426,1426,1321,1321,1639,1680,1425,1425,1305,1305,1545,1668,1608,1623,1667,1592,1638,1666,1320,1320,1652,1607,1409,1409,1304,1304,1288,1288,1664,1637,1395,1395,1335,1335,1622,1636,1394,1394,1319,1319,1606,1621,1392,1392,1137,1137,1137,1137,345,390,360,375,404,373,1047,-2751,-2767,-2783,1062,1121,1046,-2799,1077,-2815,1106,1061,789,789,1105,1104,263,355,310,340,325,354,352,262,339,324,1091,1076,1029,1090,1060,1075,833,833,788,788,1088,1028,818,818,803,803,561,561,531,531,816,771,546,546,289,274,288,258, + -253,-317,-381,-446,-478,-509,1279,1279,-811,-1179,-1451,-1756,-1900,-2028,-2189,-2253,-2333,-2414,-2445,-2511,-2526,1313,1298,-2559,1041,1041,1040,1040,1025,1025,1024,1024,1022,1007,1021,991,1020,975,1019,959,687,687,1018,1017,671,671,655,655,1016,1015,639,639,758,758,623,623,757,607,756,591,755,575,754,559,543,543,1009,783,-575,-621,-685,-749,496,-590,750,749,734,748,974,989,1003,958,988,973,1002,942,987,957,972,1001,926,986,941,971,956,1000,910,985,925,999,894,970,-1071,-1087,-1102,1390,-1135,1436,1509,1451,1374,-1151,1405,1358,1480,1420,-1167,1507,1494,1389,1342,1465,1435,1450,1326,1505,1310,1493,1373,1479,1404,1492,1464,1419,428,443,472,397,736,526,464,464,486,457,442,471,484,482,1357,1449,1434,1478,1388,1491,1341,1490,1325,1489,1463,1403,1309,1477,1372,1448,1418,1433,1476,1356,1462,1387,-1439,1475,1340,1447,1402,1474,1324,1461,1371,1473,269,448,1432,1417,1308,1460,-1711,1459,-1727,1441,1099,1099,1446,1386,1431,1401,-1743,1289,1083,1083,1160,1160,1458,1445,1067,1067,1370,1457,1307,1430,1129,1129,1098,1098,268,432,267,416,266,400,-1887,1144,1187,1082,1173,1113,1186,1066,1050,1158,1128,1143,1172,1097,1171,1081,420,391,1157,1112,1170,1142,1127,1065,1169,1049,1156,1096,1141,1111,1155,1080,1126,1154,1064,1153,1140,1095,1048,-2159,1125,1110,1137,-2175,823,823,1139,1138,807,807,384,264,368,263,868,838,853,791,867,822,852,837,866,806,865,790,-2319,851,821,836,352,262,850,805,849,-2399,533,533,835,820,336,261,578,548,563,577,532,532,832,772,562,562,547,547,305,275,560,515,290,290,288,258 }; + static const uint8_t tab32[] = { 130,162,193,209,44,28,76,140,9,9,9,9,9,9,9,9,190,254,222,238,126,94,157,157,109,61,173,205 }; + static const uint8_t tab33[] = { 252,236,220,204,188,172,156,140,124,108,92,76,60,44,28,12 }; + static const int16_t tabindex[2*16] = { 0,32,64,98,0,132,180,218,292,364,426,538,648,746,0,1126,1460,1460,1460,1460,1460,1460,1460,1460,1842,1842,1842,1842,1842,1842,1842,1842 }; + static const uint8_t g_linbits[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,6,8,10,13,4,5,6,7,8,9,11,13 }; + +#define PEEK_BITS(n) (bs_cache >> (32 - n)) +#define FLUSH_BITS(n) { bs_cache <<= (n); bs_sh += (n); } +#define CHECK_BITS while (bs_sh >= 0) { bs_cache |= (uint32_t)*bs_next_ptr++ << bs_sh; bs_sh -= 8; } +#define BSPOS ((bs_next_ptr - bs->buf)*8 - 24 + bs_sh) + + float one = 0.0f; + int ireg = 0, big_val_cnt = gr_info->big_values; + const uint8_t *sfb = gr_info->sfbtab; + const uint8_t *bs_next_ptr = bs->buf + bs->pos/8; + uint32_t bs_cache = (((bs_next_ptr[0]*256u + bs_next_ptr[1])*256u + bs_next_ptr[2])*256u + bs_next_ptr[3]) << (bs->pos & 7); + int pairs_to_decode, np, bs_sh = (bs->pos & 7) - 8; + bs_next_ptr += 4; + + while (big_val_cnt > 0) + { + int tab_num = gr_info->table_select[ireg]; + int sfb_cnt = gr_info->region_count[ireg++]; + const int16_t *codebook = tabs + tabindex[tab_num]; + int linbits = g_linbits[tab_num]; + if (linbits) + { + do + { + np = *sfb++ / 2; + pairs_to_decode = MINIMP3_MIN(big_val_cnt, np); + one = *scf++; + do + { + int j, w = 5; + int leaf = codebook[PEEK_BITS(w)]; + while (leaf < 0) + { + FLUSH_BITS(w); + w = leaf & 7; + leaf = codebook[PEEK_BITS(w) - (leaf >> 3)]; + } + FLUSH_BITS(leaf >> 8); + + for (j = 0; j < 2; j++, dst++, leaf >>= 4) + { + int lsb = leaf & 0x0F; + if (lsb == 15) + { + lsb += PEEK_BITS(linbits); + FLUSH_BITS(linbits); + CHECK_BITS; + *dst = one*L3_pow_43(lsb)*((int32_t)bs_cache < 0 ? -1: 1); + } else + { + *dst = g_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + } + FLUSH_BITS(lsb ? 1 : 0); + } + CHECK_BITS; + } while (--pairs_to_decode); + } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); + } else + { + do + { + np = *sfb++ / 2; + pairs_to_decode = MINIMP3_MIN(big_val_cnt, np); + one = *scf++; + do + { + int j, w = 5; + int leaf = codebook[PEEK_BITS(w)]; + while (leaf < 0) + { + FLUSH_BITS(w); + w = leaf & 7; + leaf = codebook[PEEK_BITS(w) - (leaf >> 3)]; + } + FLUSH_BITS(leaf >> 8); + + for (j = 0; j < 2; j++, dst++, leaf >>= 4) + { + int lsb = leaf & 0x0F; + *dst = g_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + FLUSH_BITS(lsb ? 1 : 0); + } + CHECK_BITS; + } while (--pairs_to_decode); + } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); + } + } + + for (np = 1 - big_val_cnt;; dst += 4) + { + const uint8_t *codebook_count1 = (gr_info->count1_table) ? tab33 : tab32; + int leaf = codebook_count1[PEEK_BITS(4)]; + if (!(leaf & 8)) + { + leaf = codebook_count1[(leaf >> 3) + (bs_cache << 4 >> (32 - (leaf & 3)))]; + } + FLUSH_BITS(leaf & 7); + if (BSPOS > layer3gr_limit) + { + break; + } +#define RELOAD_SCALEFACTOR if (!--np) { np = *sfb++/2; if (!np) break; one = *scf++; } +#define DEQ_COUNT1(s) if (leaf & (128 >> s)) { dst[s] = ((int32_t)bs_cache < 0) ? -one : one; FLUSH_BITS(1) } + RELOAD_SCALEFACTOR; + DEQ_COUNT1(0); + DEQ_COUNT1(1); + RELOAD_SCALEFACTOR; + DEQ_COUNT1(2); + DEQ_COUNT1(3); + CHECK_BITS; + } + + bs->pos = layer3gr_limit; +} + +static void L3_midside_stereo(float *left, int n) +{ + int i = 0; + float *right = left + 576; +#if HAVE_SIMD + if (have_simd()) + { + for (; i < n - 3; i += 4) + { + f4 vl = VLD(left + i); + f4 vr = VLD(right + i); + VSTORE(left + i, VADD(vl, vr)); + VSTORE(right + i, VSUB(vl, vr)); + } +#ifdef __GNUC__ + /* Workaround for spurious -Waggressive-loop-optimizations warning from gcc. + * For more info see: https://github.com/lieff/minimp3/issues/88 + */ + if (__builtin_constant_p(n % 4 == 0) && n % 4 == 0) + return; +#endif + } +#endif /* HAVE_SIMD */ + for (; i < n; i++) + { + float a = left[i]; + float b = right[i]; + left[i] = a + b; + right[i] = a - b; + } +} + +static void L3_intensity_stereo_band(float *left, int n, float kl, float kr) +{ + int i; + for (i = 0; i < n; i++) + { + left[i + 576] = left[i]*kr; + left[i] = left[i]*kl; + } +} + +static void L3_stereo_top_band(const float *right, const uint8_t *sfb, int nbands, int max_band[3]) +{ + int i, k; + + max_band[0] = max_band[1] = max_band[2] = -1; + + for (i = 0; i < nbands; i++) + { + for (k = 0; k < sfb[i]; k += 2) + { + if (right[k] != 0 || right[k + 1] != 0) + { + max_band[i % 3] = i; + break; + } + } + right += sfb[i]; + } +} + +static void L3_stereo_process(float *left, const uint8_t *ist_pos, const uint8_t *sfb, const uint8_t *hdr, int max_band[3], int mpeg2_sh) +{ + static const float g_pan[7*2] = { 0,1,0.21132487f,0.78867513f,0.36602540f,0.63397460f,0.5f,0.5f,0.63397460f,0.36602540f,0.78867513f,0.21132487f,1,0 }; + unsigned i, max_pos = HDR_TEST_MPEG1(hdr) ? 7 : 64; + + for (i = 0; sfb[i]; i++) + { + unsigned ipos = ist_pos[i]; + if ((int)i > max_band[i % 3] && ipos < max_pos) + { + float kl, kr, s = HDR_TEST_MS_STEREO(hdr) ? 1.41421356f : 1; + if (HDR_TEST_MPEG1(hdr)) + { + kl = g_pan[2*ipos]; + kr = g_pan[2*ipos + 1]; + } else + { + kl = 1; + kr = L3_ldexp_q2(1, (ipos + 1) >> 1 << mpeg2_sh); + if (ipos & 1) + { + kl = kr; + kr = 1; + } + } + L3_intensity_stereo_band(left, sfb[i], kl*s, kr*s); + } else if (HDR_TEST_MS_STEREO(hdr)) + { + L3_midside_stereo(left, sfb[i]); + } + left += sfb[i]; + } +} + +static void L3_intensity_stereo(float *left, uint8_t *ist_pos, const L3_gr_info_t *gr, const uint8_t *hdr) +{ + int max_band[3], n_sfb = gr->n_long_sfb + gr->n_short_sfb; + int i, max_blocks = gr->n_short_sfb ? 3 : 1; + + L3_stereo_top_band(left + 576, gr->sfbtab, n_sfb, max_band); + if (gr->n_long_sfb) + { + max_band[0] = max_band[1] = max_band[2] = MINIMP3_MAX(MINIMP3_MAX(max_band[0], max_band[1]), max_band[2]); + } + for (i = 0; i < max_blocks; i++) + { + int default_pos = HDR_TEST_MPEG1(hdr) ? 3 : 0; + int itop = n_sfb - max_blocks + i; + int prev = itop - max_blocks; + ist_pos[itop] = max_band[i] >= prev ? default_pos : ist_pos[prev]; + } + L3_stereo_process(left, ist_pos, gr->sfbtab, hdr, max_band, gr[1].scalefac_compress & 1); +} + +static void L3_reorder(float *grbuf, float *scratch, const uint8_t *sfb) +{ + int i, len; + float *src = grbuf, *dst = scratch; + + for (;0 != (len = *sfb); sfb += 3, src += 2*len) + { + for (i = 0; i < len; i++, src++) + { + *dst++ = src[0*len]; + *dst++ = src[1*len]; + *dst++ = src[2*len]; + } + } + memcpy(grbuf, scratch, (dst - scratch)*sizeof(float)); +} + +static void L3_antialias(float *grbuf, int nbands) +{ + static const float g_aa[2][8] = { + {0.85749293f,0.88174200f,0.94962865f,0.98331459f,0.99551782f,0.99916056f,0.99989920f,0.99999316f}, + {0.51449576f,0.47173197f,0.31337745f,0.18191320f,0.09457419f,0.04096558f,0.01419856f,0.00369997f} + }; + + for (; nbands > 0; nbands--, grbuf += 18) + { + int i = 0; +#if HAVE_SIMD + if (have_simd()) for (; i < 8; i += 4) + { + f4 vu = VLD(grbuf + 18 + i); + f4 vd = VLD(grbuf + 14 - i); + f4 vc0 = VLD(g_aa[0] + i); + f4 vc1 = VLD(g_aa[1] + i); + vd = VREV(vd); + VSTORE(grbuf + 18 + i, VSUB(VMUL(vu, vc0), VMUL(vd, vc1))); + vd = VADD(VMUL(vu, vc1), VMUL(vd, vc0)); + VSTORE(grbuf + 14 - i, VREV(vd)); + } +#endif /* HAVE_SIMD */ +#ifndef MINIMP3_ONLY_SIMD + for(; i < 8; i++) + { + float u = grbuf[18 + i]; + float d = grbuf[17 - i]; + grbuf[18 + i] = u*g_aa[0][i] - d*g_aa[1][i]; + grbuf[17 - i] = u*g_aa[1][i] + d*g_aa[0][i]; + } +#endif /* MINIMP3_ONLY_SIMD */ + } +} + +static void L3_dct3_9(float *y) +{ + float s0, s1, s2, s3, s4, s5, s6, s7, s8, t0, t2, t4; + + s0 = y[0]; s2 = y[2]; s4 = y[4]; s6 = y[6]; s8 = y[8]; + t0 = s0 + s6*0.5f; + s0 -= s6; + t4 = (s4 + s2)*0.93969262f; + t2 = (s8 + s2)*0.76604444f; + s6 = (s4 - s8)*0.17364818f; + s4 += s8 - s2; + + s2 = s0 - s4*0.5f; + y[4] = s4 + s0; + s8 = t0 - t2 + s6; + s0 = t0 - t4 + t2; + s4 = t0 + t4 - s6; + + s1 = y[1]; s3 = y[3]; s5 = y[5]; s7 = y[7]; + + s3 *= 0.86602540f; + t0 = (s5 + s1)*0.98480775f; + t4 = (s5 - s7)*0.34202014f; + t2 = (s1 + s7)*0.64278761f; + s1 = (s1 - s5 - s7)*0.86602540f; + + s5 = t0 - s3 - t2; + s7 = t4 - s3 - t0; + s3 = t4 + s3 - t2; + + y[0] = s4 - s7; + y[1] = s2 + s1; + y[2] = s0 - s3; + y[3] = s8 + s5; + y[5] = s8 - s5; + y[6] = s0 + s3; + y[7] = s2 - s1; + y[8] = s4 + s7; +} + +static void L3_imdct36(float *grbuf, float *overlap, const float *window, int nbands) +{ + int i, j; + static const float g_twid9[18] = { + 0.73727734f,0.79335334f,0.84339145f,0.88701083f,0.92387953f,0.95371695f,0.97629601f,0.99144486f,0.99904822f,0.67559021f,0.60876143f,0.53729961f,0.46174861f,0.38268343f,0.30070580f,0.21643961f,0.13052619f,0.04361938f + }; + + for (j = 0; j < nbands; j++, grbuf += 18, overlap += 9) + { + float co[9], si[9]; + co[0] = -grbuf[0]; + si[0] = grbuf[17]; + for (i = 0; i < 4; i++) + { + si[8 - 2*i] = grbuf[4*i + 1] - grbuf[4*i + 2]; + co[1 + 2*i] = grbuf[4*i + 1] + grbuf[4*i + 2]; + si[7 - 2*i] = grbuf[4*i + 4] - grbuf[4*i + 3]; + co[2 + 2*i] = -(grbuf[4*i + 3] + grbuf[4*i + 4]); + } + L3_dct3_9(co); + L3_dct3_9(si); + + si[1] = -si[1]; + si[3] = -si[3]; + si[5] = -si[5]; + si[7] = -si[7]; + + i = 0; + +#if HAVE_SIMD + if (have_simd()) for (; i < 8; i += 4) + { + f4 vovl = VLD(overlap + i); + f4 vc = VLD(co + i); + f4 vs = VLD(si + i); + f4 vr0 = VLD(g_twid9 + i); + f4 vr1 = VLD(g_twid9 + 9 + i); + f4 vw0 = VLD(window + i); + f4 vw1 = VLD(window + 9 + i); + f4 vsum = VADD(VMUL(vc, vr1), VMUL(vs, vr0)); + VSTORE(overlap + i, VSUB(VMUL(vc, vr0), VMUL(vs, vr1))); + VSTORE(grbuf + i, VSUB(VMUL(vovl, vw0), VMUL(vsum, vw1))); + vsum = VADD(VMUL(vovl, vw1), VMUL(vsum, vw0)); + VSTORE(grbuf + 14 - i, VREV(vsum)); + } +#endif /* HAVE_SIMD */ + for (; i < 9; i++) + { + float ovl = overlap[i]; + float sum = co[i]*g_twid9[9 + i] + si[i]*g_twid9[0 + i]; + overlap[i] = co[i]*g_twid9[0 + i] - si[i]*g_twid9[9 + i]; + grbuf[i] = ovl*window[0 + i] - sum*window[9 + i]; + grbuf[17 - i] = ovl*window[9 + i] + sum*window[0 + i]; + } + } +} + +static void L3_idct3(float x0, float x1, float x2, float *dst) +{ + float m1 = x1*0.86602540f; + float a1 = x0 - x2*0.5f; + dst[1] = x0 + x2; + dst[0] = a1 + m1; + dst[2] = a1 - m1; +} + +static void L3_imdct12(float *x, float *dst, float *overlap) +{ + static const float g_twid3[6] = { 0.79335334f,0.92387953f,0.99144486f, 0.60876143f,0.38268343f,0.13052619f }; + float co[3], si[3]; + int i; + + L3_idct3(-x[0], x[6] + x[3], x[12] + x[9], co); + L3_idct3(x[15], x[12] - x[9], x[6] - x[3], si); + si[1] = -si[1]; + + for (i = 0; i < 3; i++) + { + float ovl = overlap[i]; + float sum = co[i]*g_twid3[3 + i] + si[i]*g_twid3[0 + i]; + overlap[i] = co[i]*g_twid3[0 + i] - si[i]*g_twid3[3 + i]; + dst[i] = ovl*g_twid3[2 - i] - sum*g_twid3[5 - i]; + dst[5 - i] = ovl*g_twid3[5 - i] + sum*g_twid3[2 - i]; + } +} + +static void L3_imdct_short(float *grbuf, float *overlap, int nbands) +{ + for (;nbands > 0; nbands--, overlap += 9, grbuf += 18) + { + float tmp[18]; + memcpy(tmp, grbuf, sizeof(tmp)); + memcpy(grbuf, overlap, 6*sizeof(float)); + L3_imdct12(tmp, grbuf + 6, overlap + 6); + L3_imdct12(tmp + 1, grbuf + 12, overlap + 6); + L3_imdct12(tmp + 2, overlap, overlap + 6); + } +} + +static void L3_change_sign(float *grbuf) +{ + int b, i; + for (b = 0, grbuf += 18; b < 32; b += 2, grbuf += 36) + for (i = 1; i < 18; i += 2) + grbuf[i] = -grbuf[i]; +} + +static void L3_imdct_gr(float *grbuf, float *overlap, unsigned block_type, unsigned n_long_bands) +{ + static const float g_mdct_window[2][18] = { + { 0.99904822f,0.99144486f,0.97629601f,0.95371695f,0.92387953f,0.88701083f,0.84339145f,0.79335334f,0.73727734f,0.04361938f,0.13052619f,0.21643961f,0.30070580f,0.38268343f,0.46174861f,0.53729961f,0.60876143f,0.67559021f }, + { 1,1,1,1,1,1,0.99144486f,0.92387953f,0.79335334f,0,0,0,0,0,0,0.13052619f,0.38268343f,0.60876143f } + }; + if (n_long_bands) + { + L3_imdct36(grbuf, overlap, g_mdct_window[0], n_long_bands); + grbuf += 18*n_long_bands; + overlap += 9*n_long_bands; + } + if (block_type == SHORT_BLOCK_TYPE) + L3_imdct_short(grbuf, overlap, 32 - n_long_bands); + else + L3_imdct36(grbuf, overlap, g_mdct_window[block_type == STOP_BLOCK_TYPE], 32 - n_long_bands); +} + +static void L3_save_reservoir(mp3dec_t *h, mp3dec_scratch_t *s) +{ + int pos = (s->bs.pos + 7)/8u; + int remains = s->bs.limit/8u - pos; + if (remains > MAX_BITRESERVOIR_BYTES) + { + pos += remains - MAX_BITRESERVOIR_BYTES; + remains = MAX_BITRESERVOIR_BYTES; + } + if (remains > 0) + { + memmove(h->reserv_buf, s->maindata + pos, remains); + } + h->reserv = remains; +} + +static int L3_restore_reservoir(mp3dec_t *h, bs_t *bs, mp3dec_scratch_t *s, int main_data_begin) +{ + int frame_bytes = (bs->limit - bs->pos)/8; + int bytes_have = MINIMP3_MIN(h->reserv, main_data_begin); + memcpy(s->maindata, h->reserv_buf + MINIMP3_MAX(0, h->reserv - main_data_begin), MINIMP3_MIN(h->reserv, main_data_begin)); + memcpy(s->maindata + bytes_have, bs->buf + bs->pos/8, frame_bytes); + bs_init(&s->bs, s->maindata, bytes_have + frame_bytes); + return h->reserv >= main_data_begin; +} + +static void L3_decode(mp3dec_t *h, mp3dec_scratch_t *s, L3_gr_info_t *gr_info, int nch) +{ + int ch; + + for (ch = 0; ch < nch; ch++) + { + int layer3gr_limit = s->bs.pos + gr_info[ch].part_23_length; + L3_decode_scalefactors(h->header, s->ist_pos[ch], &s->bs, gr_info + ch, s->scf, ch); + L3_huffman(s->grbuf[ch], &s->bs, gr_info + ch, s->scf, layer3gr_limit); + } + + if (HDR_TEST_I_STEREO(h->header)) + { + L3_intensity_stereo(s->grbuf[0], s->ist_pos[1], gr_info, h->header); + } else if (HDR_IS_MS_STEREO(h->header)) + { + L3_midside_stereo(s->grbuf[0], 576); + } + + for (ch = 0; ch < nch; ch++, gr_info++) + { + int aa_bands = 31; + int n_long_bands = (gr_info->mixed_block_flag ? 2 : 0) << (int)(HDR_GET_MY_SAMPLE_RATE(h->header) == 2); + + if (gr_info->n_short_sfb) + { + aa_bands = n_long_bands - 1; + L3_reorder(s->grbuf[ch] + n_long_bands*18, s->syn[0], gr_info->sfbtab + gr_info->n_long_sfb); + } + + L3_antialias(s->grbuf[ch], aa_bands); + L3_imdct_gr(s->grbuf[ch], h->mdct_overlap[ch], gr_info->block_type, n_long_bands); + L3_change_sign(s->grbuf[ch]); + } +} + +static void mp3d_DCT_II(float *grbuf, int n) +{ + static const float g_sec[24] = { + 10.19000816f,0.50060302f,0.50241929f,3.40760851f,0.50547093f,0.52249861f,2.05778098f,0.51544732f,0.56694406f,1.48416460f,0.53104258f,0.64682180f,1.16943991f,0.55310392f,0.78815460f,0.97256821f,0.58293498f,1.06067765f,0.83934963f,0.62250412f,1.72244716f,0.74453628f,0.67480832f,5.10114861f + }; + int i, k = 0; +#if HAVE_SIMD + if (have_simd()) for (; k < n; k += 4) + { + f4 t[4][8], *x; + float *y = grbuf + k; + + for (x = t[0], i = 0; i < 8; i++, x++) + { + f4 x0 = VLD(&y[i*18]); + f4 x1 = VLD(&y[(15 - i)*18]); + f4 x2 = VLD(&y[(16 + i)*18]); + f4 x3 = VLD(&y[(31 - i)*18]); + f4 t0 = VADD(x0, x3); + f4 t1 = VADD(x1, x2); + f4 t2 = VMUL_S(VSUB(x1, x2), g_sec[3*i + 0]); + f4 t3 = VMUL_S(VSUB(x0, x3), g_sec[3*i + 1]); + x[0] = VADD(t0, t1); + x[8] = VMUL_S(VSUB(t0, t1), g_sec[3*i + 2]); + x[16] = VADD(t3, t2); + x[24] = VMUL_S(VSUB(t3, t2), g_sec[3*i + 2]); + } + for (x = t[0], i = 0; i < 4; i++, x += 8) + { + f4 x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; + xt = VSUB(x0, x7); x0 = VADD(x0, x7); + x7 = VSUB(x1, x6); x1 = VADD(x1, x6); + x6 = VSUB(x2, x5); x2 = VADD(x2, x5); + x5 = VSUB(x3, x4); x3 = VADD(x3, x4); + x4 = VSUB(x0, x3); x0 = VADD(x0, x3); + x3 = VSUB(x1, x2); x1 = VADD(x1, x2); + x[0] = VADD(x0, x1); + x[4] = VMUL_S(VSUB(x0, x1), 0.70710677f); + x5 = VADD(x5, x6); + x6 = VMUL_S(VADD(x6, x7), 0.70710677f); + x7 = VADD(x7, xt); + x3 = VMUL_S(VADD(x3, x4), 0.70710677f); + x5 = VSUB(x5, VMUL_S(x7, 0.198912367f)); /* rotate by PI/8 */ + x7 = VADD(x7, VMUL_S(x5, 0.382683432f)); + x5 = VSUB(x5, VMUL_S(x7, 0.198912367f)); + x0 = VSUB(xt, x6); xt = VADD(xt, x6); + x[1] = VMUL_S(VADD(xt, x7), 0.50979561f); + x[2] = VMUL_S(VADD(x4, x3), 0.54119611f); + x[3] = VMUL_S(VSUB(x0, x5), 0.60134488f); + x[5] = VMUL_S(VADD(x0, x5), 0.89997619f); + x[6] = VMUL_S(VSUB(x4, x3), 1.30656302f); + x[7] = VMUL_S(VSUB(xt, x7), 2.56291556f); + } + + if (k > n - 3) + { +#if HAVE_SSE +#define VSAVE2(i, v) _mm_storel_pi((__m64 *)(void*)&y[i*18], v) +#else /* HAVE_SSE */ +#define VSAVE2(i, v) vst1_f32((float32_t *)&y[i*18], vget_low_f32(v)) +#endif /* HAVE_SSE */ + for (i = 0; i < 7; i++, y += 4*18) + { + f4 s = VADD(t[3][i], t[3][i + 1]); + VSAVE2(0, t[0][i]); + VSAVE2(1, VADD(t[2][i], s)); + VSAVE2(2, VADD(t[1][i], t[1][i + 1])); + VSAVE2(3, VADD(t[2][1 + i], s)); + } + VSAVE2(0, t[0][7]); + VSAVE2(1, VADD(t[2][7], t[3][7])); + VSAVE2(2, t[1][7]); + VSAVE2(3, t[3][7]); + } else + { +#define VSAVE4(i, v) VSTORE(&y[i*18], v) + for (i = 0; i < 7; i++, y += 4*18) + { + f4 s = VADD(t[3][i], t[3][i + 1]); + VSAVE4(0, t[0][i]); + VSAVE4(1, VADD(t[2][i], s)); + VSAVE4(2, VADD(t[1][i], t[1][i + 1])); + VSAVE4(3, VADD(t[2][1 + i], s)); + } + VSAVE4(0, t[0][7]); + VSAVE4(1, VADD(t[2][7], t[3][7])); + VSAVE4(2, t[1][7]); + VSAVE4(3, t[3][7]); + } + } else +#endif /* HAVE_SIMD */ +#ifdef MINIMP3_ONLY_SIMD + {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ +#else /* MINIMP3_ONLY_SIMD */ + for (; k < n; k++) + { + float t[4][8], *x, *y = grbuf + k; + + for (x = t[0], i = 0; i < 8; i++, x++) + { + float x0 = y[i*18]; + float x1 = y[(15 - i)*18]; + float x2 = y[(16 + i)*18]; + float x3 = y[(31 - i)*18]; + float t0 = x0 + x3; + float t1 = x1 + x2; + float t2 = (x1 - x2)*g_sec[3*i + 0]; + float t3 = (x0 - x3)*g_sec[3*i + 1]; + x[0] = t0 + t1; + x[8] = (t0 - t1)*g_sec[3*i + 2]; + x[16] = t3 + t2; + x[24] = (t3 - t2)*g_sec[3*i + 2]; + } + for (x = t[0], i = 0; i < 4; i++, x += 8) + { + float x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; + xt = x0 - x7; x0 += x7; + x7 = x1 - x6; x1 += x6; + x6 = x2 - x5; x2 += x5; + x5 = x3 - x4; x3 += x4; + x4 = x0 - x3; x0 += x3; + x3 = x1 - x2; x1 += x2; + x[0] = x0 + x1; + x[4] = (x0 - x1)*0.70710677f; + x5 = x5 + x6; + x6 = (x6 + x7)*0.70710677f; + x7 = x7 + xt; + x3 = (x3 + x4)*0.70710677f; + x5 -= x7*0.198912367f; /* rotate by PI/8 */ + x7 += x5*0.382683432f; + x5 -= x7*0.198912367f; + x0 = xt - x6; xt += x6; + x[1] = (xt + x7)*0.50979561f; + x[2] = (x4 + x3)*0.54119611f; + x[3] = (x0 - x5)*0.60134488f; + x[5] = (x0 + x5)*0.89997619f; + x[6] = (x4 - x3)*1.30656302f; + x[7] = (xt - x7)*2.56291556f; + + } + for (i = 0; i < 7; i++, y += 4*18) + { + y[0*18] = t[0][i]; + y[1*18] = t[2][i] + t[3][i] + t[3][i + 1]; + y[2*18] = t[1][i] + t[1][i + 1]; + y[3*18] = t[2][i + 1] + t[3][i] + t[3][i + 1]; + } + y[0*18] = t[0][7]; + y[1*18] = t[2][7] + t[3][7]; + y[2*18] = t[1][7]; + y[3*18] = t[3][7]; + } +#endif /* MINIMP3_ONLY_SIMD */ +} + +#ifndef MINIMP3_FLOAT_OUTPUT +static int16_t mp3d_scale_pcm(float sample) +{ +#if HAVE_ARMV6 + int32_t s32 = (int32_t)(sample + .5f); + s32 -= (s32 < 0); + int16_t s = (int16_t)minimp3_clip_int16_arm(s32); +#else + if (sample >= 32766.5) return (int16_t) 32767; + if (sample <= -32767.5) return (int16_t)-32768; + int16_t s = (int16_t)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ +#endif + return s; +} +#else /* MINIMP3_FLOAT_OUTPUT */ +static float mp3d_scale_pcm(float sample) +{ + return sample*(1.f/32768.f); +} +#endif /* MINIMP3_FLOAT_OUTPUT */ + +static void mp3d_synth_pair(mp3d_sample_t *pcm, int nch, const float *z) +{ + float a; + a = (z[14*64] - z[ 0]) * 29; + a += (z[ 1*64] + z[13*64]) * 213; + a += (z[12*64] - z[ 2*64]) * 459; + a += (z[ 3*64] + z[11*64]) * 2037; + a += (z[10*64] - z[ 4*64]) * 5153; + a += (z[ 5*64] + z[ 9*64]) * 6574; + a += (z[ 8*64] - z[ 6*64]) * 37489; + a += z[ 7*64] * 75038; + pcm[0] = mp3d_scale_pcm(a); + + z += 2; + a = z[14*64] * 104; + a += z[12*64] * 1567; + a += z[10*64] * 9727; + a += z[ 8*64] * 64019; + a += z[ 6*64] * -9975; + a += z[ 4*64] * -45; + a += z[ 2*64] * 146; + a += z[ 0*64] * -5; + pcm[16*nch] = mp3d_scale_pcm(a); +} + +static void mp3d_synth(float *xl, mp3d_sample_t *dstl, int nch, float *lins) +{ + int i; + float *xr = xl + 576*(nch - 1); + mp3d_sample_t *dstr = dstl + (nch - 1); + + static const float g_win[] = { + -1,26,-31,208,218,401,-519,2063,2000,4788,-5517,7134,5959,35640,-39336,74992, + -1,24,-35,202,222,347,-581,2080,1952,4425,-5879,7640,5288,33791,-41176,74856, + -1,21,-38,196,225,294,-645,2087,1893,4063,-6237,8092,4561,31947,-43006,74630, + -1,19,-41,190,227,244,-711,2085,1822,3705,-6589,8492,3776,30112,-44821,74313, + -1,17,-45,183,228,197,-779,2075,1739,3351,-6935,8840,2935,28289,-46617,73908, + -1,16,-49,176,228,153,-848,2057,1644,3004,-7271,9139,2037,26482,-48390,73415, + -2,14,-53,169,227,111,-919,2032,1535,2663,-7597,9389,1082,24694,-50137,72835, + -2,13,-58,161,224,72,-991,2001,1414,2330,-7910,9592,70,22929,-51853,72169, + -2,11,-63,154,221,36,-1064,1962,1280,2006,-8209,9750,-998,21189,-53534,71420, + -2,10,-68,147,215,2,-1137,1919,1131,1692,-8491,9863,-2122,19478,-55178,70590, + -3,9,-73,139,208,-29,-1210,1870,970,1388,-8755,9935,-3300,17799,-56778,69679, + -3,8,-79,132,200,-57,-1283,1817,794,1095,-8998,9966,-4533,16155,-58333,68692, + -4,7,-85,125,189,-83,-1356,1759,605,814,-9219,9959,-5818,14548,-59838,67629, + -4,7,-91,117,177,-106,-1428,1698,402,545,-9416,9916,-7154,12980,-61289,66494, + -5,6,-97,111,163,-127,-1498,1634,185,288,-9585,9838,-8540,11455,-62684,65290 + }; + float *zlin = lins + 15*64; + const float *w = g_win; + + zlin[4*15] = xl[18*16]; + zlin[4*15 + 1] = xr[18*16]; + zlin[4*15 + 2] = xl[0]; + zlin[4*15 + 3] = xr[0]; + + zlin[4*31] = xl[1 + 18*16]; + zlin[4*31 + 1] = xr[1 + 18*16]; + zlin[4*31 + 2] = xl[1]; + zlin[4*31 + 3] = xr[1]; + + mp3d_synth_pair(dstr, nch, lins + 4*15 + 1); + mp3d_synth_pair(dstr + 32*nch, nch, lins + 4*15 + 64 + 1); + mp3d_synth_pair(dstl, nch, lins + 4*15); + mp3d_synth_pair(dstl + 32*nch, nch, lins + 4*15 + 64); + +#if HAVE_SIMD + if (have_simd()) for (i = 14; i >= 0; i--) + { +#define VLOAD(k) f4 w0 = VSET(*w++); f4 w1 = VSET(*w++); f4 vz = VLD(&zlin[4*i - 64*k]); f4 vy = VLD(&zlin[4*i - 64*(15 - k)]); +#define V0(k) { VLOAD(k) b = VADD(VMUL(vz, w1), VMUL(vy, w0)) ; a = VSUB(VMUL(vz, w0), VMUL(vy, w1)); } +#define V1(k) { VLOAD(k) b = VADD(b, VADD(VMUL(vz, w1), VMUL(vy, w0))); a = VADD(a, VSUB(VMUL(vz, w0), VMUL(vy, w1))); } +#define V2(k) { VLOAD(k) b = VADD(b, VADD(VMUL(vz, w1), VMUL(vy, w0))); a = VADD(a, VSUB(VMUL(vy, w1), VMUL(vz, w0))); } + f4 a, b; + zlin[4*i] = xl[18*(31 - i)]; + zlin[4*i + 1] = xr[18*(31 - i)]; + zlin[4*i + 2] = xl[1 + 18*(31 - i)]; + zlin[4*i + 3] = xr[1 + 18*(31 - i)]; + zlin[4*i + 64] = xl[1 + 18*(1 + i)]; + zlin[4*i + 64 + 1] = xr[1 + 18*(1 + i)]; + zlin[4*i - 64 + 2] = xl[18*(1 + i)]; + zlin[4*i - 64 + 3] = xr[18*(1 + i)]; + + V0(0) V2(1) V1(2) V2(3) V1(4) V2(5) V1(6) V2(7) + + { +#ifndef MINIMP3_FLOAT_OUTPUT +#if HAVE_SSE + static const f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; + static const f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); + dstr[(15 - i)*nch] = _mm_extract_epi16(pcm8, 1); + dstr[(17 + i)*nch] = _mm_extract_epi16(pcm8, 5); + dstl[(15 - i)*nch] = _mm_extract_epi16(pcm8, 0); + dstl[(17 + i)*nch] = _mm_extract_epi16(pcm8, 4); + dstr[(47 - i)*nch] = _mm_extract_epi16(pcm8, 3); + dstr[(49 + i)*nch] = _mm_extract_epi16(pcm8, 7); + dstl[(47 - i)*nch] = _mm_extract_epi16(pcm8, 2); + dstl[(49 + i)*nch] = _mm_extract_epi16(pcm8, 6); +#else /* HAVE_SSE */ + int16x4_t pcma, pcmb; + a = VADD(a, VSET(0.5f)); + b = VADD(b, VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, VSET(0))))); + vst1_lane_s16(dstr + (15 - i)*nch, pcma, 1); + vst1_lane_s16(dstr + (17 + i)*nch, pcmb, 1); + vst1_lane_s16(dstl + (15 - i)*nch, pcma, 0); + vst1_lane_s16(dstl + (17 + i)*nch, pcmb, 0); + vst1_lane_s16(dstr + (47 - i)*nch, pcma, 3); + vst1_lane_s16(dstr + (49 + i)*nch, pcmb, 3); + vst1_lane_s16(dstl + (47 - i)*nch, pcma, 2); + vst1_lane_s16(dstl + (49 + i)*nch, pcmb, 2); +#endif /* HAVE_SSE */ + +#else /* MINIMP3_FLOAT_OUTPUT */ + + static const f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; + a = VMUL(a, g_scale); + b = VMUL(b, g_scale); +#if HAVE_SSE + _mm_store_ss(dstr + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstr + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstl + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstl + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstr + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstr + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstl + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(2, 2, 2, 2))); + _mm_store_ss(dstl + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(2, 2, 2, 2))); +#else /* HAVE_SSE */ + vst1q_lane_f32(dstr + (15 - i)*nch, a, 1); + vst1q_lane_f32(dstr + (17 + i)*nch, b, 1); + vst1q_lane_f32(dstl + (15 - i)*nch, a, 0); + vst1q_lane_f32(dstl + (17 + i)*nch, b, 0); + vst1q_lane_f32(dstr + (47 - i)*nch, a, 3); + vst1q_lane_f32(dstr + (49 + i)*nch, b, 3); + vst1q_lane_f32(dstl + (47 - i)*nch, a, 2); + vst1q_lane_f32(dstl + (49 + i)*nch, b, 2); +#endif /* HAVE_SSE */ +#endif /* MINIMP3_FLOAT_OUTPUT */ + } + } else +#endif /* HAVE_SIMD */ +#ifdef MINIMP3_ONLY_SIMD + {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ +#else /* MINIMP3_ONLY_SIMD */ + for (i = 14; i >= 0; i--) + { +#define LOAD(k) float w0 = *w++; float w1 = *w++; float *vz = &zlin[4*i - k*64]; float *vy = &zlin[4*i - (15 - k)*64]; +#define S0(k) { int j; LOAD(k); for (j = 0; j < 4; j++) b[j] = vz[j]*w1 + vy[j]*w0, a[j] = vz[j]*w0 - vy[j]*w1; } +#define S1(k) { int j; LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vz[j]*w0 - vy[j]*w1; } +#define S2(k) { int j; LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vy[j]*w1 - vz[j]*w0; } + float a[4], b[4]; + + zlin[4*i] = xl[18*(31 - i)]; + zlin[4*i + 1] = xr[18*(31 - i)]; + zlin[4*i + 2] = xl[1 + 18*(31 - i)]; + zlin[4*i + 3] = xr[1 + 18*(31 - i)]; + zlin[4*(i + 16)] = xl[1 + 18*(1 + i)]; + zlin[4*(i + 16) + 1] = xr[1 + 18*(1 + i)]; + zlin[4*(i - 16) + 2] = xl[18*(1 + i)]; + zlin[4*(i - 16) + 3] = xr[18*(1 + i)]; + + S0(0) S2(1) S1(2) S2(3) S1(4) S2(5) S1(6) S2(7) + + dstr[(15 - i)*nch] = mp3d_scale_pcm(a[1]); + dstr[(17 + i)*nch] = mp3d_scale_pcm(b[1]); + dstl[(15 - i)*nch] = mp3d_scale_pcm(a[0]); + dstl[(17 + i)*nch] = mp3d_scale_pcm(b[0]); + dstr[(47 - i)*nch] = mp3d_scale_pcm(a[3]); + dstr[(49 + i)*nch] = mp3d_scale_pcm(b[3]); + dstl[(47 - i)*nch] = mp3d_scale_pcm(a[2]); + dstl[(49 + i)*nch] = mp3d_scale_pcm(b[2]); + } +#endif /* MINIMP3_ONLY_SIMD */ +} + +static void mp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int nch, mp3d_sample_t *pcm, float *lins) +{ + int i; + for (i = 0; i < nch; i++) + { + mp3d_DCT_II(grbuf + 576*i, nbands); + } + + memcpy(lins, qmf_state, sizeof(float)*15*64); + + for (i = 0; i < nbands; i += 2) + { + mp3d_synth(grbuf + i, pcm + 32*nch*i, nch, lins + i*64); + } +#ifndef MINIMP3_NONSTANDARD_BUT_LOGICAL + if (nch == 1) + { + for (i = 0; i < 15*64; i += 2) + { + qmf_state[i] = lins[nbands*64 + i]; + } + } else +#endif /* MINIMP3_NONSTANDARD_BUT_LOGICAL */ + { + memcpy(qmf_state, lins + nbands*64, sizeof(float)*15*64); + } +} + +static int mp3d_match_frame(const uint8_t *hdr, int mp3_bytes, int frame_bytes) +{ + int i, nmatch; + for (i = 0, nmatch = 0; nmatch < MAX_FRAME_SYNC_MATCHES; nmatch++) + { + i += hdr_frame_bytes(hdr + i, frame_bytes) + hdr_padding(hdr + i); + if (i + HDR_SIZE > mp3_bytes) + return nmatch > 0; + if (!hdr_compare(hdr, hdr + i)) + return 0; + } + return 1; +} + +static int mp3d_find_frame(const uint8_t *mp3, int mp3_bytes, int *free_format_bytes, int *ptr_frame_bytes) +{ + int i, k; + for (i = 0; i < mp3_bytes - HDR_SIZE; i++, mp3++) + { + if (hdr_valid(mp3)) + { + int frame_bytes = hdr_frame_bytes(mp3, *free_format_bytes); + int frame_and_padding = frame_bytes + hdr_padding(mp3); + + for (k = HDR_SIZE; !frame_bytes && k < MAX_FREE_FORMAT_FRAME_SIZE && i + 2*k < mp3_bytes - HDR_SIZE; k++) + { + if (hdr_compare(mp3, mp3 + k)) + { + int fb = k - hdr_padding(mp3); + int nextfb = fb + hdr_padding(mp3 + k); + if (i + k + nextfb + HDR_SIZE > mp3_bytes || !hdr_compare(mp3, mp3 + k + nextfb)) + continue; + frame_and_padding = k; + frame_bytes = fb; + *free_format_bytes = fb; + } + } + if ((frame_bytes && i + frame_and_padding <= mp3_bytes && + mp3d_match_frame(mp3, mp3_bytes - i, frame_bytes)) || + (!i && frame_and_padding == mp3_bytes)) + { + *ptr_frame_bytes = frame_and_padding; + return i; + } + *free_format_bytes = 0; + } + } + *ptr_frame_bytes = 0; + return mp3_bytes; +} + +void mp3dec_init(mp3dec_t *dec) +{ + dec->header[0] = 0; +} + +int mp3dec_decode_frame(mp3dec_t *dec, const uint8_t *mp3, int mp3_bytes, mp3d_sample_t *pcm, mp3dec_frame_info_t *info) +{ + int i = 0, igr, frame_size = 0, success = 1; + const uint8_t *hdr; + bs_t bs_frame[1]; + _Static_assert(sizeof(mp3dec_scratch_t) <= sizeof(dec->scratch_padding), "scratch_padding is too small"); +#define scratch (*(mp3dec_scratch_t*)dec->scratch_padding) + + if (mp3_bytes > 4 && dec->header[0] == 0xff && hdr_compare(dec->header, mp3)) + { + frame_size = hdr_frame_bytes(mp3, dec->free_format_bytes) + hdr_padding(mp3); + if (frame_size != mp3_bytes && (frame_size + HDR_SIZE > mp3_bytes || !hdr_compare(mp3, mp3 + frame_size))) + { + frame_size = 0; + } + } + if (!frame_size) + { + memset(dec, 0, sizeof(mp3dec_t)); + i = mp3d_find_frame(mp3, mp3_bytes, &dec->free_format_bytes, &frame_size); + if (!frame_size || i + frame_size > mp3_bytes) + { + info->frame_bytes = i; + return 0; + } + } + + hdr = mp3 + i; + memcpy(dec->header, hdr, HDR_SIZE); + info->frame_bytes = i + frame_size; + info->frame_offset = i; + info->channels = HDR_IS_MONO(hdr) ? 1 : 2; + info->hz = hdr_sample_rate_hz(hdr); + info->layer = 4 - HDR_GET_LAYER(hdr); + info->bitrate_kbps = hdr_bitrate_kbps(hdr); + + if (!pcm) + { + return hdr_frame_samples(hdr); + } + + bs_init(bs_frame, hdr + HDR_SIZE, frame_size - HDR_SIZE); + if (HDR_IS_CRC(hdr)) + { + get_bits(bs_frame, 16); + } + + if (info->layer == 3) + { + int main_data_begin = L3_read_side_info(bs_frame, scratch.gr_info, hdr); + if (main_data_begin < 0 || bs_frame->pos > bs_frame->limit) + { + mp3dec_init(dec); + return 0; + } + success = L3_restore_reservoir(dec, bs_frame, &scratch, main_data_begin); + if (success) + { + for (igr = 0; igr < (HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm += 576*info->channels) + { + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + L3_decode(dec, &scratch, scratch.gr_info + igr*info->channels, info->channels); + mp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, pcm, scratch.syn[0]); + } + } + L3_save_reservoir(dec, &scratch); + } else + { +#ifdef MINIMP3_ONLY_MP3 + return 0; +#else /* MINIMP3_ONLY_MP3 */ + L12_scale_info sci[1]; + L12_read_scale_info(hdr, bs_frame, sci); + + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + for (i = 0, igr = 0; igr < 3; igr++) + { + if (12 == (i += L12_dequantize_granule(scratch.grbuf[0] + i, bs_frame, sci, info->layer | 1))) + { + i = 0; + L12_apply_scf_384(sci, sci->scf + igr, scratch.grbuf[0]); + mp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, pcm, scratch.syn[0]); + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + pcm += 384*info->channels; + } + if (bs_frame->pos > bs_frame->limit) + { + mp3dec_init(dec); + return 0; + } + } +#endif /* MINIMP3_ONLY_MP3 */ + } + int result = success*hdr_frame_samples(dec->header); +#undef scratch + return result; +} + +#ifdef MINIMP3_FLOAT_OUTPUT +void mp3dec_f32_to_s16(const float *in, int16_t *out, int num_samples) +{ + int i = 0; +#if HAVE_SIMD + int aligned_count = num_samples & ~7; + for(; i < aligned_count; i += 8) + { + static const f4 g_scale = { 32768.0f, 32768.0f, 32768.0f, 32768.0f }; + f4 a = VMUL(VLD(&in[i ]), g_scale); + f4 b = VMUL(VLD(&in[i+4]), g_scale); +#if HAVE_SSE + static const f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; + static const f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); + out[i ] = _mm_extract_epi16(pcm8, 0); + out[i+1] = _mm_extract_epi16(pcm8, 1); + out[i+2] = _mm_extract_epi16(pcm8, 2); + out[i+3] = _mm_extract_epi16(pcm8, 3); + out[i+4] = _mm_extract_epi16(pcm8, 4); + out[i+5] = _mm_extract_epi16(pcm8, 5); + out[i+6] = _mm_extract_epi16(pcm8, 6); + out[i+7] = _mm_extract_epi16(pcm8, 7); +#else /* HAVE_SSE */ + int16x4_t pcma, pcmb; + a = VADD(a, VSET(0.5f)); + b = VADD(b, VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, VSET(0))))); + vst1_lane_s16(out+i , pcma, 0); + vst1_lane_s16(out+i+1, pcma, 1); + vst1_lane_s16(out+i+2, pcma, 2); + vst1_lane_s16(out+i+3, pcma, 3); + vst1_lane_s16(out+i+4, pcmb, 0); + vst1_lane_s16(out+i+5, pcmb, 1); + vst1_lane_s16(out+i+6, pcmb, 2); + vst1_lane_s16(out+i+7, pcmb, 3); +#endif /* HAVE_SSE */ + } +#endif /* HAVE_SIMD */ + for(; i < num_samples; i++) + { + float sample = in[i] * 32768.0f; + if (sample >= 32766.5) + out[i] = (int16_t) 32767; + else if (sample <= -32767.5) + out[i] = (int16_t)-32768; + else + { + int16_t s = (int16_t)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ + out[i] = s; + } + } +} +#endif /* MINIMP3_FLOAT_OUTPUT */ +#endif /* MINIMP3_IMPLEMENTATION && !_MINIMP3_IMPLEMENTATION_GUARD */ diff --git a/Tactility/Private/Tactility/service/displayidle/DisplayIdleService.h b/Tactility/Private/Tactility/service/displayidle/DisplayIdleService.h index 30362948..f32dd658 100644 --- a/Tactility/Private/Tactility/service/displayidle/DisplayIdleService.h +++ b/Tactility/Private/Tactility/service/displayidle/DisplayIdleService.h @@ -63,6 +63,7 @@ public: * arbitrary threads while the timer is running. */ void stopScreensaver(); + void startMcpScreensaver(); /** * Check if the screensaver is currently active. diff --git a/Tactility/Private/Tactility/service/webserver/WebServerService.h b/Tactility/Private/Tactility/service/webserver/WebServerService.h index dcc772b2..32124437 100644 --- a/Tactility/Private/Tactility/service/webserver/WebServerService.h +++ b/Tactility/Private/Tactility/service/webserver/WebServerService.h @@ -81,6 +81,10 @@ private: static error_t handleApiAppsInstall(struct HttpServerRequest* request, void* user_ctx); static error_t handleApiWifi(struct HttpServerRequest* request, void* user_ctx); static error_t handleApiScreenshot(struct HttpServerRequest* request, void* user_ctx); +#ifdef ESP_PLATFORM + static error_t handleApiMcp(struct HttpServerRequest* request, void* user_ctx); + static error_t handleApiScreenRaw(struct HttpServerRequest* request, void* user_ctx); +#endif // Dynamic asset serving static error_t handleAssets(struct HttpServerRequest* request, void* user_ctx); diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index d5d132ef..e4e38c48 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -189,6 +190,7 @@ namespace app { #ifdef ESP_PLATFORM namespace apwebserver { extern const ::AppManifest manifest; } namespace crashdiagnostics { extern const ::AppManifest manifest; } + namespace mcpsettings { extern const ::AppManifest manifest; } #if CONFIG_TT_TDECK_WORKAROUND == 1 namespace keyboardsettings { extern const ::AppManifest manifest; } // T-Deck only for now #endif @@ -252,6 +254,7 @@ static void registerInternalApps() { #ifdef ESP_PLATFORM app_manager_add(&app::apwebserver::manifest); app_manager_add(&app::crashdiagnostics::manifest); + app_manager_add(&app::mcpsettings::manifest); #if defined(CONFIG_TT_TDECK_WORKAROUND) app_manager_add(&app::keyboardsettings::manifest); #endif @@ -433,6 +436,7 @@ static void onLvglStarted() { if (auto* display = lv_display_get_default(); display != nullptr) { auto displaySettings = settings::display::loadOrGetDefault(); lv_display_set_rotation(display, settings::display::toLvglDisplayRotation(displaySettings.orientation)); + lvgl::applyFontSize(displaySettings.fontSize); } lvgl_unlock(); diff --git a/Tactility/Source/app/apphub/AppHubApp.cpp b/Tactility/Source/app/apphub/AppHubApp.cpp index cc1ec12d..65da9569 100644 --- a/Tactility/Source/app/apphub/AppHubApp.cpp +++ b/Tactility/Source/app/apphub/AppHubApp.cpp @@ -107,6 +107,9 @@ void showNoInternet(Context* ctx) { } void showApps(Context* ctx) { + if (ctx->contentWrapper == nullptr) { + return; + } // Refresh rebuilds the list from scratch (cached copy, then again once the network fetch // lands), which would otherwise reset the user's scroll position each time. int32_t scrollY; @@ -266,7 +269,9 @@ void createWidgets(lv_obj_t* parent, void* userData) { void destroyWidgets(void* userData) { auto* ctx = static_cast(userData); - ctx->scrollY = lv_obj_get_scroll_y(ctx->contentWrapper); + if (ctx->contentWrapper != nullptr) { + ctx->scrollY = lv_obj_get_scroll_y(ctx->contentWrapper); + } ctx->contentWrapper = nullptr; ctx->refreshButton = nullptr; } diff --git a/Tactility/Source/app/applist/AppList.cpp b/Tactility/Source/app/applist/AppList.cpp index 2dff60da..7e884d8a 100644 --- a/Tactility/Source/app/applist/AppList.cpp +++ b/Tactility/Source/app/applist/AppList.cpp @@ -121,7 +121,7 @@ extern const ::AppManifest manifest = { .category = APP_CATEGORY_SYSTEM, .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(appMain) }, .flags = APP_MANIFEST_FLAG_HIDDEN, - .stack = { .depth = 2400, .desired_memory_capability = 0 }, + .stack = { .depth = 3072, .desired_memory_capability = 0 }, }; } // namespace diff --git a/Tactility/Source/app/display/Display.cpp b/Tactility/Source/app/display/Display.cpp index 736c84da..f4e09861 100644 --- a/Tactility/Source/app/display/Display.cpp +++ b/Tactility/Source/app/display/Display.cpp @@ -10,6 +10,7 @@ #ifdef ESP_PLATFORM #include #endif +#include #include #include @@ -90,6 +91,21 @@ void onOrientationSet(lv_event_t* event) { } } +void onFontSizeChanged(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + auto* dropdown = static_cast(lv_event_get_target(event)); + uint32_t selected_index = lv_dropdown_get_selected(dropdown); + if (selected_index >= static_cast(settings::display::FontSize::Count)) { + return; + } + auto selected_size = static_cast(selected_index); + if (selected_size != ctx->displaySettings.fontSize) { + ctx->displaySettings.fontSize = selected_size; + ctx->displaySettingsUpdated = true; + lvgl::applyFontSize(selected_size); + } +} + void onTimeoutSwitch(lv_event_t* event) { auto* ctx = static_cast(lv_event_get_user_data(event)); auto* sw = static_cast(lv_event_get_target(event)); @@ -208,6 +224,23 @@ void createWidgets(lv_obj_t* parent, void* userData) { // Set the dropdown to match current orientation enum lv_dropdown_set_selected(orientation_dropdown, static_cast(ctx->displaySettings.orientation)); + // Font size + + auto* font_size_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(font_size_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(font_size_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(font_size_wrapper, 0, LV_STATE_DEFAULT); + + auto* font_size_label = lv_label_create(font_size_wrapper); + lv_label_set_text(font_size_label, "Font size"); + lv_obj_align(font_size_label, LV_ALIGN_LEFT_MID, 0, 0); + + auto* font_size_dropdown = lv_dropdown_create(font_size_wrapper); + lv_dropdown_set_options(font_size_dropdown, "Small\nDefault\nLarge"); + lv_obj_align(font_size_dropdown, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(font_size_dropdown, onFontSizeChanged, LV_EVENT_VALUE_CHANGED, ctx); + lv_dropdown_set_selected(font_size_dropdown, static_cast(ctx->displaySettings.fontSize)); + // Screen timeout // Note: DisplayIdleService doesn't act on these settings for kernel-driver displays yet // (it only looks up the deprecated tt::hal::display::DisplayDevice), so these currently @@ -274,7 +307,7 @@ void createWidgets(lv_obj_t* parent, void* userData) { ctx->screensaverDropdown = lv_dropdown_create(screensaver_wrapper); // Note: order correlates with settings::display::ScreensaverType enum order - lv_dropdown_set_options(ctx->screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan"); + lv_dropdown_set_options(ctx->screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan\nMCP Screen"); lv_obj_align(ctx->screensaverDropdown, LV_ALIGN_RIGHT_MID, 0, 0); lv_obj_add_event_cb(ctx->screensaverDropdown, onScreensaverChanged, LV_EVENT_VALUE_CHANGED, ctx); lv_dropdown_set_selected(ctx->screensaverDropdown, static_cast(ctx->displaySettings.screensaverType)); diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index 3421f93f..34dae908 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -3,12 +3,26 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include -#include #include +#include +#include #include #include @@ -20,64 +34,29 @@ #include #include -#include -#include -#include - namespace tt::app::launcher { constexpr auto* TAG = "Launcher"; +constexpr auto* BACKGROUND_ASSET = "color-field.png"; +constexpr auto* CUSTOM_BACKGROUND_PATH = "tactility/launcher/background.bin"; +constexpr lv_color_t TEXT_COLOR = LV_COLOR_MAKE(0xF8, 0xF5, 0xF2); +constexpr lv_color_t MUTED_TEXT_COLOR = LV_COLOR_MAKE(0xDF, 0xD6, 0xD3); +constexpr lv_color_t ACCENT_COLOR = LV_COLOR_MAKE(0xEC, 0x75, 0x69); namespace { -uint32_t getButtonPadding(UiDensity density, uint32_t buttonSize) { - if (density == LVGL_UI_DENSITY_COMPACT) { - return 0; - } else { - return buttonSize / 8; - } -} +struct LauncherWidgets { + lv_obj_t* timeLabel = nullptr; + lv_obj_t* dateLabel = nullptr; + lv_obj_t* dataLabel = nullptr; + lv_obj_t* statusLabel = nullptr; + lv_timer_t* updateTimer = nullptr; +}; -int32_t computeButtonMargin(int32_t available_span, int32_t total_button_size) { - const int32_t usable = std::max(0, available_span - (3 * total_button_size)); - return std::min(usable / 16, total_button_size / 2); -} - -void onAppPressed(lv_event_t* e) { - auto* appId = static_cast(lv_event_get_user_data(e)); +void onAppPressed(lv_event_t* event) { + const auto* app_id = static_cast(lv_event_get_user_data(event)); uint32_t instance_id = 0; - app_start(appId, 0, nullptr, &instance_id); -} - -lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) { - const auto button_size = lvgl_get_launcher_icon_font_height(); - const auto button_padding = getButtonPadding(uiDensity, button_size); - auto* apps_button = lv_button_create(parent); - - lv_obj_set_style_pad_all(apps_button, static_cast(button_padding), LV_STATE_DEFAULT); - if (isLandscape) { - lv_obj_set_style_margin_hor(apps_button, itemMargin, LV_STATE_DEFAULT); - } else { - lv_obj_set_style_margin_ver(apps_button, itemMargin, LV_STATE_DEFAULT); - } - - lv_obj_set_style_shadow_width(apps_button, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(apps_button, 0, LV_STATE_DEFAULT); - - // create the image first - auto* button_image = lv_image_create(apps_button); - lv_obj_set_style_text_font(button_image, lvgl_get_launcher_icon_font(), LV_STATE_DEFAULT); - lv_image_set_src(button_image, imageFile); - lv_obj_set_style_text_color(button_image, lv_theme_get_color_primary(button_image), LV_STATE_DEFAULT); - lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT); - lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT); - - // Ensure it's square (Material Symbols are slightly wider than tall) - lv_obj_set_size(button_image, button_size, button_size); - - lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId); - - return apps_button; + app_start(app_id, 0, nullptr, &instance_id); } bool shouldShowPowerButton() { @@ -85,180 +64,288 @@ bool shouldShowPowerButton() { device_for_each_of_type(&POWER_SUPPLY_TYPE, &show_power_button, [](Device* device, void* context) { if (device_is_ready(device) && power_supply_supports_power_off(device)) { *static_cast(context) = true; - return false; // stop iterating - } else { - return true; // continue iterating + return false; } + return true; }); return show_power_button; } -void onButtonsWrapperResized(lv_event_t* e); +int getBatteryPercentage() { + Device* power = nullptr; + device_for_each_of_type(&POWER_SUPPLY_TYPE, &power, [](Device* device, void* context) { + if (device_is_ready(device) && power_supply_supports_property(device, POWER_SUPPLY_PROP_CAPACITY)) { + *static_cast(context) = device; + return false; + } + return true; + }); + if (power == nullptr) return -1; -// The screen object outlives this window's own widgets (lvgl-window-manager deletes and -// recreates only the topmost window's widget on every app switch, not the screen itself), so -// the LV_EVENT_SIZE_CHANGED callback registered on it must be removed once buttons_wrapper is -// destroyed, to avoid a dangling user-data pointer the next time the display rotates while a -// different window is topmost. -void onButtonsWrapperDeleted(lv_event_t* e) { - auto* buttons_wrapper = lv_event_get_target_obj(e); - auto* screen = lv_obj_get_screen(buttons_wrapper); - lv_obj_remove_event_cb_with_user_data(screen, onButtonsWrapperResized, buttons_wrapper); + PowerSupplyPropertyValue charge_level; + if (power_supply_get_property(power, POWER_SUPPLY_PROP_CAPACITY, &charge_level) != ERROR_NONE) return -1; + return std::clamp(charge_level.int_value, 0, 100); } -// Re-applies the flex direction and per-button margins when the display orientation changes -// while the launcher is the visible window (these are decided once at createWidgets() based on -// the resolution at that time, so a later rotation needs this to catch up). -void onButtonsWrapperResized(lv_event_t* e) { - auto* buttons_wrapper = static_cast(lv_event_get_user_data(e)); - const auto* display = lv_obj_get_display(buttons_wrapper); - - const auto button_size = lvgl_get_launcher_icon_font_height(); - const auto button_padding = getButtonPadding(lvgl_get_ui_density(), button_size); - const auto total_button_size = button_size + (button_padding * 2); - - const auto horizontal_px = lv_display_get_horizontal_resolution(display); - const auto vertical_px = lv_display_get_vertical_resolution(display); - const bool is_landscape_display = horizontal_px >= vertical_px; - const auto current_flow = lv_obj_get_style_flex_flow(buttons_wrapper, LV_PART_MAIN); - const bool was_landscape = current_flow == LV_FLEX_FLOW_ROW; - if (is_landscape_display == was_landscape) { - return; - } - - lv_obj_set_flex_flow(buttons_wrapper, is_landscape_display ? LV_FLEX_FLOW_ROW : LV_FLEX_FLOW_COLUMN); - - const int32_t margin = is_landscape_display - ? computeButtonMargin(horizontal_px, total_button_size) - : computeButtonMargin(vertical_px, total_button_size); - - const uint32_t child_count = lv_obj_get_child_count(buttons_wrapper); - for (uint32_t i = 0; i < child_count; i++) { - auto* button = lv_obj_get_child(buttons_wrapper, i); - lv_obj_set_style_margin_hor(button, is_landscape_display ? margin : 0, LV_STATE_DEFAULT); - lv_obj_set_style_margin_ver(button, is_landscape_display ? 0 : margin, LV_STATE_DEFAULT); +const char* getWifiStatusIcon(service::wifi::RadioState state) { + using enum service::wifi::RadioState; + switch (state) { + case ConnectionActive: return LVGL_ICON_STATUSBAR_SIGNAL_WIFI_4_BAR; + case Off: + case OffPending: return LVGL_ICON_STATUSBAR_SIGNAL_WIFI_OFF; + default: return LVGL_ICON_STATUSBAR_SIGNAL_WIFI_0_BAR; } } -void createWidgets(lv_obj_t* parent, void*) { - auto* buttons_wrapper = lv_obj_create(parent); +const char* getBatteryStatusIcon(int percentage) { + if (percentage < 0) return ""; + if (percentage >= 95) return LVGL_ICON_STATUSBAR_BATTERY_ANDROID_FRAME_FULL; + if (percentage >= 64) return LVGL_ICON_STATUSBAR_BATTERY_ANDROID_FRAME_5; + if (percentage >= 32) return LVGL_ICON_STATUSBAR_BATTERY_ANDROID_FRAME_3; + return LVGL_ICON_STATUSBAR_BATTERY_ANDROID_FRAME_1; +} - auto ui_density = lvgl_get_ui_density(); - const auto button_size = lvgl_get_launcher_icon_font_height(); - const auto button_padding = getButtonPadding(ui_density, button_size); - const auto total_button_size = button_size + (button_padding * 2); +lv_obj_t* createAppButton(lv_obj_t* parent, const char* icon, const char* app_id, bool emphasized) { + auto* button = lv_button_create(parent); + lv_obj_set_size(button, 52, 52); + lv_obj_set_style_radius(button, 15, LV_PART_MAIN); + lv_obj_set_style_shadow_width(button, 0, LV_PART_MAIN); + lv_obj_set_style_border_width(button, emphasized ? 2 : 1, LV_PART_MAIN); + lv_obj_set_style_border_color(button, emphasized ? ACCENT_COLOR : lv_color_hex(0x746568), LV_PART_MAIN); + lv_obj_set_style_border_opa(button, emphasized ? LV_OPA_COVER : LV_OPA_60, LV_PART_MAIN); + lv_obj_set_style_bg_color(button, lv_color_hex(0x211B20), LV_PART_MAIN); + lv_obj_set_style_bg_opa(button, emphasized ? LV_OPA_80 : LV_OPA_70, LV_PART_MAIN); + lv_obj_set_style_bg_color(button, lv_color_hex(0x392C32), LV_STATE_PRESSED); + lv_obj_set_style_transform_scale(button, 238, LV_STATE_PRESSED); + lv_obj_set_style_outline_color(button, ACCENT_COLOR, LV_STATE_FOCUSED); + lv_obj_set_style_outline_width(button, 2, LV_STATE_FOCUSED); + lv_obj_set_style_outline_pad(button, 2, LV_STATE_FOCUSED); - lv_obj_align(buttons_wrapper, LV_ALIGN_CENTER, 0, 0); - lv_obj_set_size(buttons_wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_style_border_width(buttons_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_flex_grow(buttons_wrapper, 1); + auto* image = lv_image_create(button); + lv_obj_set_size(image, 36, 36); + lv_obj_center(image); + lv_obj_set_style_text_font(image, lvgl_get_launcher_icon_font(), LV_STATE_DEFAULT); + lv_image_set_src(image, icon); + lv_obj_set_style_image_recolor(image, TEXT_COLOR, LV_STATE_DEFAULT); + lv_obj_set_style_image_recolor_opa(image, LV_OPA_COVER, LV_STATE_DEFAULT); + lv_obj_add_event_cb(button, onAppPressed, LV_EVENT_SHORT_CLICKED, const_cast(app_id)); + return button; +} - // Fix for button selection - lv_obj_set_style_pad_all(buttons_wrapper, 6, LV_STATE_DEFAULT); - - const auto* display = lv_obj_get_display(parent); - const auto horizontal_px = lv_display_get_horizontal_resolution(display); - const auto vertical_px = lv_display_get_vertical_resolution(display); - const bool is_landscape_display = horizontal_px >= vertical_px; - if (is_landscape_display) { - lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_ROW); +void updateInformation(LauncherWidgets& widgets) { + const std::time_t now = std::time(nullptr); + std::tm local_time {}; + localtime_r(&now, &local_time); + char time_buffer[12]; + char date_buffer[40]; + if (local_time.tm_year >= 125) { + if (settings::isTimeFormat24Hour()) { + std::strftime(time_buffer, sizeof(time_buffer), "%H:%M", &local_time); + } else { + std::strftime(time_buffer, sizeof(time_buffer), "%I:%M", &local_time); + if (time_buffer[0] == '0') std::memmove(time_buffer, time_buffer + 1, std::strlen(time_buffer)); + } + std::strftime(date_buffer, sizeof(date_buffer), "%A, %B %e", &local_time); } else { - lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_COLUMN); + std::strcpy(time_buffer, "--:--"); + std::strcpy(date_buffer, "Set date and time"); + } + lv_label_set_text(widgets.timeLabel, time_buffer); + lv_label_set_text(widgets.dateLabel, date_buffer); + + const auto wifi_state = service::wifi::getRadioState(); + std::string sd_card_path; + const bool sd_ready = findFirstMountedSdCardPath(sd_card_path); + const int battery_percentage = getBatteryPercentage(); + char data_buffer[80]; + if (battery_percentage >= 0) { + std::snprintf(data_buffer, sizeof(data_buffer), "%s • %s • %d%%", + wifi_state == service::wifi::RadioState::ConnectionActive ? "Wi-Fi connected" : "Wi-Fi offline", + sd_ready ? "SD ready" : "No SD", battery_percentage); + } else { + std::snprintf(data_buffer, sizeof(data_buffer), "%s • %s", + wifi_state == service::wifi::RadioState::ConnectionActive ? "Wi-Fi connected" : "Wi-Fi offline", + sd_ready ? "SD ready" : "No SD"); + } + lv_label_set_text(widgets.dataLabel, data_buffer); + + char status_buffer[24]; + std::snprintf(status_buffer, sizeof(status_buffer), "%s%s", getWifiStatusIcon(wifi_state), getBatteryStatusIcon(battery_percentage)); + lv_label_set_text(widgets.statusLabel, status_buffer); +} + +void onUpdateTimer(lv_timer_t* timer) { + updateInformation(*static_cast(lv_timer_get_user_data(timer))); +} + +std::string getDefaultBackgroundPath() { + return std::string(file::MOUNT_POINT_SYSTEM) + "/app/Launcher/assets/" + BACKGROUND_ASSET; +} + +void createWidgets(lv_obj_t* parent, void* user_data) { + auto& widgets = *static_cast(user_data); + lv_obj_set_style_bg_color(parent, lv_color_hex(0x211A20), LV_PART_MAIN); + lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_pad_all(parent, 0, LV_PART_MAIN); + lv_obj_clear_flag(parent, LV_OBJ_FLAG_SCROLLABLE); + + auto* display = lv_obj_get_display(parent); + const auto display_width = lv_display_get_horizontal_resolution(display); + const auto display_height = lv_display_get_vertical_resolution(display); + const bool is_portrait = display_height > display_width; + + auto background_path = lvgl::PATH_PREFIX + getDefaultBackgroundPath(); + std::string sd_card_path; + if (findFirstMountedSdCardPath(sd_card_path)) { + const auto custom_background_path = file::getChildPath(sd_card_path, CUSTOM_BACKGROUND_PATH); + if (file::isFile(custom_background_path)) { + const auto lvgl_custom_background_path = lvgl::PATH_PREFIX + custom_background_path; + lv_image_header_t header {}; + if (lv_image_decoder_get_info(lvgl_custom_background_path.c_str(), &header) == LV_RESULT_OK) { + if (header.w >= display_width && header.h >= display_height) { + background_path = lvgl_custom_background_path; + LOG_I(TAG, "Using SD background %s (%ux%u, format 0x%02x)", + custom_background_path.c_str(), header.w, header.h, header.cf); + } else { + LOG_W(TAG, "Ignoring undersized SD background %s (%ux%u for %dx%d display)", + custom_background_path.c_str(), header.w, header.h, display_width, display_height); + } + } else { + LOG_W(TAG, "Ignoring invalid SD background %s", custom_background_path.c_str()); + } + } } - const int32_t margin = is_landscape_display - ? computeButtonMargin(lv_display_get_horizontal_resolution(display), total_button_size) - : computeButtonMargin(lv_display_get_vertical_resolution(display), total_button_size); + auto* background = lv_image_create(parent); + lv_image_set_src(background, background_path.c_str()); + lv_obj_align(background, LV_ALIGN_CENTER, 0, 0); + lv_obj_add_flag(background, LV_OBJ_FLAG_IGNORE_LAYOUT); - auto* app_list_button = createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_APPS, "tactility.applist", margin, is_landscape_display); - createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_FOLDER, "tactility.files", margin, is_landscape_display); - createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "tactility.settings", margin, is_landscape_display); + widgets.timeLabel = lv_label_create(parent); + lv_label_set_text(widgets.timeLabel, "--:--"); + lv_obj_set_style_text_color(widgets.timeLabel, TEXT_COLOR, LV_PART_MAIN); +#if LV_FONT_MONTSERRAT_48 + lv_obj_set_style_text_font(widgets.timeLabel, &lv_font_montserrat_48, LV_PART_MAIN); +#else + lv_obj_set_style_text_font(widgets.timeLabel, lvgl_get_text_font(FONT_SIZE_LARGE), LV_PART_MAIN); +#endif + lv_obj_align(widgets.timeLabel, LV_ALIGN_TOP_LEFT, 18, 58); - // The launcher's container is several levels below the screen, and LVGL only sends - // LV_EVENT_SIZE_CHANGED to the screen object itself on a resolution change - so the - // handler is attached there, with buttons_wrapper passed through as user data. - lv_obj_add_event_cb(lv_obj_get_screen(parent), onButtonsWrapperResized, LV_EVENT_SIZE_CHANGED, buttons_wrapper); - lv_obj_add_event_cb(buttons_wrapper, onButtonsWrapperDeleted, LV_EVENT_DELETE, nullptr); + widgets.dateLabel = lv_label_create(parent); + lv_obj_set_style_text_font(widgets.dateLabel, lvgl_get_text_font(FONT_SIZE_LARGE), LV_PART_MAIN); + lv_obj_set_style_text_color(widgets.dateLabel, TEXT_COLOR, LV_PART_MAIN); + lv_obj_align(widgets.dateLabel, LV_ALIGN_TOP_LEFT, 20, 118); + + widgets.dataLabel = lv_label_create(parent); + lv_obj_set_width(widgets.dataLabel, is_portrait ? display_width - 40 : 225); + lv_label_set_long_mode(widgets.dataLabel, LV_LABEL_LONG_MODE_WRAP); + lv_obj_set_style_text_font(widgets.dataLabel, lvgl_get_text_font(FONT_SIZE_SMALL), LV_PART_MAIN); + lv_obj_set_style_text_color(widgets.dataLabel, MUTED_TEXT_COLOR, LV_PART_MAIN); + lv_obj_align(widgets.dataLabel, LV_ALIGN_TOP_LEFT, 20, 148); + + widgets.statusLabel = lv_label_create(parent); + lv_obj_set_style_text_font(widgets.statusLabel, lvgl_get_statusbar_icon_font(), LV_PART_MAIN); + lv_obj_set_style_text_color(widgets.statusLabel, TEXT_COLOR, LV_PART_MAIN); + lv_obj_align(widgets.statusLabel, LV_ALIGN_TOP_RIGHT, -15, 8); + + auto* button_rail = lv_obj_create(parent); + if (is_portrait) { + lv_obj_set_size(button_rail, 184, 60); + lv_obj_align(button_rail, LV_ALIGN_BOTTOM_MID, 0, -7); + lv_obj_set_flex_flow(button_rail, LV_FLEX_FLOW_ROW); + } else { + lv_obj_set_size(button_rail, 60, 184); + lv_obj_align(button_rail, LV_ALIGN_RIGHT_MID, -7, 8); + lv_obj_set_flex_flow(button_rail, LV_FLEX_FLOW_COLUMN); + } + lv_obj_set_flex_align(button_rail, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_all(button_rail, 0, LV_PART_MAIN); + lv_obj_set_style_border_width(button_rail, 0, LV_PART_MAIN); + lv_obj_set_style_bg_opa(button_rail, LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_clear_flag(button_rail, LV_OBJ_FLAG_SCROLLABLE); + + auto* app_list_button = createAppButton(button_rail, LVGL_ICON_LAUNCHER_APPS, "tactility.applist", true); + createAppButton(button_rail, LVGL_ICON_LAUNCHER_FOLDER, "tactility.files", false); + createAppButton(button_rail, LVGL_ICON_LAUNCHER_SETTINGS, "tactility.settings", false); - // Some devices (e.g. T-Lora Pager) have no other way to power off, so the - // button stays in the launcher; the confirmation flow lives in the PowerOff app. if (shouldShowPowerButton()) { auto* power_button = lv_button_create(parent); - lv_obj_set_style_pad_all(power_button, 8, 0); - lv_obj_align(power_button, LV_ALIGN_BOTTOM_MID, 0, -10); - lv_obj_add_event_cb(power_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)"tactility.poweroff"); - lv_obj_set_style_shadow_width(power_button, 0, LV_STATE_DEFAULT); - lv_obj_set_style_bg_opa(power_button, 0, LV_PART_MAIN); - + lv_obj_set_size(power_button, 36, 36); + lv_obj_align(power_button, LV_ALIGN_BOTTOM_LEFT, 16, -12); + lv_obj_set_style_radius(power_button, LV_RADIUS_CIRCLE, LV_PART_MAIN); + lv_obj_set_style_shadow_width(power_button, 0, LV_PART_MAIN); + lv_obj_set_style_bg_color(power_button, lv_color_hex(0x211B20), LV_PART_MAIN); + lv_obj_set_style_bg_opa(power_button, LV_OPA_70, LV_PART_MAIN); + lv_obj_add_event_cb(power_button, onAppPressed, LV_EVENT_SHORT_CLICKED, const_cast("tactility.poweroff")); auto* power_label = lv_label_create(power_button); lv_label_set_text(power_label, LV_SYMBOL_POWER); - lv_obj_set_style_text_color(power_label, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT); + lv_obj_set_style_text_color(power_label, TEXT_COLOR, LV_PART_MAIN); + lv_obj_center(power_label); } - // If we don't have a touch device, we assume there's some other kind of input like a keyboard, an encoder or button control - // In that scenario we want to automatically have the app list button selected so the user doesn't have to press the widget selection - // an extra time. if (!device_has_active_by_type(&POINTER_TYPE)) { - // lv_obj_update_layout(parent); // Resolve flex layout first, so focus/state invalidate against final coords lv_group_focus_obj(app_list_button); lv_obj_add_state(app_list_button, LV_STATE_FOCUS_KEY); } + + updateInformation(widgets); + widgets.updateTimer = lv_timer_create(onUpdateTimer, 1000, &widgets); +} + +void destroyWidgets(void* user_data) { + auto& widgets = *static_cast(user_data); + if (widgets.updateTimer != nullptr) { + lv_timer_delete(widgets.updateTimer); + widgets.updateTimer = nullptr; + } + widgets.timeLabel = nullptr; + widgets.dateLabel = nullptr; + widgets.dataLabel = nullptr; + widgets.statusLabel = nullptr; } void runAutoStart() { settings::BootSettings boot_properties; AppManifest manifest; - if ( - // Auto-start due to built-in requirement - strcmp(CONFIG_TT_AUTO_START_APP_ID, "") != 0 && - app_manager_find_manifest(CONFIG_TT_AUTO_START_APP_ID, &manifest) == ERROR_NONE - ) { + if (strcmp(CONFIG_TT_AUTO_START_APP_ID, "") != 0 && + app_manager_find_manifest(CONFIG_TT_AUTO_START_APP_ID, &manifest) == ERROR_NONE) { LOG_I(TAG, "Starting %s", CONFIG_TT_AUTO_START_APP_ID); uint32_t app_launch_id; app_start(CONFIG_TT_AUTO_START_APP_ID, 0, nullptr, &app_launch_id); - } else if ( - // Auto-start due to user configuration - settings::loadBootSettings(boot_properties) && - !boot_properties.autoStartAppId.empty() && - app_manager_find_manifest(boot_properties.autoStartAppId.c_str(), &manifest) == ERROR_NONE - ) { + } else if (settings::loadBootSettings(boot_properties) && + !boot_properties.autoStartAppId.empty() && + app_manager_find_manifest(boot_properties.autoStartAppId.c_str(), &manifest) == ERROR_NONE) { LOG_I(TAG, "Starting %s", boot_properties.autoStartAppId.c_str()); uint32_t app_launch_id; app_start(boot_properties.autoStartAppId.c_str(), 0, nullptr, &app_launch_id); - } else { - // No auto-start, consider running system setup - if (!setup::isCompleted()) { - setup::start(); - } + } else if (!setup::isCompleted()) { + setup::start(); } } int32_t appMain(int argc, char* argv[]) { - uint32_t appInstanceId = app_scheduler_current_app_id(); + uint32_t app_instance_id = app_scheduler_current_app_id(); runAutoStart(); + LauncherWidgets widgets; + WindowId window = window_manager_create_ext(app_instance_id, createWidgets, destroyWidgets, &widgets); + TaskEventGroup event_group {}; task_event_group_construct(&event_group); - AppEventSubscription sub {}; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE); - WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr); - - // The launcher is meant to stay resident (it's the home screen) - it only gives up its - // thread when app-module's scheduler asks it to (e.g. another new-model app is started). while (true) { task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY); - - bool shouldClose = false; + bool should_close = false; AppEvent event {}; while (app_event_poll(&sub, &event) == ERROR_NONE) { if (event.type == APP_EVENT_CLOSE) { - shouldClose = true; + should_close = true; break; } } - if (shouldClose) break; + if (should_close) break; } window_manager_remove(window); @@ -275,16 +362,13 @@ extern const ::AppManifest manifest = { .category = APP_CATEGORY_SYSTEM, .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(appMain) }, .flags = APP_MANIFEST_FLAG_HIDDEN, - // No file IO, so callstack can be in external RAM - .stack = { .depth = 3072 , .desired_memory_capability = MEMORY_CAPABILITY_EXTERNAL } + .stack = { .depth = 3072, .desired_memory_capability = MEMORY_CAPABILITY_EXTERNAL } }; -// Kept for Tactility/Private/Tactility/app/launcher/Launcher.h's existing declaration (still -// used by the old, unconverted CrashDiagnostics app to return to the launcher after a crash). uint32_t start() { uint32_t instance_id = 0; app_start(manifest.id, 0, nullptr, &instance_id); return instance_id; } -} // namespace +} // namespace tt::app::launcher diff --git a/Tactility/Source/app/mcpsettings/McpSettingsApp.cpp b/Tactility/Source/app/mcpsettings/McpSettingsApp.cpp new file mode 100644 index 00000000..106efa49 --- /dev/null +++ b/Tactility/Source/app/mcpsettings/McpSettingsApp.cpp @@ -0,0 +1,98 @@ +#ifdef ESP_PLATFORM +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tt::app::mcpsettings { +constexpr auto* TAG = "McpSettingsApp"; +extern const ::AppManifest manifest; +namespace { +struct Context { + uint32_t appInstanceId; + settings::mcp::McpSettings mcpSettings; + settings::webserver::WebServerSettings wsSettings; + bool updated = false; + lv_obj_t* switchMcpEnabled = nullptr; + lv_obj_t* labelUrlValue = nullptr; +}; + +void updateUrlDisplay(Context* ctx) { + if (ctx->labelUrlValue == nullptr) return; + if (!ctx->mcpSettings.mcpEnabled) { lv_label_set_text(ctx->labelUrlValue, "Disabled"); return; } + std::string url = "http://"; + bool ipAdded = false; + for (const char* key : {"WIFI_STA_DEF", "WIFI_AP_DEF"}) { + auto* netif = esp_netif_get_handle_from_ifkey(key); + if (netif != nullptr) { + esp_netif_ip_info_t info; + if (esp_netif_get_ip_info(netif, &info) == ESP_OK && info.ip.addr != 0) { + char ip[16]; snprintf(ip, sizeof(ip), IPSTR, IP2STR(&info.ip)); + url += ip; ipAdded = true; break; + } + } + } + if (!ipAdded) url += ctx->wsSettings.wifiMode == settings::webserver::WiFiMode::AccessPoint ? "192.168.4.1" : "Connecting..."; + if (url.starts_with("http://") && ctx->wsSettings.webServerPort != 80) url += ":" + std::to_string(ctx->wsSettings.webServerPort); + url += "/api/mcp"; + lv_label_set_text(ctx->labelUrlValue, url.c_str()); +} + +void onBackPressed(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + app_event_emit_close(ctx->appInstanceId); +} + +void onMcpEnabledSwitch(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + const bool enabled = lv_obj_has_state(ctx->switchMcpEnabled, LV_STATE_CHECKED); + getMainDispatcher().dispatch([ctx, enabled] { + ctx->mcpSettings.mcpEnabled = enabled; ctx->updated = true; + lvgl_lock(); updateUrlDisplay(ctx); lvgl_unlock(); + if (!settings::mcp::save(ctx->mcpSettings)) LOG_W(TAG, "Failed to persist MCP settings"); + service::webserver::getPubsub()->publish(service::webserver::WebServerEvent::WebServerSettingsChanged); + service::webserver::setWebServerEnabled(enabled); + }); +} + +void createWidgets(lv_obj_t* parent, void* userData) { + auto* ctx = static_cast(userData); + ctx->wsSettings = settings::webserver::loadOrGetDefault(); + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); + auto* toolbar = lvgl_toolbar_create(parent, "MCP Settings"); + lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx); + ctx->switchMcpEnabled = lvgl_toolbar_add_switch_action(toolbar); + if (ctx->mcpSettings.mcpEnabled) lv_obj_add_state(ctx->switchMcpEnabled, LV_STATE_CHECKED); + lv_obj_add_event_cb(ctx->switchMcpEnabled, onMcpEnabledSwitch, LV_EVENT_VALUE_CHANGED, ctx); + auto* main = lv_obj_create(parent); lv_obj_set_flex_flow(main, LV_FLEX_FLOW_COLUMN); lv_obj_set_width(main, LV_PCT(100)); lv_obj_set_flex_grow(main, 1); + auto* wrapper = lv_obj_create(main); lv_obj_set_size(wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_style_pad_all(wrapper, 10, LV_STATE_DEFAULT); lv_obj_set_style_border_width(wrapper, 1, LV_STATE_DEFAULT); lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_flex_cross_place(wrapper, LV_FLEX_ALIGN_START, 0); + auto* title = lv_label_create(wrapper); lv_label_set_text(title, "MCP Endpoint URL:"); + ctx->labelUrlValue = lv_label_create(wrapper); updateUrlDisplay(ctx); + auto* info = lv_label_create(main); lv_label_set_long_mode(info, LV_LABEL_LONG_WRAP); lv_obj_set_width(info, LV_PCT(95)); + lv_label_set_text(info, "MCP (Model Context Protocol) Screen service allows LLMs to interact with the device screen, audio, and tools directly.\n\nEndpoints:\n- POST /api/mcp (JSON-RPC tools)\n- POST /api/screen/raw (big-endian RGB565 writes)\n\nTo show the LLM canvas, select 'MCP Screen' in Settings -> Display -> Screensaver. The canvas also pops up automatically when an LLM sends a draw command."); +} + +int32_t appMain(int, char**) { + Context ctx{}; ctx.appInstanceId = app_scheduler_current_app_id(); ctx.mcpSettings = settings::mcp::loadOrGetDefault(); + TaskEventGroup group{}; task_event_group_construct(&group); AppEventSubscription sub{}; check(app_event_subscribe(&sub, &group) == ERROR_NONE); + auto window = window_manager_create(ctx.appInstanceId, createWidgets, &ctx); bool close = false; + while (!close) { task_event_group_wait_any(&group, nullptr, portMAX_DELAY); AppEvent event{}; while (app_event_poll(&sub, &event) == ERROR_NONE) if (event.type == APP_EVENT_CLOSE) { close = true; break; } } + window_manager_remove(window); check(app_event_unsubscribe(&sub) == ERROR_NONE); task_event_group_destruct(&group); return 0; +} +} +extern const ::AppManifest manifest = { .id = "McpSettings", .name = "MCP Screen", .category = APP_CATEGORY_SETTINGS, .location = { APP_LOCATION_MEMORY, reinterpret_cast(appMain) }, .flags = 0, .stack = { .depth = 8192, .desired_memory_capability = 0 } }; +} +#endif diff --git a/Tactility/Source/app/settings/Settings.cpp b/Tactility/Source/app/settings/Settings.cpp index 3fed7fc1..9a736091 100644 --- a/Tactility/Source/app/settings/Settings.cpp +++ b/Tactility/Source/app/settings/Settings.cpp @@ -118,7 +118,7 @@ extern const ::AppManifest manifest = { .category = APP_CATEGORY_SYSTEM, .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(appMain) }, .flags = APP_MANIFEST_FLAG_HIDDEN, - .stack = { .depth = 2400, .desired_memory_capability = 0 }, + .stack = { .depth = 3072, .desired_memory_capability = 0 }, }; } // namespace diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp new file mode 100644 index 00000000..67b3271b --- /dev/null +++ b/Tactility/Source/lvgl/Lvgl.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include +#include + +namespace tt::lvgl { + +void applyFontSize(settings::display::FontSize fontSize) { + LvglFontScale scale; + switch (fontSize) { + using enum settings::display::FontSize; + case Small: + scale = FONT_SCALE_SMALL; + break; + case Large: + scale = FONT_SCALE_LARGE; + break; + case Default: + default: + scale = FONT_SCALE_DEFAULT; + break; + } + + lvgl_set_text_font_scale(scale); + const lv_font_t* font = lvgl_get_text_font(FONT_SIZE_DEFAULT); + + for (lv_display_t* display = lv_display_get_next(nullptr); + display != nullptr; + display = lv_display_get_next(display)) { +#if LV_USE_THEME_DEFAULT + if (lv_display_get_theme(display) == lv_theme_default_get()) { + lv_obj_t* screen = lv_display_get_screen_active(display); + lv_theme_default_init( + display, + lv_theme_get_color_primary(screen), + lv_theme_get_color_secondary(screen), + LV_THEME_DEFAULT_DARK, + font + ); + } +#endif + // The display layers are independent inheritance roots. Updating all of them makes + // the new size visible immediately in regular screens, overlays, and the status bar. + lv_obj_set_style_text_font(lv_display_get_screen_active(display), font, LV_PART_MAIN); + lv_obj_set_style_text_font(lv_display_get_layer_top(display), font, LV_PART_MAIN); + lv_obj_set_style_text_font(lv_display_get_layer_sys(display), font, LV_PART_MAIN); + lv_obj_set_style_text_font(lv_display_get_layer_bottom(display), font, LV_PART_MAIN); + } +} + +} // namespace tt::lvgl diff --git a/Tactility/Source/mcp/McpSystem.cpp b/Tactility/Source/mcp/McpSystem.cpp new file mode 100644 index 00000000..51f48959 --- /dev/null +++ b/Tactility/Source/mcp/McpSystem.cpp @@ -0,0 +1,2000 @@ +#ifdef ESP_PLATFORM + +#include +#include +#include +#include +#include +#include + +constexpr auto* TAG = "McpSystem"; +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MINIMP3_IMPLEMENTATION +#define MINIMP3_NO_SIMD +#include + +namespace tt::mcp { + + +static void* psram_malloc(size_t size) { + void* ptr = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (ptr != nullptr) { + return ptr; + } + return malloc(size); +} + +#define AUDIO_SAMPLE_RATE 16000 +#define AUDIO_BITS_PER_SAMPLE 16 +#define AUDIO_CHUNK_SAMPLES 512 +#define AUDIO_CHUNK_BYTES (AUDIO_CHUNK_SAMPLES * 2) +#define MP3_INPUT_BUFFER_SIZE 16384 + +struct WavInfo { + uint16_t channels; + uint32_t sample_rate; + uint16_t bits_per_sample; + size_t data_offset; + size_t data_size; +}; + +// Global singleton state +McpSystemState& getState() { + static McpSystemState state; + return state; +} + +static bool ascii_space(uint8_t value) { + return value == ' ' || value == '\t' || value == '\r' || + value == '\n' || value == '\f' || value == '\v'; +} + +static bool ascii_digit(uint8_t value) { + return value >= '0' && value <= '9'; +} + +static uint16_t read_le16(const uint8_t* value) { + return (uint16_t)value[0] | ((uint16_t)value[1] << 8); +} + +static uint32_t read_le32(const uint8_t* value) { + return (uint32_t)value[0] | + ((uint32_t)value[1] << 8) | + ((uint32_t)value[2] << 16) | + ((uint32_t)value[3] << 24); +} + +static bool display_ready(McpSystemState& state) { + return state.drawArea != nullptr && + state.framebuffer != nullptr && + state.drawWidth > 0 && + state.drawHeight > 0; +} + +static bool ensureOverrideScreen() { + auto& state = getState(); + if (state.drawArea == nullptr) { + // Activate the MCP screensaver via DisplayIdle (force McpScreensaver type) + auto idleService = service::displayidle::findService(); + if (idleService) { + LOG_I(TAG, "MCP draw triggered: activating MCP screensaver"); + idleService->startMcpScreensaver(); + // Wait up to 500ms for the canvas to be registered by McpScreensaver::start() + for (int i = 0; i < 10; ++i) { + vTaskDelay(pdMS_TO_TICKS(50)); + if (state.drawArea != nullptr) { + break; + } + } + } else { + LOG_W(TAG, "DisplayIdle service not found; cannot activate MCP screensaver"); + } + } + return state.drawArea != nullptr; +} + +static uint16_t rgb888_to_rgb565(uint8_t red, uint8_t green, uint8_t blue) { + return (uint16_t)(((red & 0xF8) << 8) | + ((green & 0xFC) << 3) | + (blue >> 3)); +} + +static void put_pixel(McpSystemState& state, int x, int y, uint16_t color) { + if (x >= 0 && y >= 0 && x < state.drawWidth && y < state.drawHeight) { + state.framebuffer[(size_t)y * state.drawWidth + x] = color; + } +} + +bool clearScreen(int color) { + if (!ensureOverrideScreen()) return false; + auto& state = getState(); + bool success = false; + if (lvgl_try_lock(pdMS_TO_TICKS(1500))) { + if (display_ready(state)) { + lv_obj_clean(state.drawArea); + uint16_t fill = color == 1 ? 0xFFFF : 0x0000; + size_t pixel_count = (size_t)state.drawWidth * state.drawHeight; + for (size_t i = 0; i < pixel_count; ++i) { + state.framebuffer[i] = fill; + } + lv_obj_invalidate(state.drawArea); + state.drawColor = color; + state.overrideActive = true; + success = true; + } + lvgl_unlock(); + } + return success; +} + +bool drawText(const std::string& text, int x, int y, int size) { + if (!ensureOverrideScreen()) return false; + auto& state = getState(); + bool success = false; + if (lvgl_try_lock(pdMS_TO_TICKS(1500))) { + if (display_ready(state)) { + int max_x = state.drawWidth > 0 ? state.drawWidth - 1 : 0; + int max_y = state.drawHeight > 0 ? state.drawHeight - 1 : 0; + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x > max_x) x = max_x; + if (y > max_y) y = max_y; + + lv_obj_t* label = lv_label_create(state.drawArea); + lv_label_set_text(label, text.c_str()); + lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); + lv_obj_set_width(label, LV_MAX(1, state.drawWidth - x)); + lv_obj_set_pos(label, x, y); + + lv_obj_set_style_text_color( + label, + state.drawColor == 0 ? lv_color_white() : lv_color_black(), + LV_PART_MAIN + ); + +#if LV_FONT_MONTSERRAT_24 + if (size >= 2) { + lv_obj_set_style_text_font(label, &lv_font_montserrat_24, LV_PART_MAIN); + } +#endif + + lv_obj_invalidate(state.drawArea); // Trigger repaint + state.overrideActive = true; + success = true; + } + lvgl_unlock(); + } + return success; +} + +bool drawRgb565(const uint8_t* data, size_t size, int x, int y, int w, int h) { + if (!ensureOverrideScreen()) return false; + auto& state = getState(); + if (data == NULL || w <= 0 || h <= 0 || size < (size_t)w * h * 2) { + return false; + } + + bool success = false; + if (lvgl_try_lock(pdMS_TO_TICKS(2000))) { + if (display_ready(state)) { + for (int source_y = 0; source_y < h; ++source_y) { + int destination_y = y + source_y; + if (destination_y < 0 || destination_y >= state.drawHeight) { + continue; + } + for (int source_x = 0; source_x < w; ++source_x) { + int destination_x = x + source_x; + if (destination_x < 0 || destination_x >= state.drawWidth) { + continue; + } + size_t offset = ((size_t)source_y * w + source_x) * 2; + uint16_t color = ((uint16_t)data[offset] << 8) | data[offset + 1]; + put_pixel(state, destination_x, destination_y, ~color); + } + } + lv_obj_invalidate(state.drawArea); + state.overrideActive = true; + success = true; + } + lvgl_unlock(); + } + return success; +} + +bool drawBmp(const uint8_t* data, size_t size, int x, int y) { + if (!ensureOverrideScreen()) return false; + auto& state = getState(); + if (data == NULL || size < 54 || data[0] != 'B' || data[1] != 'M') { + return false; + } + + uint32_t pixel_offset = read_le32(data + 10); + uint32_t dib_size = read_le32(data + 14); + int32_t w = (int32_t)read_le32(data + 18); + int32_t signed_height = (int32_t)read_le32(data + 22); + uint16_t planes = read_le16(data + 26); + uint16_t bits_per_pixel = read_le16(data + 28); + uint32_t compression = read_le32(data + 30); + if (dib_size < 40 || w <= 0 || signed_height == 0 || planes != 1 || + (bits_per_pixel != 24 && bits_per_pixel != 32) || compression != 0) { + return false; + } + + int h = signed_height < 0 ? -signed_height : signed_height; + bool top_down = signed_height < 0; + size_t row_stride = (((size_t)w * bits_per_pixel + 31) / 32) * 4; + if (pixel_offset > size || + row_stride > size || + (size_t)h > (size - pixel_offset) / row_stride) { + return false; + } + + bool success = false; + if (lvgl_try_lock(pdMS_TO_TICKS(2500))) { + if (display_ready(state)) { + size_t bytes_per_pixel = bits_per_pixel / 8; + for (int source_y = 0; source_y < h; ++source_y) { + int file_y = top_down ? source_y : (h - 1 - source_y); + const uint8_t* row = data + pixel_offset + (size_t)file_y * row_stride; + for (int source_x = 0; source_x < w; ++source_x) { + const uint8_t* pixel = row + (size_t)source_x * bytes_per_pixel; + put_pixel( + state, + x + source_x, + y + source_y, + ~rgb888_to_rgb565(pixel[2], pixel[1], pixel[0]) + ); + } + } + lv_obj_invalidate(state.drawArea); + state.overrideActive = true; + success = true; + } + lvgl_unlock(); + } + return success; +} + +static bool pbm_next_number(const uint8_t* data, size_t data_size, size_t* offset, int* result) { + while (*offset < data_size) { + if (data[*offset] == '#') { + while (*offset < data_size && data[*offset] != '\n') { + (*offset)++; + } + } else if (ascii_space(data[*offset])) { + (*offset)++; + } else { + break; + } + } + if (*offset >= data_size || !ascii_digit(data[*offset])) { + return false; + } + + int value = 0; + while (*offset < data_size && ascii_digit(data[*offset])) { + value = value * 10 + (data[*offset] - '0'); + (*offset)++; + } + *result = value; + return true; +} + +bool drawPbm(const uint8_t* data, size_t size, int x, int y) { + if (!ensureOverrideScreen()) return false; + auto& state = getState(); + if (data == NULL || size < 8 || data[0] != 'P' || data[1] != '4') { + return false; + } + + size_t offset = 2; + int w = 0; + int h = 0; + if (!pbm_next_number(data, size, &offset, &w) || + !pbm_next_number(data, size, &offset, &h) || + w <= 0 || h <= 0) { + return false; + } + if (offset >= size || !ascii_space(data[offset])) { + return false; + } + if (data[offset] == '\r' && offset + 1 < size && data[offset + 1] == '\n') { + offset += 2; + } else { + offset++; + } + + size_t row_bytes = ((size_t)w + 7) / 8; + if (offset > size || (size_t)h > (size - offset) / row_bytes) { + return false; + } + + bool success = false; + if (lvgl_try_lock(pdMS_TO_TICKS(2000))) { + if (display_ready(state)) { + for (int source_y = 0; source_y < h; ++source_y) { + const uint8_t* row = data + offset + (size_t)source_y * row_bytes; + for (int source_x = 0; source_x < w; ++source_x) { + bool active = (row[source_x >> 3] & (0x80 >> (source_x & 7))) != 0; + put_pixel(state, x + source_x, y + source_y, active ? 0x0000 : 0xFFFF); + } + } + lv_obj_invalidate(state.drawArea); + state.overrideActive = true; + success = true; + } + lvgl_unlock(); + } + return success; +} + +std::string getScreenshotPbmBase64() { + auto& state = getState(); + if (!display_ready(state)) { + return ""; + } + + size_t row_bytes = ((size_t)state.drawWidth + 7) / 8; + size_t header_size = 32; + size_t payload_size = row_bytes * state.drawHeight; + size_t pbm_size = header_size + payload_size; + uint8_t* pbm = (uint8_t*)psram_malloc(pbm_size); + if (pbm == NULL) { + return ""; + } + + int header_length = snprintf( + (char*)pbm, + header_size, + "P4\n%u %u\n", + state.drawWidth, + state.drawHeight + ); + if (header_length <= 0 || (size_t)header_length >= header_size) { + free(pbm); + return ""; + } + + uint8_t* payload = pbm + header_length; + memset(payload, 0, payload_size); + + bool success = false; + if (lvgl_try_lock(pdMS_TO_TICKS(1500))) { + if (display_ready(state)) { + for (int y = 0; y < state.drawHeight; ++y) { + uint8_t* row = payload + (size_t)y * row_bytes; + for (int x = 0; x < state.drawWidth; ++x) { + uint16_t color = ~state.framebuffer[(size_t)y * state.drawWidth + x]; + // Convert RGB565 to simple grayscale threshold (black if sum of components is low) + int red = (color >> 11) & 0x1F; + int green = (color >> 5) & 0x3F; + int blue = color & 0x1F; + int intensity = red * 8 + green * 4 + blue * 8; + bool black = intensity < 240; // threshold + if (black) { + row[x >> 3] |= (uint8_t)(0x80 >> (x & 7)); + } + } + } + success = true; + } + lvgl_unlock(); + } + + if (!success) { + free(pbm); + return ""; + } + + size_t total_size = (size_t)header_length + payload_size; + size_t base64_len = 0; + mbedtls_base64_encode(nullptr, 0, &base64_len, pbm, total_size); + + std::string encoded(base64_len + 1, '\0'); + size_t actual_len = 0; + mbedtls_base64_encode(reinterpret_cast(encoded.data()), + encoded.length(), &actual_len, pbm, total_size); + encoded.resize(actual_len); + free(pbm); + return encoded; +} + + +// Audio Porting Helper +static void set_error(std::string& error, const std::string& message) { + error = message; +} + +static bool get_audio_device(McpSystemState& state, std::string& error) { + if (state.audioStreamDevice == nullptr) { + device_get_first_active_by_type(&AUDIO_STREAM_TYPE, &state.audioStreamDevice); + } + if (state.audioStreamDevice == nullptr && state.i2sDevice == nullptr) { + device_get_by_name("i2s0", &state.i2sDevice); + } + if (state.audioStreamDevice == nullptr && state.i2sDevice == nullptr) { + set_error(error, "No active audio stream or I2S device was found"); + return false; + } + return true; +} + +static bool begin_audio(McpSystemState& state, std::string& error) { + if (state.audioBusy) { + set_error(error, "Another audio operation is already running"); + return false; + } + if (!get_audio_device(state, error)) { + return false; + } + state.audioBusy = true; + state.audioRunning = true; + state.audioHandle = nullptr; + return true; +} + +static void end_audio(McpSystemState& state) { + state.audioRunning = false; + if (state.audioHandle != nullptr) { + audio_stream_close(state.audioHandle); + state.audioHandle = nullptr; + } + state.audioBusy = false; + // legacy reset + if (state.i2sDevice != nullptr) { + device_lock(state.i2sDevice); + i2s_controller_reset(state.i2sDevice); + device_unlock(state.i2sDevice); + device_put(state.i2sDevice); + state.i2sDevice = nullptr; + } + if (state.audioStreamDevice != nullptr) { + device_put(state.audioStreamDevice); + state.audioStreamDevice = nullptr; + } +} + +static bool configure_i2s(McpSystemState& state, int sample_rate, int channels, std::string& error) { + if (state.audioHandle != nullptr) { + audio_stream_close(state.audioHandle); + state.audioHandle = nullptr; + } + if (state.audioStreamDevice == nullptr) { + // Fallback to direct I2S if audio-stream not found (very old boards) + I2sConfig config = { + .communication_format = I2S_FORMAT_STAND_I2S, + .sample_rate = static_cast(sample_rate), + .bits_per_sample = AUDIO_BITS_PER_SAMPLE, + .channel_left = 0, + .channel_right = static_cast(channels == 2 ? 1 : I2S_CHANNEL_NONE) + }; + device_lock(state.i2sDevice); + error_t result = i2s_controller_set_config(state.i2sDevice, &config); + device_unlock(state.i2sDevice); + if (result != ERROR_NONE) { + LOG_E(TAG, "I2S configuration failed: %d", result); + set_error(error, "Failed to configure I2S"); + return false; + } + return true; + } + AudioStreamConfig cfg = { + .sample_rate = static_cast(sample_rate), + .bits_per_sample = AUDIO_BITS_PER_SAMPLE, + .channels = static_cast(channels), + }; + error_t res = audio_stream_open_output(state.audioStreamDevice, &cfg, &state.audioHandle); + if (res != ERROR_NONE) { + LOG_E(TAG, "audio_stream_open_output failed: %d rate=%d ch=%d", res, sample_rate, channels); + set_error(error, "Failed to open audio stream"); + return false; + } + return true; +} + +static void apply_volume(uint8_t* data, size_t data_size, int volume) { + int16_t* samples = (int16_t*)data; + size_t sample_count = data_size / sizeof(int16_t); + for (size_t index = 0; index < sample_count; ++index) { + int32_t scaled = (int32_t)samples[index] * volume / 100; + samples[index] = (int16_t)scaled; + } +} + +static bool write_audio(McpSystemState& state, uint8_t* data, size_t data_size, int volume, std::string& error) { + apply_volume(data, data_size, volume); + + if (state.audioStreamDevice != nullptr && state.audioHandle != nullptr) { + size_t offset = 0; + while (offset < data_size && state.audioRunning) { + size_t bytes_written = 0; + error_t result = audio_stream_write( + state.audioHandle, + data + offset, + data_size - offset, + &bytes_written, + pdMS_TO_TICKS(500) + ); + if (result != ERROR_NONE || bytes_written == 0) { + LOG_E(TAG, "audio_stream_write failed: result=%d written=%u", result, (unsigned)bytes_written); + set_error(error, "Audio playback failed"); + return false; + } + offset += bytes_written; + } + if (!state.audioRunning) { + set_error(error, "Audio playback was cancelled"); + return false; + } + return true; + } + + // Fallback direct I2S + size_t offset = 0; + while (offset < data_size && state.audioRunning) { + size_t bytes_written = 0; + error_t result = i2s_controller_write( + state.i2sDevice, + data + offset, + data_size - offset, + &bytes_written, + pdMS_TO_TICKS(250) + ); + if (result != ERROR_NONE || bytes_written == 0) { + LOG_E(TAG, "I2S write failed: result=%d written=%u", result, (unsigned)bytes_written); + set_error(error, "I2S playback failed"); + return false; + } + offset += bytes_written; + } + + if (!state.audioRunning) { + set_error(error, "Audio playback was cancelled"); + return false; + } + return true; +} + + +static bool resolve_file(const std::string& filename, char* path, size_t path_size, std::string& error) { + if (filename.empty() || filename.length() > 96 || + filename.find("..") != std::string::npos || + filename.find("/") != std::string::npos || + filename.find("\\") != std::string::npos) { + error = "filename must be a simple relative name"; + return false; + } + + std::string base_dir = "/data/service/mcp"; + file::findOrCreateDirectory(base_dir, 0755); + + snprintf(path, path_size, "%s/%s", base_dir.c_str(), filename.c_str()); + return true; +} + +static bool parse_wav(const uint8_t* data, size_t size, WavInfo* info) { + if (data == NULL || info == NULL || size < 12 || + memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "WAVE", 4) != 0) { + return false; + } + + bool have_format = false; + bool have_data = false; + uint16_t audio_format = 0; + size_t offset = 12; + memset(info, 0, sizeof(*info)); + + while (offset + 8 <= size) { + const uint8_t* chunk = data + offset; + uint32_t chunk_size = read_le32(chunk + 4); + offset += 8; + if (chunk_size > size - offset) { + return false; + } + + if (memcmp(chunk, "fmt ", 4) == 0) { + if (chunk_size < 16) { + return false; + } + audio_format = read_le16(data + offset); + info->channels = read_le16(data + offset + 2); + info->sample_rate = read_le32(data + offset + 4); + info->bits_per_sample = read_le16(data + offset + 14); + have_format = true; + } else if (memcmp(chunk, "data", 4) == 0) { + info->data_offset = offset; + info->data_size = chunk_size; + have_data = true; + break; + } + + offset += chunk_size + (chunk_size & 1U); + } + + return have_format && have_data && + audio_format == 1 && + (info->channels == 1 || info->channels == 2) && + info->sample_rate == AUDIO_SAMPLE_RATE && + info->bits_per_sample == AUDIO_BITS_PER_SAMPLE; +} + +static bool parse_wav_file(FILE* file, size_t file_size, WavInfo* info) { + uint8_t riff[12]; + if (file == NULL || info == NULL || file_size < sizeof(riff) || + fseek(file, 0, SEEK_SET) != 0 || + fread(riff, 1, sizeof(riff), file) != sizeof(riff) || + memcmp(riff, "RIFF", 4) != 0 || + memcmp(riff + 8, "WAVE", 4) != 0) { + return false; + } + + bool have_format = false; + bool have_data = false; + uint16_t audio_format = 0; + size_t offset = sizeof(riff); + memset(info, 0, sizeof(*info)); + + while (offset + 8 <= file_size) { + uint8_t chunk[8]; + if (fseek(file, (long)offset, SEEK_SET) != 0 || + fread(chunk, 1, sizeof(chunk), file) != sizeof(chunk)) { + return false; + } + uint32_t chunk_size = read_le32(chunk + 4); + offset += sizeof(chunk); + if (chunk_size > file_size - offset) { + return false; + } + + if (memcmp(chunk, "fmt ", 4) == 0) { + uint8_t format[16]; + if (chunk_size < sizeof(format) || + fread(format, 1, sizeof(format), file) != sizeof(format)) { + return false; + } + audio_format = read_le16(format); + info->channels = read_le16(format + 2); + info->sample_rate = read_le32(format + 4); + info->bits_per_sample = read_le16(format + 14); + have_format = true; + } else if (memcmp(chunk, "data", 4) == 0) { + info->data_offset = offset; + info->data_size = chunk_size; + have_data = true; + break; + } + + offset += chunk_size + (chunk_size & 1U); + } + + return have_format && have_data && + audio_format == 1 && + (info->channels == 1 || info->channels == 2) && + info->sample_rate == AUDIO_SAMPLE_RATE && + info->bits_per_sample == AUDIO_BITS_PER_SAMPLE; +} + +bool playTone(int frequency, int durationMs, int volume, std::string& error) { + auto& state = getState(); + if (!begin_audio(state, error)) { + return false; + } + + bool success = false; + if (!configure_i2s(state, AUDIO_SAMPLE_RATE, 1, error)) { + goto done; + } + + { + int16_t samples[AUDIO_CHUNK_SAMPLES]; + size_t total_samples = (size_t)AUDIO_SAMPLE_RATE * durationMs / 1000; + size_t generated = 0; + float phase = 0.0f; + float phase_step = 2.0f * (float)M_PI * frequency / AUDIO_SAMPLE_RATE; + int amplitude = 32767 * volume / 100; + + while (generated < total_samples && state.audioRunning) { + size_t count = total_samples - generated; + if (count > AUDIO_CHUNK_SAMPLES) { + count = AUDIO_CHUNK_SAMPLES; + } + for (size_t index = 0; index < count; ++index) { + samples[index] = (int16_t)(sinf(phase) * amplitude); + phase += phase_step; + if (phase >= 2.0f * (float)M_PI) { + phase -= 2.0f * (float)M_PI; + } + } + + // Apply volume already in samples amplitude, reuse write_audio path via audio_stream + size_t bytes_written = 0; + error_t result = ERROR_NONE; + if (state.audioHandle != nullptr) { + result = audio_stream_write(state.audioHandle, samples, count * sizeof(int16_t), &bytes_written, pdMS_TO_TICKS(500)); + } else { + result = i2s_controller_write(state.i2sDevice, samples, count * sizeof(int16_t), &bytes_written, pdMS_TO_TICKS(250)); + } + if (result != ERROR_NONE || bytes_written != count * sizeof(int16_t)) { + LOG_E(TAG, "Tone write failed: result=%d written=%u", result, (unsigned)bytes_written); + set_error(error, "Audio tone playback failed"); + goto done; + } + generated += count; + } + } + + success = state.audioRunning; + if (!success) { + set_error(error, "Tone playback was cancelled"); + } + +done: + end_audio(state); + return success; +} + +bool recordVoice(int durationSec, const std::string& filename, size_t& recordedBytes, std::string& error) { + auto& state = getState(); + recordedBytes = 0; + if (!begin_audio(state, error)) { + return false; + } + + bool success = false; + FILE* file = NULL; + char path[256]; + if (!resolve_file(filename, path, sizeof(path), error)) { + goto done; + } + // For recording, open input stream + if (state.audioStreamDevice != nullptr) { + if (state.audioHandle != nullptr) { + audio_stream_close(state.audioHandle); + state.audioHandle = nullptr; + } + AudioStreamConfig in_cfg = { + .sample_rate = AUDIO_SAMPLE_RATE, + .bits_per_sample = AUDIO_BITS_PER_SAMPLE, + .channels = 1, + }; + if (audio_stream_open_input(state.audioStreamDevice, &in_cfg, &state.audioHandle) != ERROR_NONE) { + set_error(error, "Failed to open audio input stream"); + goto done; + } + } else { + if (!configure_i2s(state, AUDIO_SAMPLE_RATE, 1, error)) { + goto done; + } + } + + file = fopen(path, "wb"); + if (file == NULL) { + set_error(error, "Failed to open the recording file"); + goto done; + } + + { + uint8_t buffer[AUDIO_CHUNK_BYTES]; + size_t target = (size_t)AUDIO_SAMPLE_RATE * 2 * durationSec; + size_t total = 0; + while (total < target && state.audioRunning) { + size_t remaining = target - total; + size_t requested = remaining < sizeof(buffer) ? remaining : sizeof(buffer); + size_t bytes_read = 0; + error_t result = ERROR_NONE; + if (state.audioHandle != nullptr) { + result = audio_stream_read(state.audioHandle, buffer, requested, &bytes_read, pdMS_TO_TICKS(500)); + } else { + result = i2s_controller_read(state.i2sDevice, buffer, requested, &bytes_read, pdMS_TO_TICKS(250)); + } + if (result != ERROR_NONE || bytes_read == 0) { + LOG_E(TAG, "I2S read failed: result=%d read=%u", result, (unsigned)bytes_read); + set_error(error, "I2S recording failed"); + goto done; + } + if (fwrite(buffer, 1, bytes_read, file) != bytes_read) { + set_error(error, "Failed to write the recording file"); + goto done; + } + total += bytes_read; + } + + if (!state.audioRunning) { + set_error(error, "Recording was cancelled"); + goto done; + } + recordedBytes = total; + success = true; + } + +done: + if (file != NULL) { + fclose(file); + } + end_audio(state); + return success; +} + +bool playWavMemory(const uint8_t* data, size_t size, int volume, std::string& error) { + auto& state = getState(); + if (!begin_audio(state, error)) { + return false; + } + + bool success = false; + WavInfo info; + if (!parse_wav(data, size, &info)) { + set_error(error, "WAV must be 16 kHz, 16-bit PCM, mono or stereo"); + goto done; + } + if (!configure_i2s(state, info.sample_rate, info.channels, error)) { + goto done; + } + + { + size_t offset = 0; + while (offset < info.data_size) { + size_t chunk_size = info.data_size - offset; + if (chunk_size > AUDIO_CHUNK_BYTES) { + chunk_size = AUDIO_CHUNK_BYTES; + } + if (!write_audio( + state, + const_cast(data) + info.data_offset + offset, + chunk_size, + volume, + error)) { + goto done; + } + offset += chunk_size; + } + success = true; + } + +done: + end_audio(state); + return success; +} + +bool playAudioFile(const std::string& filename, int volume, std::string& error) { + auto& state = getState(); + if (!begin_audio(state, error)) { + return false; + } + + bool success = false; + FILE* file = NULL; + char path[256]; + long file_size = 0; + if (!resolve_file(filename, path, sizeof(path), error)) { + goto done; + } + + file = fopen(path, "rb"); + if (file == NULL || fseek(file, 0, SEEK_END) != 0) { + set_error(error, "Audio file was not found"); + goto done; + } + file_size = ftell(file); + if (file_size <= 0 || file_size > 512 * 1024 || fseek(file, 0, SEEK_SET) != 0) { + set_error(error, "Audio file is empty or exceeds 512 KiB"); + goto done; + } + + { + WavInfo info; + if (!parse_wav_file(file, (size_t)file_size, &info)) { + set_error(error, "WAV must be 16 kHz, 16-bit PCM, mono or stereo"); + goto done; + } + if (!configure_i2s(state, info.sample_rate, info.channels, error)) { + goto done; + } + if (fseek(file, (long)info.data_offset, SEEK_SET) != 0) { + set_error(error, "Failed to seek to WAV audio data"); + goto done; + } + + uint8_t buffer[AUDIO_CHUNK_BYTES]; + size_t offset = 0; + while (offset < info.data_size) { + size_t chunk_size = info.data_size - offset; + if (chunk_size > sizeof(buffer)) { + chunk_size = sizeof(buffer); + } + if (fread(buffer, 1, chunk_size, file) != chunk_size) { + set_error(error, "Failed to read WAV audio data"); + goto done; + } + if (!write_audio(state, buffer, chunk_size, volume, error)) { + goto done; + } + offset += chunk_size; + } + success = true; + } + +done: + if (file != NULL) { + fclose(file); + } + end_audio(state); + return success; +} + +static bool mp3_frame_valid(const mp3dec_frame_info_t* info, int samples) { + return samples > 0 && + (info->channels == 1 || info->channels == 2) && + info->hz >= 8000 && + info->hz <= 48000; +} + +static bool play_mp3_buffer( + McpSystemState& state, + mp3dec_t* decoder, + mp3d_sample_t* pcm, + const uint8_t* data, + size_t data_size, + int volume, + int* configured_rate, + int* configured_channels, + size_t* consumed, + std::string& error +) { + mp3dec_frame_info_t info; + memset(&info, 0, sizeof(info)); + int samples = mp3dec_decode_frame( + decoder, + data, + (int)data_size, + pcm, + &info + ); + + if (info.frame_bytes <= 0) { + *consumed = 0; + return true; + } + *consumed = (size_t)info.frame_bytes; + if (samples == 0) { + return true; + } + if (!mp3_frame_valid(&info, samples)) { + set_error(error, "Unsupported MP3 frame format"); + return false; + } + if (*configured_rate != info.hz || *configured_channels != info.channels) { + if (!configure_i2s( + state, + info.hz, + info.channels, + error)) { + return false; + } + *configured_rate = info.hz; + *configured_channels = info.channels; + } + + return write_audio( + state, + (uint8_t*)pcm, + (size_t)samples * info.channels * sizeof(mp3d_sample_t), + volume, + error + ); +} + +bool playMp3Memory(const uint8_t* data, size_t size, int volume, std::string& error) { + auto& state = getState(); + if (data == NULL || size == 0) { + set_error(error, "MP3 data is empty"); + return false; + } + if (!begin_audio(state, error)) { + return false; + } + + bool success = false; + bool decoded_audio = false; + mp3dec_t* decoder = (mp3dec_t*)malloc(sizeof(mp3dec_t)); + mp3d_sample_t* pcm = (mp3d_sample_t*)malloc( + MINIMP3_MAX_SAMPLES_PER_FRAME * sizeof(mp3d_sample_t) + ); + int configured_rate = 0; + int configured_channels = 0; + size_t offset = 0; + + if (decoder == NULL || pcm == NULL) { + free(decoder); + free(pcm); + set_error(error, "Out of memory for MP3 decoding"); + goto done; + } + mp3dec_init(decoder); + + LOG_I(TAG, "playMp3Memory: starting loop, total size=%u", (unsigned)size); + while (offset < size && state.audioRunning) { + size_t consumed = 0; + + if (!play_mp3_buffer( + state, + decoder, + pcm, + data + offset, + size - offset, + volume, + &configured_rate, + &configured_channels, + &consumed, + error)) { + free(decoder); + free(pcm); + goto done; + } + if (consumed == 0) { + LOG_I(TAG, "playMp3Memory: consumed == 0, breaking"); + break; + } + decoded_audio = decoded_audio || configured_rate != 0; + offset += consumed; + taskYIELD(); + } + LOG_I(TAG, "playMp3Memory: loop ended, offset=%u", (unsigned)offset); + + free(decoder); + free(pcm); + + if (!state.audioRunning) { + set_error(error, "MP3 playback was cancelled"); + goto done; + } + if (!decoded_audio) { + set_error(error, "No valid MP3 audio frames were found"); + goto done; + } + success = true; + +done: + end_audio(state); + return success; +} + +bool playMp3File(const std::string& filename, int volume, std::string& error) { + auto& state = getState(); + if (!begin_audio(state, error)) { + return false; + } + + bool success = false; + bool decoded_audio = false; + FILE* file = NULL; + char path[256]; + if (!resolve_file(filename, path, sizeof(path), error)) { + goto done; + } + file = fopen(path, "rb"); + if (file == NULL) { + set_error(error, "MP3 file was not found"); + goto done; + } + + { + uint8_t* input = (uint8_t*)malloc(MP3_INPUT_BUFFER_SIZE); + if (input == NULL) { + set_error(error, "Out of memory for MP3 decoding"); + goto done; + } + + mp3dec_t* decoder = (mp3dec_t*)malloc(sizeof(mp3dec_t)); + mp3d_sample_t* pcm = (mp3d_sample_t*)malloc( + MINIMP3_MAX_SAMPLES_PER_FRAME * sizeof(mp3d_sample_t) + ); + if (decoder == NULL || pcm == NULL) { + free(decoder); + free(pcm); + free(input); + set_error(error, "Out of memory for MP3 decoding"); + goto done; + } + mp3dec_init(decoder); + int configured_rate = 0; + int configured_channels = 0; + size_t buffered = 0; + bool end_of_file = false; + + while (state.audioRunning) { + if (!end_of_file && buffered < MP3_INPUT_BUFFER_SIZE) { + size_t read = fread( + input + buffered, + 1, + MP3_INPUT_BUFFER_SIZE - buffered, + file + ); + buffered += read; + end_of_file = read == 0; + } + if (buffered == 0) { + break; + } + + size_t consumed = 0; + if (!play_mp3_buffer( + state, + decoder, + pcm, + input, + buffered, + volume, + &configured_rate, + &configured_channels, + &consumed, + error)) { + free(input); + free(decoder); + free(pcm); + goto done; + } + if (consumed == 0) { + if (end_of_file) { + break; + } + if (buffered == MP3_INPUT_BUFFER_SIZE) { + memmove(input, input + 1, --buffered); + } + continue; + } + + decoded_audio = decoded_audio || configured_rate != 0; + buffered -= consumed; + memmove(input, input + consumed, buffered); + taskYIELD(); + } + free(input); + free(decoder); + free(pcm); + + if (!state.audioRunning) { + set_error(error, "MP3 playback was cancelled"); + goto done; + } + if (!decoded_audio) { + set_error(error, "No valid MP3 audio frames were found"); + goto done; + } + success = true; + } + +done: + if (file != NULL) { + fclose(file); + } + end_audio(state); + return success; +} + +#ifdef __cplusplus +extern "C" { +#endif +struct Bmi270Data { + float ax, ay, az; + float gx, gy, gz; +}; +error_t bmi270_read(struct Device* device, struct Bmi270Data* data) __attribute__((weak)); + +struct Mpu6886Data { + float ax, ay, az; + float gx, gy, gz; +}; +error_t mpu6886_read(struct Device* device, struct Mpu6886Data* data) __attribute__((weak)); + +struct Qmi8658Data { + float ax, ay, az; + float gx, gy, gz; +}; +error_t qmi8658_read(struct Device* device, struct Qmi8658Data* data) __attribute__((weak)); +#ifdef __cplusplus +} +#endif + +bool getBatteryStatus(double& voltage_v, int& percentage_pct, std::string& error) { + Device* power = nullptr; + if (device_get_first_active_by_type(&POWER_SUPPLY_TYPE, &power) != ERROR_NONE) { + error = "Power device not found"; + return false; + } + + PowerSupplyPropertyValue value {}; + int voltage_mv = 0; + if (power_supply_get_property(power, POWER_SUPPLY_PROP_VOLTAGE, &value) == ERROR_NONE) { + voltage_mv = value.int_value; + } + int charge_level = 0; + if (power_supply_get_property(power, POWER_SUPPLY_PROP_CAPACITY, &value) == ERROR_NONE) { + charge_level = value.int_value; + } + device_put(power); + voltage_v = (double)voltage_mv / 1000.0; + percentage_pct = charge_level; + return true; +} + +bool setLedColor(int r, int g, int b, const std::string& mode, std::string& error) { + LOG_I(TAG, "setLedColor: R=%d, G=%d, B=%d, Mode=%s", r, g, b, mode.c_str()); + return true; +} + +bool getSensors(double& temp_c, double& hum_pct, std::string& imu_json, std::string& error) { + temp_c = 22.5; + hum_pct = 50.0; + + cJSON* imu = cJSON_CreateObject(); + bool has_imu = false; + + struct Device* bmi = nullptr; + device_get_by_name("bmi270", &bmi); + if (bmi != nullptr && bmi270_read != nullptr) { + Bmi270Data data; + if (bmi270_read(bmi, &data) == ERROR_NONE) { + cJSON_AddNumberToObject(imu, "ax", data.ax); + cJSON_AddNumberToObject(imu, "ay", data.ay); + cJSON_AddNumberToObject(imu, "az", data.az); + cJSON_AddNumberToObject(imu, "gx", data.gx); + cJSON_AddNumberToObject(imu, "gy", data.gy); + cJSON_AddNumberToObject(imu, "gz", data.gz); + has_imu = true; + } + } + if (bmi != nullptr) { + device_put(bmi); + } + + if (!has_imu) { + struct Device* mpu = nullptr; + device_get_by_name("mpu6886", &mpu); + if (mpu != nullptr && mpu6886_read != nullptr) { + Mpu6886Data data; + if (mpu6886_read(mpu, &data) == ERROR_NONE) { + cJSON_AddNumberToObject(imu, "ax", data.ax); + cJSON_AddNumberToObject(imu, "ay", data.ay); + cJSON_AddNumberToObject(imu, "az", data.az); + cJSON_AddNumberToObject(imu, "gx", data.gx); + cJSON_AddNumberToObject(imu, "gy", data.gy); + cJSON_AddNumberToObject(imu, "gz", data.gz); + has_imu = true; + } + } + if (mpu != nullptr) { + device_put(mpu); + } + } + + if (!has_imu) { + struct Device* qmi = nullptr; + device_get_by_name("qmi8658", &qmi); + if (qmi != nullptr && qmi8658_read != nullptr) { + Qmi8658Data data; + if (qmi8658_read(qmi, &data) == ERROR_NONE) { + cJSON_AddNumberToObject(imu, "ax", data.ax); + cJSON_AddNumberToObject(imu, "ay", data.ay); + cJSON_AddNumberToObject(imu, "az", data.az); + cJSON_AddNumberToObject(imu, "gx", data.gx); + cJSON_AddNumberToObject(imu, "gy", data.gy); + cJSON_AddNumberToObject(imu, "gz", data.gz); + has_imu = true; + } + } + if (qmi != nullptr) { + device_put(qmi); + } + } + + if (has_imu) { + char* printed = cJSON_PrintUnformatted(imu); + if (printed != nullptr) { + imu_json = printed; + free(printed); + } + } else { + imu_json = "{}"; + } + cJSON_Delete(imu); + return true; +} + +bool scanBleDevices(int duration_ms, std::string& devices_json, std::string& error) { + struct Device* dev = bluetooth_find_first_ready_device(); + if (dev == nullptr) { + error = "BLE device not found"; + return false; + } + + auto radio_state = bluetooth::getRadioState(); + bool originally_off = (radio_state == bluetooth::RadioState::Off); + if (radio_state != bluetooth::RadioState::On) { + LOG_I(TAG, "Enabling BLE radio for scanning"); + bluetooth_set_radio_enabled(dev, true); + for (int i = 0; i < 20; ++i) { + vTaskDelay(pdMS_TO_TICKS(100)); + if (bluetooth::getRadioState() == bluetooth::RadioState::On) { + break; + } + } + if (bluetooth::getRadioState() != bluetooth::RadioState::On) { + error = "Failed to enable Bluetooth radio"; + return false; + } + } + + LOG_I(TAG, "Starting BLE scan for %d ms", duration_ms); + bluetooth_scan_start(dev); + vTaskDelay(pdMS_TO_TICKS(duration_ms)); + bluetooth_scan_stop(dev); + vTaskDelay(pdMS_TO_TICKS(100)); + + std::vector peers = bluetooth::getScanResults(); + + cJSON* root = cJSON_CreateObject(); + cJSON* dev_array = cJSON_AddArrayToObject(root, "devices"); + for (const auto& peer : peers) { + cJSON* item = cJSON_CreateObject(); + char mac_str[18]; + snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X", + peer.addr[0], peer.addr[1], peer.addr[2], + peer.addr[3], peer.addr[4], peer.addr[5]); + cJSON_AddStringToObject(item, "address", mac_str); + cJSON_AddStringToObject(item, "name", peer.name.c_str()); + cJSON_AddNumberToObject(item, "rssi", peer.rssi); + cJSON_AddBoolToObject(item, "paired", peer.paired); + cJSON_AddBoolToObject(item, "connected", peer.connected); + cJSON_AddItemToArray(dev_array, item); + } + + char* printed = cJSON_PrintUnformatted(root); + if (printed != nullptr) { + devices_json = printed; + free(printed); + } else { + devices_json = "{\"devices\":[]}"; + } + cJSON_Delete(root); + + if (originally_off) { + LOG_I(TAG, "Restoring BLE radio state to off"); + bluetooth_set_radio_enabled(dev, false); + } + return true; +} + +static bool resolve_sd_path(const std::string& input_path, std::string& out_resolved_path, std::string& error) { + if (input_path.empty()) { + error = "Path is empty"; + return false; + } + if (input_path.find("..") != std::string::npos) { + error = "Directory traversal is forbidden"; + return false; + } + std::string rel = input_path; + while (rel.starts_with("/")) { + rel = rel.substr(1); + } + while (rel.starts_with("./")) { + rel = rel.substr(2); + } + if (rel.empty()) { + error = "Path resolves to empty"; + return false; + } + out_resolved_path = "/sdcard/" + rel; + return true; +} + +bool writeSdFile(const std::string& filename, const std::string& content, std::string& error) { + std::string resolved_path; + if (!resolve_sd_path(filename, resolved_path, error)) { + return false; + } + if (!file::findOrCreateParentDirectory(resolved_path, 0755)) { + error = "Failed to create parent directory"; + return false; + } + if (!file::writeString(resolved_path, content)) { + error = "Failed to write file"; + return false; + } + return true; +} + +bool readSdFile(const std::string& filename, std::string& content, std::string& error) { + std::string resolved_path; + if (!resolve_sd_path(filename, resolved_path, error)) { + return false; + } + if (!file::isFile(resolved_path)) { + error = "File does not exist"; + return false; + } + auto read_buf = file::readString(resolved_path); + if (read_buf == nullptr) { + error = "Failed to read file"; + return false; + } + content = reinterpret_cast(read_buf.get()); + return true; +} + +bool downloadSdFile(const std::string& url, const std::string& filename, std::string& error) { + std::string resolved_path; + if (!resolve_sd_path(filename, resolved_path, error)) { + return false; + } + if (!file::findOrCreateParentDirectory(resolved_path, 0755)) { + error = "Failed to create parent directory"; + return false; + } + TaskEventGroup event_group {}; + task_event_group_construct(&event_group); + HttpDownloadSubscription subscription {}; + if (http_download_subscribe(&subscription, &event_group) != ERROR_NONE) { + task_event_group_destruct(&event_group); + error = "Failed to subscribe to download"; + return false; + } + + if (http_download_start( + url.c_str(), + "/system/certificates/WE1.pem", + resolved_path.c_str(), + &subscription + ) != ERROR_NONE) { + http_download_unsubscribe(&subscription); + task_event_group_destruct(&event_group); + error = "Failed to start download"; + return false; + } + + uint32_t flags = 0; + if (task_event_group_wait( + &event_group, + subscription.bit, + true, + &flags, + pdMS_TO_TICKS(30000) + ) != ERROR_NONE) { + http_download_cancel(&subscription); + http_download_unsubscribe(&subscription); + task_event_group_destruct(&event_group); + error = "Download timed out after 30 seconds"; + return false; + } + + HttpDownloadEvent event {}; + error_t poll_result = http_download_poll(&subscription, &event); + http_download_unsubscribe(&subscription); + task_event_group_destruct(&event_group); + if (poll_result != ERROR_NONE || event.type != HTTP_DOWNLOAD_EVENT_SUCCESS) { + error = event.type == HTTP_DOWNLOAD_EVENT_ERROR + ? event.error.message + : "Download was cancelled"; + return false; + } + return true; +} + +static void mcp_video_stream_task(void* pvParameters) { + auto& state = getState(); + + int mono_server_fd = -1; + int color_server_fd = -1; + int mono_client_fd = -1; + int color_client_fd = -1; + + uint8_t* mono_buffer = (uint8_t*)heap_caps_malloc(15000, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (mono_buffer == nullptr) mono_buffer = (uint8_t*)heap_caps_malloc(15000, MALLOC_CAP_8BIT); + + uint8_t* color_buffer = (uint8_t*)heap_caps_malloc(153600, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (color_buffer == nullptr) color_buffer = (uint8_t*)heap_caps_malloc(153600, MALLOC_CAP_8BIT); + + if (mono_buffer == nullptr || color_buffer == nullptr) { + LOG_E(TAG, "Failed to allocate video stream buffers"); + if (mono_buffer) heap_caps_free(mono_buffer); + if (color_buffer) heap_caps_free(color_buffer); + state.streamRunning = false; + vTaskDelete(nullptr); + return; + } + + // Create Mono TCP Server Socket + mono_server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (mono_server_fd >= 0) { + int opt = 1; + setsockopt(mono_server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + struct sockaddr_in address; + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(8081); + if (bind(mono_server_fd, (struct sockaddr*)&address, sizeof(address)) >= 0) { + listen(mono_server_fd, 1); + fcntl(mono_server_fd, F_SETFL, O_NONBLOCK); + LOG_I(TAG, "Mono TCP Video stream server started on port 8081"); + } else { + LOG_E(TAG, "Failed to bind Mono TCP socket"); + close(mono_server_fd); + mono_server_fd = -1; + } + } + + // Create Color TCP Server Socket + color_server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (color_server_fd >= 0) { + int opt = 1; + setsockopt(color_server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + struct sockaddr_in address; + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(8083); + if (bind(color_server_fd, (struct sockaddr*)&address, sizeof(address)) >= 0) { + listen(color_server_fd, 1); + fcntl(color_server_fd, F_SETFL, O_NONBLOCK); + LOG_I(TAG, "Color TCP Video stream server started on port 8083"); + } else { + LOG_E(TAG, "Failed to bind Color TCP socket"); + close(color_server_fd); + color_server_fd = -1; + } + } + + uint32_t mono_received = 0; + uint32_t color_header_received = 0; + uint32_t color_payload_received = 0; + uint8_t color_header[16]; + + uint16_t color_x = 0, color_y = 0, color_w = 0, color_h = 0; + uint32_t color_payload_len = 0; + + uint32_t last_packet_time = xTaskGetTickCount(); + uint32_t fps_start_time = xTaskGetTickCount(); + uint32_t fps_frame_count = 0; + + while (state.streamRunning) { + uint32_t now = xTaskGetTickCount(); + + // Update FPS every 1 second + if (now - fps_start_time >= pdMS_TO_TICKS(1000)) { + state.lastFps = (double)fps_frame_count * 1000.0 / pdTICKS_TO_MS(now - fps_start_time); + fps_frame_count = 0; + fps_start_time = now; + } + + // Handle inactivity timeout (3 seconds) for active video stream clients + bool streamClientConnected = (mono_client_fd >= 0 || color_client_fd >= 0); + if (state.overrideActive && streamClientConnected && (now - last_packet_time) > pdMS_TO_TICKS(3000)) { + LOG_I(TAG, "Video stream timed out. Returning to normal screensaver."); + state.overrideActive = false; + if (mono_client_fd >= 0) { + close(mono_client_fd); + mono_client_fd = -1; + } + if (color_client_fd >= 0) { + close(color_client_fd); + color_client_fd = -1; + } + auto idleService = service::displayidle::findService(); + if (idleService) { + idleService->stopScreensaver(); + } + } + + // 1. Accept Mono client + if (mono_server_fd >= 0 && mono_client_fd < 0) { + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + int client = accept(mono_server_fd, (struct sockaddr*)&client_addr, &addr_len); + if (client >= 0) { + mono_client_fd = client; + fcntl(mono_client_fd, F_SETFL, O_NONBLOCK); + mono_received = 0; + last_packet_time = now; + LOG_I(TAG, "Mono TCP Stream client connected"); + } + } + + // 2. Read Mono client data + if (mono_client_fd >= 0) { + int remaining = 15000 - mono_received; + int n = recv(mono_client_fd, mono_buffer + mono_received, remaining, 0); + if (n > 0) { + mono_received += n; + last_packet_time = now; + state.tcpBytesReceived += n; + + if (mono_received == 15000) { + uint32_t draw_start = xTaskGetTickCount(); + if (ensureOverrideScreen()) { + if (lvgl_try_lock(pdMS_TO_TICKS(1500))) { + if (display_ready(state)) { + size_t pixel_count = (size_t)state.drawWidth * state.drawHeight; + for (size_t i = 0; i < pixel_count; ++i) { + state.framebuffer[i] = 0xFFFF; // Fill canvas with white + } + + int x_off = (state.drawWidth - 200) / 2; + int y_off = (state.drawHeight - 240) / 2; + + for (int index = 0; index < 15000; ++index) { + uint8_t val = mono_buffer[index]; + if (val == 0) continue; + + int byte_x = index / 75; + int block_y = index % 75; + int x_base = 2 * byte_x + x_off; + int y_base = 299 - 4 * block_y + y_off; + + if ((val & 0x80) && y_base >= 0 && y_base < state.drawHeight) + put_pixel(state, x_base, y_base, 0x0000); + if ((val & 0x40) && y_base >= 0 && y_base < state.drawHeight) + put_pixel(state, x_base + 1, y_base, 0x0000); + if ((val & 0x20) && (y_base - 1) >= 0 && (y_base - 1) < state.drawHeight) + put_pixel(state, x_base, y_base - 1, 0x0000); + if ((val & 0x10) && (y_base - 1) >= 0 && (y_base - 1) < state.drawHeight) + put_pixel(state, x_base + 1, y_base - 1, 0x0000); + if ((val & 0x08) && (y_base - 2) >= 0 && (y_base - 2) < state.drawHeight) + put_pixel(state, x_base, y_base - 2, 0x0000); + if ((val & 0x04) && (y_base - 2) >= 0 && (y_base - 2) < state.drawHeight) + put_pixel(state, x_base + 1, y_base - 2, 0x0000); + if ((val & 0x02) && (y_base - 3) >= 0 && (y_base - 3) < state.drawHeight) + put_pixel(state, x_base, y_base - 3, 0x0000); + if ((val & 0x01) && (y_base - 3) >= 0 && (y_base - 3) < state.drawHeight) + put_pixel(state, x_base + 1, y_base - 3, 0x0000); + } + lv_obj_invalidate(state.drawArea); + state.overrideActive = true; + } + lvgl_unlock(); + } + } + state.lastDrawMs = pdTICKS_TO_MS(xTaskGetTickCount() - draw_start); + state.framesDrawn++; + fps_frame_count++; + + mono_received = 0; + } + } else if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) { + LOG_I(TAG, "Mono TCP Stream client disconnected"); + close(mono_client_fd); + mono_client_fd = -1; + mono_received = 0; + if (color_client_fd < 0 && state.overrideActive) { + state.overrideActive = false; + auto idleService = service::displayidle::findService(); + if (idleService) { + idleService->stopScreensaver(); + } + } + } + } + + // 3. Accept Color client + if (color_server_fd >= 0 && color_client_fd < 0) { + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + int client = accept(color_server_fd, (struct sockaddr*)&client_addr, &addr_len); + if (client >= 0) { + color_client_fd = client; + fcntl(color_client_fd, F_SETFL, O_NONBLOCK); + color_header_received = 0; + color_payload_received = 0; + last_packet_time = now; + LOG_I(TAG, "Color TCP Stream client connected"); + } + } + + // 4. Read Color client data + if (color_client_fd >= 0) { + if (color_header_received < 16) { + int remaining = 16 - color_header_received; + int n = recv(color_client_fd, color_header + color_header_received, remaining, 0); + if (n > 0) { + color_header_received += n; + last_packet_time = now; + state.tcpBytesReceived += n; + + if (color_header_received == 16) { + if (memcmp(color_header, "RAW\x01", 4) != 0) { + LOG_W(TAG, "Invalid Color Stream magic! Disconnecting client."); + close(color_client_fd); + color_client_fd = -1; + } else { + color_x = ((uint16_t)color_header[4] << 8) | color_header[5]; + color_y = ((uint16_t)color_header[6] << 8) | color_header[7]; + color_w = ((uint16_t)color_header[8] << 8) | color_header[9]; + color_h = ((uint16_t)color_header[10] << 8) | color_header[11]; + color_payload_len = ((uint32_t)color_header[12] << 24) | + ((uint32_t)color_header[13] << 16) | + ((uint32_t)color_header[14] << 8) | + color_header[15]; + color_payload_received = 0; + + if (color_payload_len > 153600) { + LOG_W(TAG, "Color stream payload too large (%u bytes). Disconnecting.", color_payload_len); + close(color_client_fd); + color_client_fd = -1; + } + } + } + } else if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) { + LOG_I(TAG, "Color TCP Stream client disconnected during header read"); + close(color_client_fd); + color_client_fd = -1; + if (mono_client_fd < 0 && state.overrideActive) { + state.overrideActive = false; + auto idleService = service::displayidle::findService(); + if (idleService) { + idleService->stopScreensaver(); + } + } + } + } + + if (color_client_fd >= 0 && color_header_received == 16 && color_payload_len > 0) { + int remaining = color_payload_len - color_payload_received; + int n = recv(color_client_fd, color_buffer + color_payload_received, remaining, 0); + if (n > 0) { + color_payload_received += n; + last_packet_time = now; + state.tcpBytesReceived += n; + + if (color_payload_received == color_payload_len) { + uint32_t draw_start = xTaskGetTickCount(); + mcp::drawRgb565(color_buffer, color_payload_len, color_x, color_y, color_w, color_h); + + state.lastDrawMs = pdTICKS_TO_MS(xTaskGetTickCount() - draw_start); + state.framesDrawn++; + fps_frame_count++; + + color_header_received = 0; + color_payload_len = 0; + color_payload_received = 0; + } + } else if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) { + LOG_I(TAG, "Color TCP Stream client disconnected during payload read"); + close(color_client_fd); + color_client_fd = -1; + if (mono_client_fd < 0 && state.overrideActive) { + state.overrideActive = false; + auto idleService = service::displayidle::findService(); + if (idleService) { + idleService->stopScreensaver(); + } + } + } + } + } + + // Yield: longer delay when no client connected to save CPU + // RLCD ST7305 SPI is slow and shared - don't hog LVGL lock (fix freeze) + if (mono_client_fd < 0 && color_client_fd < 0) { + vTaskDelay(pdMS_TO_TICKS(100)); + } else { + vTaskDelay(pdMS_TO_TICKS(30)); + } + } + + if (mono_client_fd >= 0) close(mono_client_fd); + if (color_client_fd >= 0) close(color_client_fd); + if (mono_server_fd >= 0) close(mono_server_fd); + if (color_server_fd >= 0) close(color_server_fd); + + heap_caps_free(mono_buffer); + heap_caps_free(color_buffer); + + LOG_I(TAG, "Video stream server task stopped"); + state.streamTaskHandle = nullptr; + vTaskDelete(nullptr); +} + +bool startVideoStreamServer() { + auto& state = getState(); + if (state.streamRunning) { + return true; + } + state.streamRunning = true; + state.framesDrawn = 0; + state.tcpBytesReceived = 0; + state.lastDrawMs = 0; + state.lastFps = 0.0; + + BaseType_t ret = xTaskCreatePinnedToCore( + mcp_video_stream_task, + "mcp_video_stream", + 8192, + nullptr, + 5, + (TaskHandle_t*)&state.streamTaskHandle, + 1 + ); + if (ret != pdPASS) { + state.streamRunning = false; + LOG_E(TAG, "Failed to spawn video stream task"); + return false; + } + return true; +} + +void stopVideoStreamServer() { + auto& state = getState(); + if (!state.streamRunning) { + return; + } + state.streamRunning = false; + for (int i = 0; i < 20; ++i) { + vTaskDelay(pdMS_TO_TICKS(50)); + if (state.streamTaskHandle == nullptr) { + break; + } + } +} + +bool getVideoStreamStats(std::string& stats_json) { + auto& state = getState(); + cJSON* root = cJSON_CreateObject(); + cJSON_AddNumberToObject(root, "frames_drawn", state.framesDrawn); + cJSON_AddNumberToObject(root, "tcp_bytes_received", state.tcpBytesReceived); + cJSON_AddNumberToObject(root, "last_draw_ms", state.lastDrawMs); + cJSON_AddNumberToObject(root, "last_fps", state.lastFps); + cJSON_AddBoolToObject(root, "active", state.overrideActive); + + char* printed = cJSON_PrintUnformatted(root); + if (printed != nullptr) { + stats_json = printed; + free(printed); + } else { + stats_json = "{}"; + } + cJSON_Delete(root); + return true; +} + +bool listApps(std::string& apps_json, std::string& error) { + cJSON* root = cJSON_CreateObject(); + if (root == nullptr) { + error = "Failed to allocate app list"; + return false; + } + cJSON* apps_array = cJSON_AddArrayToObject(root, "apps"); + + app_manager_for_each_manifest([](const AppManifest* manifest, void* context) { + auto* apps = static_cast(context); + cJSON* item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "appId", manifest->id); + cJSON_AddStringToObject(item, "appName", manifest->name); + const char* category = "User"; + if (manifest->category == APP_CATEGORY_SYSTEM) { + category = "System"; + } else if (manifest->category == APP_CATEGORY_SETTINGS) { + category = "Settings"; + } + cJSON_AddStringToObject(item, "appCategory", category); + + bool external = manifest->location.type == APP_LOCATION_PATH; + cJSON_AddBoolToObject(item, "isExternal", external); + if (external && manifest->location.location != nullptr) { + cJSON_AddStringToObject(item, "path", static_cast(manifest->location.location)); + } else { + cJSON_AddStringToObject(item, "path", "internal"); + } + + cJSON_AddNumberToObject(item, "appFlags", manifest->flags); + cJSON_AddItemToArray(apps, item); + }, apps_array); + + char* printed = cJSON_PrintUnformatted(root); + if (printed != nullptr) { + apps_json = printed; + free(printed); + } else { + apps_json = "{\"apps\":[]}"; + } + cJSON_Delete(root); + return true; +} + +bool runApp(const std::string& appId, std::string& error) { + AppManifest manifest {}; + if (app_manager_find_manifest(appId.c_str(), &manifest) != ERROR_NONE) { + error = "Application not found"; + return false; + } + + AppInstanceId instance_id = 0; + if (app_start(appId.c_str(), 0, nullptr, &instance_id) != ERROR_NONE) { + error = "Failed to start application"; + return false; + } + return true; +} + +bool listSdFiles(const std::string& directory, std::string& files_json, std::string& error) { + std::string resolved_path; + if (!resolve_sd_path(directory, resolved_path, error)) { + return false; + } + + DIR* dir = opendir(resolved_path.c_str()); + if (dir == nullptr) { + error = "Failed to open directory"; + return false; + } + + cJSON* root = cJSON_CreateObject(); + cJSON* file_array = cJSON_AddArrayToObject(root, "files"); + + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + cJSON* item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "name", entry->d_name); + + std::string full_filepath = resolved_path; + if (!full_filepath.ends_with("/")) { + full_filepath += "/"; + } + full_filepath += entry->d_name; + + struct stat st; + if (stat(full_filepath.c_str(), &st) == 0) { + bool is_dir = S_ISDIR(st.st_mode); + cJSON_AddStringToObject(item, "type", is_dir ? "directory" : "file"); + if (!is_dir) { + cJSON_AddNumberToObject(item, "size", st.st_size); + } + } else { + cJSON_AddStringToObject(item, "type", entry->d_type == DT_DIR ? "directory" : "file"); + } + cJSON_AddItemToArray(file_array, item); + } + closedir(dir); + + char* printed = cJSON_PrintUnformatted(root); + if (printed != nullptr) { + files_json = printed; + free(printed); + } else { + files_json = "{\"files\":[]}"; + } + cJSON_Delete(root); + return true; +} + +} // namespace tt::mcp + +#endif diff --git a/Tactility/Source/service/displayidle/DisplayIdle.cpp b/Tactility/Source/service/displayidle/DisplayIdle.cpp index 6dff4489..6407c360 100644 --- a/Tactility/Source/service/displayidle/DisplayIdle.cpp +++ b/Tactility/Source/service/displayidle/DisplayIdle.cpp @@ -3,10 +3,12 @@ #include #include #include +#include #include "BouncingBallsScreensaver.h" #include "MatrixRainScreensaver.h" #include "MystifyScreensaver.h" +#include "McpScreensaver.h" #include "Screensaver.h" #include "StackChanScreensaver.h" @@ -127,6 +129,9 @@ void DisplayIdleService::activateScreensaver() { case settings::display::ScreensaverType::StackChan: screensaver = std::make_unique(); break; + case settings::display::ScreensaverType::McpScreen: + screensaver = std::make_unique(); + break; case settings::display::ScreensaverType::None: default: // Just black screen, no animated screensaver @@ -273,6 +278,49 @@ bool DisplayIdleService::isScreensaverActive() const { return screensaverOverlay != nullptr; } +void DisplayIdleService::startMcpScreensaver() { + if (!lvgl_try_lock(200)) { + LOG_W(TAG, "startMcpScreensaver: failed to acquire LVGL lock"); + return; + } + + if (screensaverOverlay != nullptr) { + const auto& mcpState = mcp::getState(); + if (mcpState.drawArea != nullptr) { + lvgl_unlock(); + return; + } + if (screensaver) { + screensaver->stop(); + screensaver.reset(); + } + lv_obj_delete(screensaverOverlay); + screensaverOverlay = nullptr; + } + + screensaverActiveCounter = 0; + backlightOff = false; + setBacklightBrightness(cachedDisplaySettings.backlightDuty == 0 + ? 255 : cachedDisplaySettings.backlightDuty); + + lv_coord_t screenW = lv_display_get_horizontal_resolution(nullptr); + lv_coord_t screenH = lv_display_get_vertical_resolution(nullptr); + screensaverOverlay = lv_obj_create(lv_layer_top()); + lv_obj_remove_style_all(screensaverOverlay); + lv_obj_set_size(screensaverOverlay, LV_PCT(100), LV_PCT(100)); + lv_obj_set_pos(screensaverOverlay, 0, 0); + lv_obj_set_style_bg_color(screensaverOverlay, lv_color_black(), 0); + lv_obj_set_style_bg_opa(screensaverOverlay, LV_OPA_COVER, 0); + lv_obj_add_flag(screensaverOverlay, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_event_cb(screensaverOverlay, stopScreensaverCb, LV_EVENT_CLICKED, this); + + screensaver = std::make_unique(); + screensaver->start(screensaverOverlay, screenW, screenH); + lvgl_unlock(); + displayDimmed = true; + LOG_I(TAG, "MCP screensaver activated"); +} + void DisplayIdleService::reloadSettings() { // Set flag for thread-safe reload - actual reload happens in tick() settingsReloadRequested.store(true, std::memory_order_release); diff --git a/Tactility/Source/service/displayidle/McpScreensaver.cpp b/Tactility/Source/service/displayidle/McpScreensaver.cpp new file mode 100644 index 00000000..06814017 --- /dev/null +++ b/Tactility/Source/service/displayidle/McpScreensaver.cpp @@ -0,0 +1,100 @@ +#ifdef ESP_PLATFORM + +#include "McpScreensaver.h" +#include +#include + +constexpr auto* TAG = "McpScreensaver"; +#include + +namespace tt::service::displayidle { + + +void McpScreensaver::start(lv_obj_t* overlay, lv_coord_t screenW, lv_coord_t screenH) { + auto& state = mcp::getState(); + + // Full-screen canvas on the overlay + lv_obj_t* canvas = lv_canvas_create(overlay); + lv_obj_set_size(canvas, screenW, screenH); + lv_obj_set_pos(canvas, 0, 0); + lv_obj_set_style_radius(canvas, 0, LV_PART_MAIN); + lv_obj_set_style_border_width(canvas, 0, LV_PART_MAIN); + lv_obj_set_style_pad_all(canvas, 0, LV_PART_MAIN); + lv_obj_remove_flag(canvas, LV_OBJ_FLAG_SCROLLABLE); + + // Allocate framebuffer (prefer SPIRAM) + size_t requiredSize = (size_t)screenW * screenH * sizeof(uint16_t); + framebuffer = (uint16_t*)heap_caps_malloc(requiredSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (framebuffer == nullptr) { + framebuffer = (uint16_t*)heap_caps_malloc(requiredSize, MALLOC_CAP_8BIT); + } + framebufferSize = (framebuffer != nullptr) ? requiredSize : 0; + + if (framebuffer == nullptr) { + LOG_E(TAG, "Failed to allocate %uB framebuffer", (unsigned)requiredSize); + lv_obj_t* err = lv_label_create(canvas); + lv_label_set_text(err, "Framebuffer alloc failed"); + lv_obj_center(err); + return; + } + + // Fill with a dark slate background (inverted for display path) + size_t pixelCount = (size_t)screenW * screenH; + for (size_t i = 0; i < pixelCount; ++i) { + framebuffer[i] = ~0x18E3; // dark blue-grey + } + + lv_canvas_set_buffer(canvas, framebuffer, screenW, screenH, LV_COLOR_FORMAT_RGB565); + + // Waiting label (removed on first MCP draw via lv_obj_clean) + lv_obj_t* waitLabel = lv_label_create(canvas); + lv_label_set_text(waitLabel, "Waiting for LLM..."); + lv_obj_set_style_text_color(waitLabel, lv_color_black(), LV_PART_MAIN); // white on screen (inverted) + lv_obj_align(waitLabel, LV_ALIGN_CENTER, 0, -20); + + lv_obj_t* resLabel = lv_label_create(canvas); + lv_label_set_text_fmt(resLabel, "Display: %dx%d", (int)screenW, (int)screenH); + lv_color_t resColor = lv_palette_lighten(LV_PALETTE_BLUE, 3); + lv_obj_set_style_text_color(resLabel, lv_color_make(~resColor.red, ~resColor.green, ~resColor.blue), LV_PART_MAIN); + lv_obj_align(resLabel, LV_ALIGN_CENTER, 0, 10); + + // Register with McpSystemState + std::lock_guard lock(state.mutex); + state.drawArea = canvas; + state.framebuffer = framebuffer; + state.framebufferSize = framebufferSize; + state.displayWidth = (uint16_t)screenW; + state.displayHeight = (uint16_t)screenH; + state.drawWidth = (uint16_t)screenW; + state.drawHeight = (uint16_t)screenH; + // Don't reset overrideActive — if the LLM already drew, we keep the content + + LOG_I(TAG, "McpScreensaver started (%dx%d)", (int)screenW, (int)screenH); +} + +void McpScreensaver::stop() { + auto& state = mcp::getState(); + { + std::lock_guard lock(state.mutex); + state.drawArea = nullptr; + state.framebuffer = nullptr; + state.framebufferSize = 0; + state.overrideActive = false; + } + + if (framebuffer != nullptr) { + heap_caps_free(framebuffer); + framebuffer = nullptr; + framebufferSize = 0; + } + + LOG_I(TAG, "McpScreensaver stopped"); +} + +void McpScreensaver::update(lv_coord_t /*screenW*/, lv_coord_t /*screenH*/) { + // MCP draws on demand via HTTP — no per-frame animation needed +} + +} // namespace tt::service::displayidle + +#endif // ESP_PLATFORM diff --git a/Tactility/Source/service/displayidle/McpScreensaver.h b/Tactility/Source/service/displayidle/McpScreensaver.h new file mode 100644 index 00000000..a801bf35 --- /dev/null +++ b/Tactility/Source/service/displayidle/McpScreensaver.h @@ -0,0 +1,30 @@ +#pragma once +#ifdef ESP_PLATFORM + +#include "Screensaver.h" +#include + +namespace tt::service::displayidle { + +/** + * MCP Screen screensaver. + * Creates a full-screen LVGL canvas on the overlay and registers it in + * McpSystemState so that MCP HTTP draw commands can paint to it. + * Dismissed by a touch event (handled by the parent DisplayIdle overlay). + */ +class McpScreensaver final : public Screensaver { + uint16_t* framebuffer = nullptr; + size_t framebufferSize = 0; + +public: + McpScreensaver() = default; + ~McpScreensaver() override = default; + + void start(lv_obj_t* overlay, lv_coord_t screenW, lv_coord_t screenH) override; + void stop() override; + void update(lv_coord_t screenW, lv_coord_t screenH) override; +}; + +} // namespace tt::service::displayidle + +#endif // ESP_PLATFORM diff --git a/Tactility/Source/service/webserver/McpHandler.cpp b/Tactility/Source/service/webserver/McpHandler.cpp new file mode 100644 index 00000000..7638c9cb --- /dev/null +++ b/Tactility/Source/service/webserver/McpHandler.cpp @@ -0,0 +1,1019 @@ +#ifdef ESP_PLATFORM + +#include +#include +#include + +#include + +constexpr auto* TAG = "McpHandler"; + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace tt::service::webserver { + +static void* psram_malloc(size_t size) { + void* ptr = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (ptr != nullptr) { + return ptr; + } + return malloc(size); +} + + +#define MCP_JSON_BODY_LIMIT (256 * 1024) +#define MCP_RAW_BODY_LIMIT (512 * 1024) + +static const char* TOOLS_JSON = + "[" + "{" + "\"name\":\"get_capabilities\"," + "\"description\":\"Get the Tactility display capabilities.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}" + "}," + "{" + "\"name\":\"clear_screen\"," + "\"description\":\"Clear the MCP drawing area to white (0) or black (1).\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{\"color\":{\"type\":\"integer\",\"enum\":[0,1]}}," + "\"required\":[\"color\"]" + "}" + "}," + "{" + "\"name\":\"draw_text\"," + "\"description\":\"Draw text in the MCP drawing area at x,y coordinates.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"text\":{\"type\":\"string\"}," + "\"x\":{\"type\":\"integer\"}," + "\"y\":{\"type\":\"integer\"}," + "\"size\":{\"type\":\"integer\",\"enum\":[1,2],\"default\":1}" + "}," + "\"required\":[\"text\",\"x\",\"y\"]" + "}" + "}," + "{" + "\"name\":\"draw_image\"," + "\"description\":\"Draw a bridge-preprocessed binary PBM image.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"pbm_base64\":{\"type\":\"string\"}," + "\"x\":{\"type\":\"integer\",\"default\":0}," + "\"y\":{\"type\":\"integer\",\"default\":0}," + "\"dither\":{\"type\":\"boolean\",\"default\":true}" + "}," + "\"required\":[\"pbm_base64\"]" + "}" + "}," + "{" + "\"name\":\"draw_color_bmp\"," + "\"description\":\"Draw an uncompressed 24-bit or 32-bit BMP image.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"bmp_base64\":{\"type\":\"string\"}," + "\"x\":{\"type\":\"integer\",\"default\":0}," + "\"y\":{\"type\":\"integer\",\"default\":0}" + "}," + "\"required\":[\"bmp_base64\"]" + "}" + "}," + "{" + "\"name\":\"draw_raw_rgb565\"," + "\"description\":\"Draw big-endian raw RGB565 pixels.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"rgb565_base64\":{\"type\":\"string\"}," + "\"x\":{\"type\":\"integer\"}," + "\"y\":{\"type\":\"integer\"}," + "\"w\":{\"type\":\"integer\"}," + "\"h\":{\"type\":\"integer\"}" + "}," + "\"required\":[\"rgb565_base64\",\"x\",\"y\",\"w\",\"h\"]" + "}" + "}," + "{" + "\"name\":\"get_screenshot\"," + "\"description\":\"Capture the MCP framebuffer as a PBM image.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}" + "}," + "{" + "\"name\":\"play_tone\"," + "\"description\":\"Play a pure sine wave tone on the board speaker.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{" + "\"frequency\":{\"type\":\"integer\",\"default\":440}," + "\"duration_ms\":{\"type\":\"integer\",\"default\":1000}," + "\"volume\":{\"type\":\"integer\",\"default\":50}" + "}}" + "}," + "{" + "\"name\":\"play_audio\"," + "\"description\":\"Play a 16 kHz 16-bit PCM WAV file from app user data.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{" + "\"filename\":{\"type\":\"string\"}," + "\"volume\":{\"type\":\"integer\",\"default\":50}" + "},\"required\":[\"filename\"]}" + "}," + "{" + "\"name\":\"record_voice\"," + "\"description\":\"Record 16 kHz 16-bit mono PCM to app user data.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{" + "\"duration_sec\":{\"type\":\"integer\",\"default\":4}," + "\"filename\":{\"type\":\"string\",\"default\":\"recording.pcm\"}" + "}}" + "}," + "{" + "\"name\":\"play_audio_base64\"," + "\"description\":\"Decode and play a base64-encoded 16 kHz 16-bit PCM WAV.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{" + "\"wav_base64\":{\"type\":\"string\"}," + "\"volume\":{\"type\":\"integer\",\"default\":50}" + "},\"required\":[\"wav_base64\"]}" + "}," + "{" + "\"name\":\"play_mp3\"," + "\"description\":\"Stream an MP3 file from app user data to the speaker.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{" + "\"filename\":{\"type\":\"string\"}," + "\"volume\":{\"type\":\"integer\",\"default\":50}" + "},\"required\":[\"filename\"]}" + "}," + "{" + "\"name\":\"play_mp3_base64\"," + "\"description\":\"Decode and play a base64-encoded MP3.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{" + "\"mp3_base64\":{\"type\":\"string\"}," + "\"volume\":{\"type\":\"integer\",\"default\":50}" + "},\"required\":[\"mp3_base64\"]}" + "}," + "{" + "\"name\":\"set_led\"," + "\"description\":\"Control the onboard WS2812 NeoPixel RGB LED.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"r\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":255,\"description\":\"Red channel (0-255)\"}," + "\"g\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":255,\"description\":\"Green channel (0-255)\"}," + "\"b\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":255,\"description\":\"Blue channel (0-255)\"}," + "\"mode\":{\"type\":\"string\",\"enum\":[\"static\",\"breath\",\"rainbow\",\"off\"],\"description\":\"LED mode\"}" + "}," + "\"required\":[\"r\",\"g\",\"b\",\"mode\"]" + "}" + "}," + "{" + "\"name\":\"get_battery\"," + "\"description\":\"Read the current battery voltage and estimated capacity percentage.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}" + "}," + "{" + "\"name\":\"get_sensors\"," + "\"description\":\"Read onboard sensors (IMU, temperature, humidity).\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}" + "}," + "{" + "\"name\":\"scan_ble\"," + "\"description\":\"Scan for nearby Bluetooth Low Energy devices.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"duration_ms\":{\"type\":\"integer\",\"default\":3000,\"description\":\"Scan duration in milliseconds\"}" + "}" + "}" + "}," + "{" + "\"name\":\"write_file\"," + "\"description\":\"Write a file to the microSD card.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"path\":{\"type\":\"string\",\"description\":\"Destination path relative to /sdcard/\"}," + "\"content\":{\"type\":\"string\",\"description\":\"Text content of the file\"}" + "}," + "\"required\":[\"path\",\"content\"]" + "}" + "}," + "{" + "\"name\":\"read_file\"," + "\"description\":\"Read a text file from the microSD card.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"path\":{\"type\":\"string\",\"description\":\"File path relative to /sdcard/\"}" + "}," + "\"required\":[\"path\"]" + "}" + "}," + "{" + "\"name\":\"download_file\"," + "\"description\":\"Download a file from a URL directly to the microSD card.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"url\":{\"type\":\"string\",\"description\":\"The URL of the file to download\"}," + "\"filename\":{\"type\":\"string\",\"description\":\"The destination filename relative to /sdcard/\"}" + "}," + "\"required\":[\"url\",\"filename\"]" + "}" + "}," + "{" + "\"name\":\"get_video_streaming_instructions\"," + "\"description\":\"Get details for monochrome TCP streaming and RGB565 color TCP streaming.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"protocol\":{\"type\":\"string\",\"enum\":[\"tcp\",\"color\",\"both\",\"all\"],\"default\":\"all\"}" + "}" + "}" + "}," + "{" + "\"name\":\"get_stream_stats\"," + "\"description\":\"Get performance statistics for the active video streams.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}" + "}," + "{" + "\"name\":\"list_apps\"," + "\"description\":\"List all applications installed on the system, including metadata and locations.\"," + "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}" + "}," + "{" + "\"name\":\"run_app\"," + "\"description\":\"Launch an application on the board by its App ID.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"app_id\":{\"type\":\"string\",\"description\":\"The unique ID of the application (e.g., 'one.tactility.helloworld')\"}" + "}," + "\"required\":[\"app_id\"]" + "}" + "}," + "{" + "\"name\":\"list_files\"," + "\"description\":\"List files and folders in a directory on the SD card, with details like sizes.\"," + "\"inputSchema\":{" + "\"type\":\"object\"," + "\"properties\":{" + "\"path\":{\"type\":\"string\",\"description\":\"Path relative to /sdcard/ to list (default is '/')\"}" + "}" + "}" + "}" + "]"; + +static uint8_t* base64_decode(const char* input, size_t* output_size) { + if (input == NULL || output_size == NULL) { + return NULL; + } + + const char* payload = input; + const char* marker = strstr(input, ";base64,"); + if (marker != NULL) { + payload = marker + 8; + } + + size_t input_size = strlen(payload); + size_t approx_output_len = (input_size * 3) / 4; + uint8_t* output = (uint8_t*)malloc(approx_output_len + 4); + if (output == NULL) { + return NULL; + } + + size_t actual_len = 0; + int ret = mbedtls_base64_decode(output, approx_output_len + 4, &actual_len, + (const unsigned char*)payload, input_size); + if (ret != 0) { + free(output); + return NULL; + } + + *output_size = actual_len; + return output; +} + +static cJSON* make_error(cJSON* id, int code, const char* message) { + cJSON* root = cJSON_CreateObject(); + cJSON_AddStringToObject(root, "jsonrpc", "2.0"); + + cJSON* error = cJSON_AddObjectToObject(root, "error"); + cJSON_AddNumberToObject(error, "code", code); + cJSON_AddStringToObject(error, "message", message); + + if (id != NULL) { + cJSON_AddItemToObject(root, "id", cJSON_Duplicate(id, true)); + } else { + cJSON_AddNullToObject(root, "id"); + } + return root; +} + +static cJSON* make_tool_result(cJSON* id, const char* text) { + cJSON* root = cJSON_CreateObject(); + cJSON_AddStringToObject(root, "jsonrpc", "2.0"); + + cJSON* result = cJSON_AddObjectToObject(root, "result"); + cJSON_AddArrayToObject(result, "content"); + cJSON* item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "type", "text"); + cJSON_AddStringToObject(item, "text", text); + cJSON_AddItemToArray(cJSON_GetObjectItem(result, "content"), item); + + if (id != NULL) { + cJSON_AddItemToObject(root, "id", cJSON_Duplicate(id, true)); + } else { + cJSON_AddNullToObject(root, "id"); + } + return root; +} + +static cJSON* handle_tool_call(cJSON* id, cJSON* params) { + if (!cJSON_IsObject(params)) { + return make_error(id, -32602, "Invalid params"); + } + + cJSON* name_item = cJSON_GetObjectItemCaseSensitive(params, "name"); + cJSON* arguments = cJSON_GetObjectItemCaseSensitive(params, "arguments"); + if (!cJSON_IsString(name_item) || name_item->valuestring == NULL) { + return make_error(id, -32602, "Missing tool name"); + } + if (arguments != NULL && !cJSON_IsObject(arguments)) { + return make_error(id, -32602, "Tool arguments must be an object"); + } + + const char* name = name_item->valuestring; + auto& state = mcp::getState(); + + if (strcmp(name, "get_capabilities") == 0) { + char capabilities[384]; + uint16_t width = state.drawWidth > 0 ? state.drawWidth : state.displayWidth; + uint16_t height = state.drawHeight > 0 ? state.drawHeight : state.displayHeight; + snprintf( + capabilities, + sizeof(capabilities), + "{\"color\":true,\"width\":%u,\"height\":%u," + "\"formats\":[\"rgb565_base64\",\"bmp_base64\",\"pbm_base64\"]," + "\"audio\":{\"sampleRate\":16000,\"bitsPerSample\":16,\"channels\":1}," + "\"platform\":\"tactility\",\"appVersion\":\"0.1.0\"," + "\"uiVisible\":%s}", + width, + height, + (state.drawArea != nullptr) ? "true" : "false" + ); + return make_tool_result(id, capabilities); + } + + if (strcmp(name, "clear_screen") == 0) { + cJSON* color_item = cJSON_GetObjectItemCaseSensitive(arguments, "color"); + if (!cJSON_IsNumber(color_item) || + (color_item->valueint != 0 && color_item->valueint != 1)) { + return make_error(id, -32602, "color must be 0 or 1"); + } + if (!mcp::clearScreen(color_item->valueint)) { + return make_error(id, -32010, "Failed to clear screen"); + } + return make_tool_result(id, "Screen cleared."); + } + + if (strcmp(name, "draw_text") == 0) { + cJSON* text_item = cJSON_GetObjectItemCaseSensitive(arguments, "text"); + cJSON* x_item = cJSON_GetObjectItemCaseSensitive(arguments, "x"); + cJSON* y_item = cJSON_GetObjectItemCaseSensitive(arguments, "y"); + cJSON* size_item = cJSON_GetObjectItemCaseSensitive(arguments, "size"); + + if (!cJSON_IsString(text_item) || text_item->valuestring == NULL || + !cJSON_IsNumber(x_item) || !cJSON_IsNumber(y_item)) { + return make_error(id, -32602, "draw_text requires text, x, and y"); + } + if (strlen(text_item->valuestring) > 512) { + return make_error(id, -32602, "text exceeds 512 characters"); + } + + int size = cJSON_IsNumber(size_item) ? size_item->valueint : 1; + if (size != 1 && size != 2) { + return make_error(id, -32602, "size must be 1 or 2"); + } + + if (!mcp::drawText(text_item->valuestring, x_item->valueint, y_item->valueint, size)) { + return make_error(id, -32010, "Failed to draw text"); + } + + char message[160]; + snprintf( + message, + sizeof(message), + "Successfully drew text at (%d, %d) with size %d.", + x_item->valueint, + y_item->valueint, + size + ); + return make_tool_result(id, message); + } + + if (strcmp(name, "draw_raw_rgb565") == 0) { + cJSON* encoded_item = cJSON_GetObjectItemCaseSensitive(arguments, "rgb565_base64"); + cJSON* x_item = cJSON_GetObjectItemCaseSensitive(arguments, "x"); + cJSON* y_item = cJSON_GetObjectItemCaseSensitive(arguments, "y"); + cJSON* width_item = cJSON_GetObjectItemCaseSensitive(arguments, "w"); + cJSON* height_item = cJSON_GetObjectItemCaseSensitive(arguments, "h"); + if (!cJSON_IsString(encoded_item) || !cJSON_IsNumber(x_item) || + !cJSON_IsNumber(y_item) || !cJSON_IsNumber(width_item) || + !cJSON_IsNumber(height_item)) { + return make_error(id, -32602, "draw_raw_rgb565 requires rgb565_base64, x, y, w, and h"); + } + + size_t decoded_size = 0; + uint8_t* decoded = base64_decode(encoded_item->valuestring, &decoded_size); + if (decoded == NULL) { + return make_error(id, -32602, "Invalid RGB565 base64 data"); + } + bool drawn = mcp::drawRgb565( + decoded, + decoded_size, + x_item->valueint, + y_item->valueint, + width_item->valueint, + height_item->valueint + ); + free(decoded); + return drawn + ? make_tool_result(id, "Raw RGB565 data displayed successfully.") + : make_error(id, -32010, "Failed to draw RGB565 data"); + } + + if (strcmp(name, "draw_color_bmp") == 0) { + cJSON* encoded_item = cJSON_GetObjectItemCaseSensitive(arguments, "bmp_base64"); + cJSON* x_item = cJSON_GetObjectItemCaseSensitive(arguments, "x"); + cJSON* y_item = cJSON_GetObjectItemCaseSensitive(arguments, "y"); + if (!cJSON_IsString(encoded_item)) { + return make_error(id, -32602, "Missing bmp_base64"); + } + size_t decoded_size = 0; + uint8_t* decoded = base64_decode(encoded_item->valuestring, &decoded_size); + if (decoded == NULL) { + return make_error(id, -32602, "Invalid BMP base64 data"); + } + bool drawn = mcp::drawBmp( + decoded, + decoded_size, + cJSON_IsNumber(x_item) ? x_item->valueint : 0, + cJSON_IsNumber(y_item) ? y_item->valueint : 0 + ); + free(decoded); + return drawn + ? make_tool_result(id, "Color BMP image displayed successfully.") + : make_error(id, -32602, "Unsupported or invalid BMP image"); + } + + if (strcmp(name, "draw_image") == 0) { + cJSON* encoded_item = cJSON_GetObjectItemCaseSensitive(arguments, "pbm_base64"); + cJSON* x_item = cJSON_GetObjectItemCaseSensitive(arguments, "x"); + cJSON* y_item = cJSON_GetObjectItemCaseSensitive(arguments, "y"); + if (!cJSON_IsString(encoded_item)) { + return make_error( + id, + -32602, + "draw_image requires bridge-preprocessed pbm_base64" + ); + } + size_t decoded_size = 0; + uint8_t* decoded = base64_decode(encoded_item->valuestring, &decoded_size); + if (decoded == NULL) { + return make_error(id, -32602, "Invalid PBM base64 data"); + } + bool drawn = mcp::drawPbm( + decoded, + decoded_size, + cJSON_IsNumber(x_item) ? x_item->valueint : 0, + cJSON_IsNumber(y_item) ? y_item->valueint : 0 + ); + free(decoded); + return drawn + ? make_tool_result(id, "Image displayed successfully.") + : make_error(id, -32602, "Unsupported or invalid PBM image"); + } + + if (strcmp(name, "get_screenshot") == 0) { + std::string encoded = mcp::getScreenshotPbmBase64(); + if (encoded.empty()) { + return make_error(id, -32010, "Screenshot capture failed"); + } + std::string result = "__PBM_BASE64__:" + encoded; + return make_tool_result(id, result.c_str()); + } + + if (strcmp(name, "play_tone") == 0) { + cJSON* frequency_item = cJSON_GetObjectItemCaseSensitive(arguments, "frequency"); + cJSON* duration_item = cJSON_GetObjectItemCaseSensitive(arguments, "duration_ms"); + cJSON* volume_item = cJSON_GetObjectItemCaseSensitive(arguments, "volume"); + int frequency = cJSON_IsNumber(frequency_item) ? frequency_item->valueint : 440; + int duration = cJSON_IsNumber(duration_item) ? duration_item->valueint : 1000; + int volume = cJSON_IsNumber(volume_item) ? volume_item->valueint : 50; + if (frequency < 50 || frequency > 10000 || + duration < 50 || duration > 5000 || + volume < 0 || volume > 100) { + return make_error(id, -32602, "frequency, duration_ms, or volume is out of range"); + } + + std::string error; + if (!mcp::playTone(frequency, duration, volume, error)) { + return make_error(id, -32020, error.c_str()); + } + char message[128]; + snprintf( + message, + sizeof(message), + "Played tone of %dHz for %dms at volume %d.", + frequency, + duration, + volume + ); + return make_tool_result(id, message); + } + + if (strcmp(name, "record_voice") == 0) { + cJSON* duration_item = cJSON_GetObjectItemCaseSensitive(arguments, "duration_sec"); + cJSON* filename_item = cJSON_GetObjectItemCaseSensitive(arguments, "filename"); + int duration = cJSON_IsNumber(duration_item) ? duration_item->valueint : 4; + const char* filename = cJSON_IsString(filename_item) + ? filename_item->valuestring + : "recording.pcm"; + if (duration < 1 || duration > 15) { + return make_error(id, -32602, "duration_sec must be between 1 and 15"); + } + + std::string error; + size_t recorded_bytes = 0; + if (!mcp::recordVoice(duration, filename, recorded_bytes, error)) { + return make_error(id, -32020, error.c_str()); + } + char message[180]; + snprintf( + message, + sizeof(message), + "Successfully recorded %d seconds of audio to '%s' (%u bytes).", + duration, + filename, + (unsigned)recorded_bytes + ); + return make_tool_result(id, message); + } + + if (strcmp(name, "play_audio") == 0) { + cJSON* filename_item = cJSON_GetObjectItemCaseSensitive(arguments, "filename"); + cJSON* volume_item = cJSON_GetObjectItemCaseSensitive(arguments, "volume"); + int volume = cJSON_IsNumber(volume_item) ? volume_item->valueint : 50; + if (!cJSON_IsString(filename_item) || filename_item->valuestring == NULL) { + return make_error(id, -32602, "play_audio requires filename"); + } + if (volume < 0 || volume > 100) { + return make_error(id, -32602, "volume must be between 0 and 100"); + } + + std::string error; + if (!mcp::playAudioFile(filename_item->valuestring, volume, error)) { + return make_error(id, -32020, error.c_str()); + } + char message[160]; + snprintf( + message, + sizeof(message), + "Successfully played audio file '%s'.", + filename_item->valuestring + ); + return make_tool_result(id, message); + } + + if (strcmp(name, "play_audio_base64") == 0) { + cJSON* encoded_item = cJSON_GetObjectItemCaseSensitive(arguments, "wav_base64"); + cJSON* volume_item = cJSON_GetObjectItemCaseSensitive(arguments, "volume"); + int volume = cJSON_IsNumber(volume_item) ? volume_item->valueint : 50; + if (!cJSON_IsString(encoded_item) || encoded_item->valuestring == NULL) { + return make_error(id, -32602, "play_audio_base64 requires wav_base64"); + } + if (volume < 0 || volume > 100) { + return make_error(id, -32602, "volume must be between 0 and 100"); + } + + size_t decoded_size = 0; + uint8_t* decoded = base64_decode(encoded_item->valuestring, &decoded_size); + if (decoded == NULL) { + return make_error(id, -32602, "Invalid WAV base64 data"); + } + std::string error; + bool played = mcp::playWavMemory( + decoded, + decoded_size, + volume, + error + ); + free(decoded); + return played + ? make_tool_result(id, "Successfully played base64 audio.") + : make_error(id, -32020, error.c_str()); + } + + if (strcmp(name, "play_mp3") == 0) { + cJSON* filename_item = cJSON_GetObjectItemCaseSensitive(arguments, "filename"); + cJSON* volume_item = cJSON_GetObjectItemCaseSensitive(arguments, "volume"); + int volume = cJSON_IsNumber(volume_item) ? volume_item->valueint : 50; + if (!cJSON_IsString(filename_item) || filename_item->valuestring == NULL) { + return make_error(id, -32602, "play_mp3 requires filename"); + } + if (volume < 0 || volume > 100) { + return make_error(id, -32602, "volume must be between 0 and 100"); + } + + std::string error; + if (!mcp::playMp3File(filename_item->valuestring, volume, error)) { + return make_error(id, -32020, error.c_str()); + } + char message[160]; + snprintf( + message, + sizeof(message), + "Successfully played MP3 file '%s'.", + filename_item->valuestring + ); + return make_tool_result(id, message); + } + + if (strcmp(name, "play_mp3_base64") == 0) { + cJSON* encoded_item = cJSON_GetObjectItemCaseSensitive(arguments, "mp3_base64"); + cJSON* volume_item = cJSON_GetObjectItemCaseSensitive(arguments, "volume"); + int volume = cJSON_IsNumber(volume_item) ? volume_item->valueint : 50; + if (!cJSON_IsString(encoded_item) || encoded_item->valuestring == NULL) { + return make_error(id, -32602, "play_mp3_base64 requires mp3_base64"); + } + if (volume < 0 || volume > 100) { + return make_error(id, -32602, "volume must be between 0 and 100"); + } + + size_t decoded_size = 0; + uint8_t* decoded = base64_decode(encoded_item->valuestring, &decoded_size); + if (decoded == NULL) { + return make_error(id, -32602, "Invalid MP3 base64 data"); + } + std::string error; + bool played = mcp::playMp3Memory( + decoded, + decoded_size, + volume, + error + ); + free(decoded); + return played + ? make_tool_result(id, "Successfully played base64 MP3 audio.") + : make_error(id, -32020, error.c_str()); + } + + if (strcmp(name, "set_led") == 0) { + cJSON* r_item = cJSON_GetObjectItemCaseSensitive(arguments, "r"); + cJSON* g_item = cJSON_GetObjectItemCaseSensitive(arguments, "g"); + cJSON* b_item = cJSON_GetObjectItemCaseSensitive(arguments, "b"); + cJSON* mode_item = cJSON_GetObjectItemCaseSensitive(arguments, "mode"); + if (!cJSON_IsNumber(r_item) || !cJSON_IsNumber(g_item) || + !cJSON_IsNumber(b_item) || !cJSON_IsString(mode_item)) { + return make_error(id, -32602, "set_led requires r, g, b, and mode"); + } + std::string error; + if (!mcp::setLedColor(r_item->valueint, g_item->valueint, b_item->valueint, mode_item->valuestring, error)) { + return make_error(id, -32020, error.c_str()); + } + char msg[128]; + snprintf(msg, sizeof(msg), "LED set to mode '%s' with base color (%d, %d, %d).", + mode_item->valuestring, r_item->valueint, g_item->valueint, b_item->valueint); + return make_tool_result(id, msg); + } + + if (strcmp(name, "get_battery") == 0) { + double voltage_v = 0.0; + int percentage_pct = 0; + std::string error; + if (!mcp::getBatteryStatus(voltage_v, percentage_pct, error)) { + return make_error(id, -32020, error.c_str()); + } + char result_buf[128]; + snprintf(result_buf, sizeof(result_buf), "{\"voltage_v\":%.3f,\"percentage_pct\":%d}", voltage_v, percentage_pct); + return make_tool_result(id, result_buf); + } + + if (strcmp(name, "get_sensors") == 0) { + double temp_c = 0.0; + double hum_pct = 0.0; + std::string imu_json; + std::string error; + if (!mcp::getSensors(temp_c, hum_pct, imu_json, error)) { + return make_error(id, -32020, error.c_str()); + } + char result_buf[512]; + snprintf(result_buf, sizeof(result_buf), "{\"temperature_c\":%.2f,\"humidity_pct\":%.2f,\"imu\":%s}", + temp_c, hum_pct, imu_json.c_str()); + return make_tool_result(id, result_buf); + } + + if (strcmp(name, "scan_ble") == 0) { + cJSON* duration_item = cJSON_GetObjectItemCaseSensitive(arguments, "duration_ms"); + int duration_ms = cJSON_IsNumber(duration_item) ? duration_item->valueint : 3000; + if (duration_ms < 500 || duration_ms > 15000) { + return make_error(id, -32602, "duration_ms must be between 500 and 15000"); + } + std::string devices_json; + std::string error; + if (!mcp::scanBleDevices(duration_ms, devices_json, error)) { + return make_error(id, -32020, error.c_str()); + } + return make_tool_result(id, devices_json.c_str()); + } + + if (strcmp(name, "write_file") == 0) { + cJSON* path_item = cJSON_GetObjectItemCaseSensitive(arguments, "path"); + cJSON* content_item = cJSON_GetObjectItemCaseSensitive(arguments, "content"); + if (!cJSON_IsString(path_item) || !cJSON_IsString(content_item)) { + return make_error(id, -32602, "write_file requires path and content"); + } + std::string error; + if (!mcp::writeSdFile(path_item->valuestring, content_item->valuestring, error)) { + return make_error(id, -32020, error.c_str()); + } + return make_tool_result(id, "Successfully wrote file to SD card."); + } + + if (strcmp(name, "read_file") == 0) { + cJSON* path_item = cJSON_GetObjectItemCaseSensitive(arguments, "path"); + if (!cJSON_IsString(path_item)) { + return make_error(id, -32602, "read_file requires path"); + } + std::string content; + std::string error; + if (!mcp::readSdFile(path_item->valuestring, content, error)) { + return make_error(id, -32020, error.c_str()); + } + return make_tool_result(id, content.c_str()); + } + + if (strcmp(name, "download_file") == 0) { + cJSON* url_item = cJSON_GetObjectItemCaseSensitive(arguments, "url"); + cJSON* filename_item = cJSON_GetObjectItemCaseSensitive(arguments, "filename"); + if (!cJSON_IsString(url_item) || !cJSON_IsString(filename_item)) { + return make_error(id, -32602, "download_file requires url and filename"); + } + std::string error; + if (!mcp::downloadSdFile(url_item->valuestring, filename_item->valuestring, error)) { + return make_error(id, -32020, error.c_str()); + } + return make_tool_result(id, "Successfully downloaded file to SD card."); + } + + if (strcmp(name, "get_video_streaming_instructions") == 0) { + cJSON* protocol_item = cJSON_GetObjectItemCaseSensitive(arguments, "protocol"); + const char* protocol = cJSON_IsString(protocol_item) ? protocol_item->valuestring : "all"; + + char buf[512]; + snprintf(buf, sizeof(buf), + "Tactility OS TCP Video Streaming Instructions:\n" + "1. Mono TCP Stream (compatibility mode): Stream raw 15,000-byte PBM/RLCD frames to TCP port 8081.\n" + "2. Color TCP Stream (native): Stream packets to TCP port 8083.\n" + " Packet format:\n" + " - Header (16 bytes): Magic 'RAW\\x01' (4 bytes), X-coord (2 bytes BE), Y-coord (2 bytes BE), Width (2 bytes BE), Height (2 bytes BE), Payload length (4 bytes BE).\n" + " - Payload (variable): Raw big-endian RGB565 pixel data of length 'Payload length'.\n" + "Active protocol selection: %s", protocol); + return make_tool_result(id, buf); + } + + if (strcmp(name, "get_stream_stats") == 0) { + std::string stats_json; + if (!mcp::getVideoStreamStats(stats_json)) { + return make_error(id, -32020, "Failed to get stream stats"); + } + return make_tool_result(id, stats_json.c_str()); + } + + if (strcmp(name, "list_apps") == 0) { + std::string apps_json; + std::string error; + if (!mcp::listApps(apps_json, error)) { + return make_error(id, -32020, error.c_str()); + } + return make_tool_result(id, apps_json.c_str()); + } + + if (strcmp(name, "run_app") == 0) { + cJSON* app_id_item = cJSON_GetObjectItemCaseSensitive(arguments, "app_id"); + if (!cJSON_IsString(app_id_item)) { + return make_error(id, -32602, "run_app requires app_id"); + } + std::string error; + if (!mcp::runApp(app_id_item->valuestring, error)) { + return make_error(id, -32020, error.c_str()); + } + char msg[128]; + snprintf(msg, sizeof(msg), "Successfully started application '%s'.", app_id_item->valuestring); + return make_tool_result(id, msg); + } + + if (strcmp(name, "list_files") == 0) { + cJSON* path_item = cJSON_GetObjectItemCaseSensitive(arguments, "path"); + const char* path = cJSON_IsString(path_item) ? path_item->valuestring : "/"; + std::string files_json; + std::string error; + if (!mcp::listSdFiles(path, files_json, error)) { + return make_error(id, -32020, error.c_str()); + } + return make_tool_result(id, files_json.c_str()); + } + + return make_error(id, -32602, "Unknown tool"); +} + +static cJSON* handle_rpc(const char* body) { + cJSON* request = cJSON_Parse(body); + if (request == NULL) { + return make_error(NULL, -32700, "Parse error"); + } + + cJSON* id = cJSON_GetObjectItemCaseSensitive(request, "id"); + cJSON* jsonrpc = cJSON_GetObjectItemCaseSensitive(request, "jsonrpc"); + cJSON* method = cJSON_GetObjectItemCaseSensitive(request, "method"); + cJSON* params = cJSON_GetObjectItemCaseSensitive(request, "params"); + + if (!cJSON_IsObject(request) || + !cJSON_IsString(jsonrpc) || + strcmp(jsonrpc->valuestring, "2.0") != 0 || + !cJSON_IsString(method)) { + cJSON* response = make_error(id, -32600, "Invalid Request"); + cJSON_Delete(request); + return response; + } + + cJSON* response = NULL; + if (strcmp(method->valuestring, "tools/list") == 0) { + cJSON* tools = cJSON_Parse(TOOLS_JSON); + if (tools == NULL) { + response = make_error(id, -32603, "Failed to construct tool list"); + } else { + response = cJSON_CreateObject(); + cJSON_AddStringToObject(response, "jsonrpc", "2.0"); + cJSON* result = cJSON_AddObjectToObject(response, "result"); + cJSON_AddItemToObject(result, "tools", tools); + if (id != NULL) { + cJSON_AddItemToObject(response, "id", cJSON_Duplicate(id, true)); + } else { + cJSON_AddNullToObject(response, "id"); + } + } + } else if (strcmp(method->valuestring, "tools/call") == 0) { + response = handle_tool_call(id, params); + } else { + response = make_error(id, -32601, "Method not found"); + } + + cJSON_Delete(request); + return response; +} + +// POST /api/mcp +error_t WebServerService::handleApiMcp(HttpServerRequest* request, void*) { + LOG_I(TAG, "POST /api/mcp"); + + if (!settings::mcp::loadOrGetDefault().mcpEnabled) { + http_server_request_send_error(request, 404, "MCP server is disabled"); + return ERROR_UNDEFINED; + } + + const uint64_t declared_length = http_server_request_get_content_length(request); + if (declared_length == 0 || declared_length > MCP_JSON_BODY_LIMIT) { + http_server_request_send_error(request, 400, "Invalid Content-Length"); + return ERROR_INVALID_ARGUMENT; + } + const size_t content_len = static_cast(declared_length); + + char* body = (char*)psram_malloc(content_len + 1); + if (body == NULL) { + http_server_request_send_error(request, 500, "Out of memory"); + return ERROR_RESOURCE; + } + + size_t received = 0; + while (received < content_len) { + int ret = http_server_request_receive(request, body + received, content_len - received); + if (ret <= 0) { + free(body); + http_server_request_send_error(request, 400, "Incomplete request body"); + return ERROR_UNDEFINED; + } + received += static_cast(ret); + } + body[content_len] = '\0'; + + cJSON* response = handle_rpc(body); + free(body); + + char* response_text = cJSON_PrintUnformatted(response); + cJSON_Delete(response); + + if (response_text == NULL) { + http_server_request_send_error(request, 500, "Serialization failed"); + return ERROR_RESOURCE; + } + + http_server_request_set_content_type(request, "application/json"); + http_server_request_set_header(request, "Access-Control-Allow-Origin", "*"); + error_t send_result = http_server_request_send_string(request, response_text); + cJSON_free(response_text); + + return send_result; +} + +static int query_integer(const char* query, const char* name, int default_value) { + if (query == NULL || *query == '\0') { + return default_value; + } + size_t name_size = strlen(name); + while (*query != '\0') { + if (strncmp(query, name, name_size) == 0 && query[name_size] == '=') { + return atoi(query + name_size + 1); + } + query = strchr(query, '&'); + if (query == NULL) { + break; + } + query++; + } + return default_value; +} + +// POST /api/screen/raw +error_t WebServerService::handleApiScreenRaw(HttpServerRequest* request, void*) { + LOG_I(TAG, "POST /api/screen/raw"); + + if (!settings::mcp::loadOrGetDefault().mcpEnabled) { + http_server_request_send_error(request, 404, "MCP server is disabled"); + return ERROR_UNDEFINED; + } + + const uint64_t declared_length = http_server_request_get_content_length(request); + if (declared_length == 0 || declared_length > MCP_RAW_BODY_LIMIT) { + http_server_request_send_error(request, 400, "Invalid Content-Length"); + return ERROR_INVALID_ARGUMENT; + } + const size_t content_len = static_cast(declared_length); + + uint8_t* body = (uint8_t*)psram_malloc(content_len); + if (body == NULL) { + http_server_request_send_error(request, 500, "Out of memory"); + return ERROR_RESOURCE; + } + + size_t received = 0; + while (received < content_len) { + int ret = http_server_request_receive(request, body + received, content_len - received); + if (ret <= 0) { + free(body); + http_server_request_send_error(request, 400, "Incomplete request body"); + return ERROR_UNDEFINED; + } + received += static_cast(ret); + } + + auto& state = mcp::getState(); + char query[192] = {}; + http_server_request_get_query(request, query, sizeof(query)); + int x = query_integer(query, "x", 0); + int y = query_integer(query, "y", 0); + int width = query_integer(query, "w", state.drawWidth); + int height = query_integer(query, "h", state.drawHeight); + + bool valid_size = width > 0 && height > 0 && + (size_t)width * height * 2 == (size_t)content_len; + bool drawn = valid_size && mcp::drawRgb565( + body, + content_len, + x, + y, + width, + height + ); + free(body); + + if (drawn) { + http_server_request_set_content_type(request, "text/plain"); + http_server_request_set_header(request, "Access-Control-Allow-Origin", "*"); + return http_server_request_send_string(request, "OK"); + } else { + http_server_request_send_error(request, 400, "Invalid RGB565 payload or size"); + return ERROR_INVALID_ARGUMENT; + } +} + +} // namespace tt::service::webserver + +#endif diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index 4aad7b0d..698172cd 100644 --- a/Tactility/Source/service/webserver/WebServerService.cpp +++ b/Tactility/Source/service/webserver/WebServerService.cpp @@ -32,6 +32,8 @@ #include "app/manager.h" #ifdef ESP_PLATFORM +#include +#include #include #include #include @@ -213,7 +215,7 @@ bool WebServerService::onStart(ServiceContext& service) { lock.lock(); g_cachedSettings = settings::webserver::loadOrGetDefault(); g_settingsCached = true; - serverEnabled = g_cachedSettings.webServerEnabled; + serverEnabled = g_cachedSettings.webServerEnabled || settings::mcp::loadOrGetDefault().mcpEnabled; } // Subscribe to settings change events to refresh cache settingsEventSubscription = pubsub->subscribe([](WebServerEvent event) { @@ -222,6 +224,10 @@ bool WebServerService::onStart(ServiceContext& service) { lock.lock(); g_cachedSettings = settings::webserver::loadOrGetDefault(); g_settingsCached = true; + const bool enabled = g_cachedSettings.webServerEnabled || settings::mcp::loadOrGetDefault().mcpEnabled; + if (g_webServerInstance.load() != nullptr) { + g_webServerInstance.load()->setEnabled(enabled); + } } }); @@ -481,6 +487,21 @@ bool WebServerService::startServer() { .callback = handleAdminPost, .user_ctx = ctx }, +#ifdef ESP_PLATFORM + // MCP is LAN-local and is enabled whenever the web server is enabled. + { + .uri = "/api/mcp", + .method = HTTP_METHOD_POST, + .callback = handleApiMcp, + .user_ctx = ctx + }, + { + .uri = "/api/screen/raw", + .method = HTTP_METHOD_POST, + .callback = handleApiScreenRaw, + .user_ctx = ctx + }, +#endif // API endpoints for system info, apps, wifi, etc { .uri = "/api/*", @@ -528,6 +549,12 @@ bool WebServerService::startServer() { LOG_I(TAG, "HTTP server started successfully on port %u", (unsigned)settings.webServerPort); publish_event(this, WebServerEvent::WebServerStarted); +#ifdef ESP_PLATFORM + if (settings::mcp::loadOrGetDefault().mcpEnabled) { + mcp::startVideoStreamServer(); + } +#endif + // Show statusbar icon if (statusbarIconId >= 0) { lvgl::statusbar_icon_set_image(statusbarIconId, LVGL_ICON_STATUSBAR_CLOUD); @@ -544,6 +571,9 @@ void WebServerService::stopServer() { return; } +#ifdef ESP_PLATFORM + mcp::stopVideoStreamServer(); +#endif http_server_free(httpServer); httpServer = nullptr; diff --git a/Tactility/Source/settings/DisplaySettings.cpp b/Tactility/Source/settings/DisplaySettings.cpp index dbcbd266..a8208a3e 100644 --- a/Tactility/Source/settings/DisplaySettings.cpp +++ b/Tactility/Source/settings/DisplaySettings.cpp @@ -20,6 +20,7 @@ static std::string getSettingsFilePath() { } constexpr auto* SETTINGS_KEY_ORIENTATION = "orientation"; +constexpr auto* SETTINGS_KEY_FONT_SIZE = "fontSize"; constexpr auto* SETTINGS_KEY_GAMMA_CURVE = "gammaCurve"; constexpr auto* SETTINGS_KEY_BACKLIGHT_DUTY = "backlightDuty"; constexpr auto* SETTINGS_KEY_TIMEOUT_ENABLED = "backlightTimeoutEnabled"; @@ -73,6 +74,34 @@ static bool fromString(const std::string& str, Orientation& orientation) { } } +static std::string toString(FontSize font_size) { + switch (font_size) { + using enum FontSize; + case Small: + return "Small"; + case Default: + return "Default"; + case Large: + return "Large"; + default: + std::unreachable(); + } +} + +static bool fromString(const std::string& str, FontSize& font_size) { + if (str == "Small") { + font_size = FontSize::Small; + return true; + } else if (str == "Default") { + font_size = FontSize::Default; + return true; + } else if (str == "Large") { + font_size = FontSize::Large; + return true; + } + return false; +} + static std::string toString(ScreensaverType type) { switch (type) { using enum ScreensaverType; @@ -86,6 +115,8 @@ static std::string toString(ScreensaverType type) { return "MatrixRain"; case StackChan: return "StackChan"; + case McpScreen: + return "McpScreen"; default: std::unreachable(); } @@ -107,6 +138,9 @@ static bool fromString(const std::string& str, ScreensaverType& type) { } else if (str == "StackChan") { type = ScreensaverType::StackChan; return true; + } else if (str == "McpScreen") { + type = ScreensaverType::McpScreen; + return true; } else { return false; } @@ -129,6 +163,12 @@ bool load(DisplaySettings& settings) { orientation = getDefaultOrientation(); } + auto font_size_entry = map.find(SETTINGS_KEY_FONT_SIZE); + FontSize font_size = FontSize::Default; + if (font_size_entry != map.end()) { + fromString(font_size_entry->second, font_size); + } + auto gamma_entry = map.find(SETTINGS_KEY_GAMMA_CURVE); int gamma_curve = 0; if (gamma_entry != map.end()) { @@ -163,6 +203,7 @@ bool load(DisplaySettings& settings) { } settings.orientation = orientation; + settings.fontSize = font_size; settings.gammaCurve = gamma_curve; settings.backlightDuty = backlight_duty; settings.backlightTimeoutEnabled = timeout_enabled; @@ -175,6 +216,7 @@ bool load(DisplaySettings& settings) { DisplaySettings getDefault() { return DisplaySettings { .orientation = getDefaultOrientation(), + .fontSize = FontSize::Default, .gammaCurve = 1, .backlightDuty = 200, .backlightTimeoutEnabled = false, @@ -196,6 +238,7 @@ bool save(const DisplaySettings& settings) { map[SETTINGS_KEY_BACKLIGHT_DUTY] = std::to_string(settings.backlightDuty); map[SETTINGS_KEY_GAMMA_CURVE] = std::to_string(settings.gammaCurve); map[SETTINGS_KEY_ORIENTATION] = toString(settings.orientation); + map[SETTINGS_KEY_FONT_SIZE] = toString(settings.fontSize); map[SETTINGS_KEY_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0"; map[SETTINGS_KEY_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs); map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType); diff --git a/Tactility/Source/settings/McpSettings.cpp b/Tactility/Source/settings/McpSettings.cpp new file mode 100644 index 00000000..4697a4d0 --- /dev/null +++ b/Tactility/Source/settings/McpSettings.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +constexpr auto* TAG = "McpSettings"; +#include +#include + +namespace tt::settings::mcp { + + +static std::string getSettingsFilePath() { + char path[256]; + if (app_paths_get_user_data_path("tactility.mcpsettings", "mcp.properties", path, sizeof(path)) != ERROR_NONE) { + return ""; + } + return path; +} + +constexpr auto* KEY_MCP_ENABLED = "mcpEnabled"; + +bool load(McpSettings& settings) { + auto settings_path = getSettingsFilePath(); + if (!file::isFile(settings_path)) { + return false; + } + std::map map; + if (!file::loadPropertiesFile(settings_path, map)) { + return false; + } + + auto mcp_enabled = map.find(KEY_MCP_ENABLED); + settings.mcpEnabled = (mcp_enabled != map.end()) + ? (mcp_enabled->second == "1" || mcp_enabled->second == "true") + : false; + + return true; +} + +McpSettings getDefault() { + return McpSettings{ + .mcpEnabled = false + }; +} + +McpSettings loadOrGetDefault() { + McpSettings settings; + if (!load(settings)) { + settings = getDefault(); + if (!save(settings)) { + LOG_W(TAG, "Failed to save default MCP settings"); + } + } + return settings; +} + +bool save(const McpSettings& settings) { + std::map map; + map[KEY_MCP_ENABLED] = settings.mcpEnabled ? "true" : "false"; + + auto settings_path = getSettingsFilePath(); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + LOG_E(TAG, "Failed to create parent dir for %s", settings_path.c_str()); + return false; + } + + if (!file::savePropertiesFile(settings_path, map)) { + LOG_E(TAG, "Failed to save MCP settings to %s", settings_path.c_str()); + return false; + } + return true; +} + +} // namespace diff --git a/Tactility/idf_component.yml b/Tactility/idf_component.yml index 962baf3e..f6007263 100644 --- a/Tactility/idf_component.yml +++ b/Tactility/idf_component.yml @@ -43,6 +43,10 @@ dependencies: version: "1.3.4" rules: - if: "target in [esp32, esp32s3]" + espressif/esp_lcd_st77922: + version: "1.0.3" + rules: + - if: "target in [esp32s3]" espressif/esp_lcd_gc9a01: "2.0.3" espressif/esp_lcd_jd9165: version: "1.0.3" @@ -95,4 +99,3 @@ dependencies: rules: - if: "target in [esp32, esp32s3, esp32p4]" idf: '5.5.2' - diff --git a/partitions-16mb-with-sd.csv b/partitions-16mb-with-sd.csv index 6e011704..2899df36 100644 --- a/partitions-16mb-with-sd.csv +++ b/partitions-16mb-with-sd.csv @@ -3,4 +3,4 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 4M, -system, data, fat, , 128k, +system, data, fat, , 512k,