From 3246d36576af525c148390db82efb3539e05cae4 Mon Sep 17 00:00:00 2001 From: jonnosan Date: Sat, 4 Sep 2010 07:50:58 +0000 Subject: [PATCH] git-svn-id: http://svn.code.sf.net/p/netboot65/code@273 93682198-c243-4bdb-bd91-e943c89aac3b --- client/basic/Makefile | 3 + client/basic/bails.s | 54 +++++- client/basic/httpd.inc | 379 +++++++++++++++++++++++++++++++++++++ client/basic/kipperbas.d64 | Bin 174848 -> 174848 bytes client/cfg/kipperbas.cfg | 2 + client/ip65/tcp.s | 2 + 6 files changed, 435 insertions(+), 5 deletions(-) create mode 100644 client/basic/httpd.inc diff --git a/client/basic/Makefile b/client/basic/Makefile index 136c8c2..6a1ac6a 100644 --- a/client/basic/Makefile +++ b/client/basic/Makefile @@ -19,6 +19,9 @@ C64PROGLIB=../drivers/c64prog.lib all: kipperbas.d64 bails.prg +bails.o: bails.s $(INCFILES) httpd.inc + $(AS) $(AFLAGS) $< + %.o: %.s $(INCFILES) $(AS) $(AFLAGS) $< diff --git a/client/basic/bails.s b/client/basic/bails.s index 73b72a6..293d9da 100644 --- a/client/basic/bails.s +++ b/client/basic/bails.s @@ -81,6 +81,7 @@ crunched_line = $0200 ;Input buffer .import http_variables_buffer + .zeropage temp: .res 2 temp2: .res 2 @@ -566,6 +567,16 @@ make_null_terminated_and_print: ldax #string_buffer jmp print +print_integer: +sta $63 +stx $62 +jmp $bdd1 ;BASIC routine + +print_decimal: + jsr reset_string + jsr emit_decimal + jmp make_null_terminated_and_print + print_mac: jsr reset_string jsr emit_mac @@ -888,7 +899,8 @@ grok_keyword: stax http_variables_buffer jsr extract_string ldax #transfer_buffer - jsr http_parse_request +got_http_request: + jsr http_parse_request lda #$02 jsr http_get_value stax copy_src @@ -964,10 +976,19 @@ grok_keyword: httpd_keyword: jsr get_integer - stax httpd_port + stax httpd_port_number jsr skip_comma_get_integer stax default_line_number - rts + ldax #listening + jsr print + ldax #cfg_ip + jsr print_dotted_quad + lda #':' + jsr print_a + ldax httpd_port_number + jsr print_integer + jsr print_cr + jmp httpd_start .rodata vectors: @@ -983,6 +1004,9 @@ vectors: hexdigits: .byte "0123456789ABCDEF" +listening: +.byte"LISTENING ON ",0 + pinging: .byte"PINGING ",0 interface_type: @@ -1104,5 +1128,25 @@ transfer_buffer: .res 256 handler_address: .res 2 hash: .res 1 string_ptr: .res 1 -httpd_port: .res 2 -default_line_number: .res 2 \ No newline at end of file +default_line_number: .res 2 + + +.include "httpd.inc" +;-- LICENSE FOR bails.s -- +; The contents of this file are subject to the Mozilla Public License +; Version 1.1 (the "License"); you may not use this file except in +; compliance with the License. You may obtain a copy of the License at +; http://www.mozilla.org/MPL/ +; +; Software distributed under the License is distributed on an "AS IS" +; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +; License for the specific language governing rights and limitations +; under the License. +; +; The Original Code is netboot65. +; +; The Initial Developer of the Original Code is Jonno Downes, +; jonno@jamtronix.com. +; Portions created by the Initial Developer are Copyright (C) 2009,2010 +; Jonno Downes. All Rights Reserved. +; -- LICENSE END -- diff --git a/client/basic/httpd.inc b/client/basic/httpd.inc new file mode 100644 index 0000000..ee8e55a --- /dev/null +++ b/client/basic/httpd.inc @@ -0,0 +1,379 @@ +;a simple HTTP server (ip65 httpd customised for use with BASIC on bails) +;.include "../inc/common.i" +; +;.ifndef KPR_API_VERSION_NUMBER +; .define EQU = +; .include "../inc/kipper_constants.i" +;.endif + + +HTTPD_TIMEOUT_SECONDS=5 ;what's the maximum time we let 1 connection be open for? + +DEBUG=1 + + +.import http_parse_request +.import http_get_value +.import tcp_listen +.import tcp_callback +.import ip65_process +.import check_for_abort_key +.import ip65_error +.import parse_hex_digits +.import copymem +.importzp copy_src +.importzp copy_dest +.import tcp_inbound_data_ptr +.import tcp_inbound_data_length +.import tcp_send_data_len +.import tcp_send +.import tcp_close +.import io_read_catalogue +.import native_to_ascii +.import io_read_file_with_callback +.import io_filename +.import io_callback +.import tcp_connect_remote_port +.import tcp_remote_ip + +temp_ptr =copy_src + +.bss +found_eol: .byte 0 +connection_closed: .byte 0 +output_buffer_length: .res 2 +sent_header: .res 1 +connection_timeout_seconds: .byte 0 +tcp_buffer_ptr: .res 2 +buffer_size: .res 1 +temp_x: .res 1 + + +.segment "TCP_VARS" + +__httpd_io_buffer: .res 1024 ;temp buffer for storing inbound requests. + +.segment "HTTP_VARS" + +httpd_io_buffer: .word __httpd_io_buffer +httpd_port_number: .word 80 + + +get_next_byte: + lda $ffff + inc get_next_byte+1 + bne @skip + inc get_next_byte+2 +@skip: + rts + + +xmit_a: + +xmit_a_ptr: + sta $ffff + inc xmit_a_ptr+1 + bne :+ + inc xmit_a_ptr+2 +: + inc output_buffer_length + bne :+ + inc output_buffer_length+1 + lda output_buffer_length+1 + cmp #2 + bne :+ + stx temp_x + jsr send_buffer + ldx temp_x +: + rts + +.code + +;start a HTTP server +;this routine will stay in an endless loop that is broken only if user press the ABORT key (runstop on a c64) +;inputs: +; none +;outputs: +; none +httpd_start: + lda #0 + sta $dc08 ;make sure TOD clock is started + +@listen: + jsr tcp_close + ldax httpd_io_buffer + stax tcp_buffer_ptr + ldax #http_callback + stax tcp_callback + ldax httpd_port_number + + jsr tcp_listen + bcc @connect_ok + rts +@connect_ok: +.ifdef DEBUG + inc $d020 +.endif + ldax #connection_from + jsr print + ldax #tcp_remote_ip + jsr print_dotted_quad + lda #':' + jsr print_a + ldax tcp_connect_remote_port + + jsr print_integer + jsr print_cr + lda #0 + sta connection_closed + sta found_eol + clc + lda $dc09 ;time of day clock: seconds (in BCD) + sed + adc #HTTPD_TIMEOUT_SECONDS + cmp #$60 + bcc @timeout_set + sec + sbc #$60 +@timeout_set: + cld + sta connection_timeout_seconds + +@main_polling_loop: + jsr ip65_process + jsr check_for_abort_key + bcc @no_abort + lda #KPR_ERROR_ABORTED_BY_USER + sta ip65_error + rts +@no_abort: + lda found_eol + bne @got_eol + + lda $dc09 ;time of day clock: seconds + + cmp connection_timeout_seconds + beq @connection_timed_out + lda connection_closed + beq @main_polling_loop +@connection_timed_out: +.ifdef DEBUG + dec $d020 +.endif + jmp @listen + +@got_eol: + ldax httpd_io_buffer + jmp got_http_request + +http_callback: + lda tcp_inbound_data_length+1 + cmp #$ff + bne @not_eof + inc connection_closed +@done: + rts +@not_eof: + lda found_eol + bne @done + +;copy this chunk to our input buffer + ldax tcp_buffer_ptr + stax copy_dest + ldax tcp_inbound_data_ptr + stax copy_src + ldax tcp_inbound_data_length + jsr copymem + +;increment the pointer into the input buffer + clc + lda tcp_buffer_ptr + adc tcp_inbound_data_length + sta tcp_buffer_ptr + sta temp_ptr + lda tcp_buffer_ptr+1 + adc tcp_inbound_data_length+1 + sta tcp_buffer_ptr+1 + sta temp_ptr+1 + +;put a null byte at the end (assumes we have set temp_ptr already) + lda #0 + tay + sta (temp_ptr),y + +;look for CR or LF in input + sta found_eol + ldax httpd_io_buffer + stax get_next_byte+1 + +@look_for_eol: + jsr get_next_byte + cmp #$0a + beq @found_eol + cmp #$0d + bne @not_eol +@found_eol: + inc found_eol + rts +@not_eol: + cmp #0 + bne @look_for_eol + rts + + + +reset_output_buffer: + ldax httpd_io_buffer + sta xmit_a_ptr+1 + stx xmit_a_ptr+2 + lda #0 + sta output_buffer_length + sta output_buffer_length+1 + rts + + +send_buffer: + ldax output_buffer_length + stax tcp_send_data_len + ldax httpd_io_buffer + jsr tcp_send + jmp reset_output_buffer + + send_header: +;inputs: Y = header type +;$00 = no header (assume header sent already) +;$01 = 200 OK, 'text/text' +;$02 = 200 OK, 'text/html' +;$03 = 200 OK, 'application/octet-stream' +;$04 = 404 Not Found +;$05..$FF = 500 System Error + + cpy #00 + bne :+ + rts +: + cpy #1 + bne @not_text + jsr emit_ok_status_line_and_content_type + ldax #text_text + jsr emit_string + jmp @done + +@not_text: + cpy #2 + + bne @not_html + jsr emit_ok_status_line_and_content_type + ldax #text_html + jsr emit_string + jmp @done + +@not_html: + cpy #3 + bne @not_binary + jsr emit_ok_status_line_and_content_type + ldax #application_octet_stream + jsr emit_string + jmp @done + +@not_binary: + cpy #4 + bne @not_404 + ldax #http_version + jsr emit_string + ldax #status_not_found + jsr emit_string + + jsr @done + ldax #status_not_found + jmp emit_string + +@not_404: + ldax #http_version + jsr emit_string + ldax #status_system_error + jsr emit_string + jsr @done + ldax #status_system_error + jmp emit_string +@done: + ldax #end_of_header + jmp emit_string + + +emit_ok_status_line_and_content_type: + ldax #http_version + jsr emit_string + ldax #status_ok + jsr emit_string + ldax #content_type + jmp emit_string + +emit_string: + stax temp_ptr + ldy #0 +@next_byte: + lda (temp_ptr),y + beq @done + jsr xmit_a + iny + bne @next_byte +@done: + rts + + +.rodata + +CR=$0D +LF=$0A + +http_version: + .byte "HTTP/1.0 ",0 + +status_ok: + .byte "200 OK",CR,LF,0 + +status_not_found: + .byte "404 Not Found",CR,LF,0 + +status_system_error: + .byte "500 System Error",CR,LF,0 +content_type: + .byte "Content-Type: ",0 + +text_text: + .byte "text/text",CR,LF,0 + +text_html: + .byte "text/html",CR,LF,0 + +application_octet_stream: + .byte "application/octet-stream",CR,LF,0 + +end_of_header: + .byte "Connection: Close",CR,LF + .byte "Server: BoB_httpd/0.c64",CR,LF + .byte CR,LF,0 + +connection_from: .byte "CONNECTION FROM ",0 + + +;-- LICENSE FOR httpd.inc -- +; The contents of this file are subject to the Mozilla Public License +; Version 1.1 (the "License"); you may not use this file except in +; compliance with the License. You may obtain a copy of the License at +; http://www.mozilla.org/MPL/ +; +; Software distributed under the License is distributed on an "AS IS" +; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +; License for the specific language governing rights and limitations +; under the License. +; +; The Original Code is netboot65. +; +; The Initial Developer of the Original Code is Jonno Downes, +; jonno@jamtronix.com. +; Portions created by the Initial Developer are Copyright (C) 2009,2010 +; Jonno Downes. All Rights Reserved. +; -- LICENSE END -- diff --git a/client/basic/kipperbas.d64 b/client/basic/kipperbas.d64 index 3e926a0580f26817accf5efa94eac91e9ea67c80..96af7d1498804a07913398b919a806687716d732 100644 GIT binary patch delta 7716 zcmZuW3se+WmR;S@H2-cuZ4tHn1jP?xB8HfN2!22-iXZSFOEBpwC(v=0*ZX$%0O+BK3%z_y6Mx zZZo&X|LzKI@zjPxx-)X6d&-4}oR-ixltF9WLgoIq!Mt#0KT}!S`C$^PAQv zrV|_bXgxZyL2T(c`bLMTr-xV8D24pNh7RtLvH6j)#TbiKWQ%JQWUg_Mt*)RgV~dN{ zxEkTB#znWf8n?3Per@OjsmK;dDawl+DoTa<0&k{ME&}qfcq9Cq{3sZopOiySg}vP9 z(eB=OL%S{Vzo7i7w@H@kjACj(=Q`Fimp$#H!l}1g-aPsiKea+R7j*G!Yq}Hp-`2Dx zDM`UY!fz`SIf#M@y)ruSW?#RSnSEb$c5FXh-_YSY-q0ZO`z!c=0v&)j%65NV2{)ti zV1sl9_II+PPqk{d#HuPLWLMbJ0BfXz%!!eKda^=js^Co(%BJ8Uc!3)EcPjXg{oeyZ z-k$~=80dik;y*s9{31SaAt>?RgH;C=8lSif6Ou9UpYe%nlM{c4PyBUq;=}mFt;vZ` z*+gbeg>rjxYSdWcRqjsCFuxL>U&H^$vu)|%Y5enRl;wtY1XL+1TZ1aUyrPX0gBJcm zg>RcnIU45zs*4N8FFV*h6@22oSmz%itM$aeZ7%H=>;PygbA)iE0$UK4S12GBdx5yZ za&LtK`(U3S2fRQR<2GucM?*|AJkV1XvPWD}r2xi2HE0Rnl>#Xk!a`JXL46M@m7fRK z@;|8qXFDe;X~?p$=DYdxwiZzBXv)BQctRwM3eU^_d;a(+$5f-*^M22QzTQq+K4& z#KJpkgC-#oQ|1{m(JWW+od4&mxPq0LE@F29op)pWpJE-R6P-6YHS>+vqM0tu61BUi z-4!wGiTtl)`sJVrR?$pX$nNrwt>R`wH1f4;l#LTM;A__LZM#9%m2vmoWw?*Rk@J#~ z9$>LkR89oflLFPZNaf{q%y1@VxC|G6xV}~9$)6<{4F6futo)G!!aM8uSD&TlCC#Sn zFo=mX!=--*s5xAnf7l21MBiM%SA3>lkL2jNk#xP_tiz?o^!J%Z^42DWNykWJH4y*cdn#~IVpU3Ya1iof zhe@h}ObD<`&v1FW>k(i+Z?~)wz&K1Gh@h{C@OA6>ZM(ZI(eo0(!L83`xsESTJ50wH zs^c4*z~k%p#Fqrmb-2J~{M$>p$5w)Vv^$C^$dLuAZxW|_c85v$uMGv|`a;D7^vG9` zL}q+{sFa(2-~*|{bj74+^qe};*{40Yar(w<{n{P+fuyGxQ-AzG(!LNCNHgFl3BmG~ zQ%6s;uNHek=h4>`9(;I8RaI*h_y`%5d=Dh4ODY)|m0Cw0NFu+=pI^o;$-N+*{ZQh2 zsudc#D4qR3V;Bm3B%QtFf3b`!OnoSIQQIXlCMjR~JIgqp8}q+g#?8yV*+eof(OXR< z^HRiGl%)I!r2evun>}wh*wpgNqh~ryp<7LQcF!+~?NT2+lP`fHZk*}YMnX6JWvjU| z?{HJUeN2SXctHx>f$@EjJZMsEWVA^Hg>4C3Zw9y$Bc%xP#5Jip20mknmZCs0n)z2L z@D%E|4?(zm*euek%}YOSUV5#0=_k#wC?#kOTx7^jF;t4eSDR-RqmQ0KT4KNbPEjfl z1MI8KURI2MXSZ(bPVGqSQQ9vyLyZyXhs_aAPo-rjv>NfdtGa9X-&OMi)x5e13}bQw zL(UzS+3Z$KfiyP|1(KJgfXxl80$DKenX-u7Y=ZT-n)t=liWy`C8#v`5QifV(3t5dy zL?!!ru(?G!+7+~QSbG#buSdCX(ND^up4eqAl{#dQ!wT7CPYI;!@Lc($%aSJa+wUn^ zL7D%-CXgXZ4h%Pe1WbWuU>ki@PBW9!5_ZZUQCzxyd(t-~a~j(4h#vGNzHK_6pHz~Uh@@|R3Ke``{#^`LDsR=sdj9$Rqk2G5^ z;68`+S$%)phqRl~eW=}T+ksl!cOhDi+66?{p!OOBvGZd_xMYHm7bxW%`v#H3WA>2} z@zv^>vk|~EyLxL;`wrB*8v(W6XBoBLt&AG99-j>E1ENMSP7S(SAQb6F6xxLNDOF&Q z3KXy!24NQajIKu{(@i%ZlHsORh&bG|+KY(YtvE<3_!r4>lPPYJ;#SN+>h+`DK*}lG zNVc1#yU)-~NH&oyH%W7Mz3PGAwyu}IlwO7y)$VsmjR(-P2a(8ERPjfu z+N??oC`{xl>-mqX`Db=IMZUkP+fHqA#HJq!l(-QszE^5qZjeJHY=|H*+3E`Epp~U+^Q+VoOmJfw1XNcbHg+%$tat90CTscz?f%Td3VU) z!H!T{_>hisW=qS>Y1zH1>Q8(%vDzXbr zHQ^&jhdu`8h9HkX4TJXBgCZ7vVI)Cc3Vv{J-0klo{e!{Jk=6$!qDK%=i6q(6gpnG1x>C$IH3(jD zVx)XOE>|N8HK8HdL>iH=2|-l&ASO6)L(bzf#}#Vv_!8L2L=rtds|O;(XGDe%BEuB^ zE`s0~vfGS}3SVr6cKH@;KGhRQ2SR}~Pawks#qFauY2kcXPjj}u zoG;5j9``=M&X4r`3?de!h0^WuPkZKk4DLS%`{rU`pTnRg;>5n`nA&hC8IvsR z%f^ajXRtQzf-*>gFm>VJK31>zf#Rj@j^zn85vnPxt&hK+Nn6TfhkzOGB_1$KQlZJfQF28 z4i3%5akmR{9Wl_o&_e?;q}^j5y>}0>@fSm_hWmi`7@Wqv$8NWTQ}DR|tc&}Pw|XLX z8V;0$Maa!80zn(lQW)-?088!I=fEK+rqglU)gK{D^DwDEbS5U1h|a=fEuynAS%*To zn9jg)FQs-Ia^SBe8<9G3)a%4xw&|c|W&x&6*e57)mK>TblU(eZf$3B^G)*RX*f$eX zryQCtlUdj|8;jI%$W$efq5hDW1ESm_^wxqMc^esS}fZg#D;y7 zrsW9l#i0VZf7pouk*nkXcL(J$)Zck>$){<8Tx$$6NCu{J`0diW$4 zaPnnhr<}k#3`6H&2TR-zARLc&z_A->B}M0B_=CeK2G?dsaSMDxsDlBaasW0HjAr3b zHvUR95XzqA1f0e!=G}nSxHB7*Y%CgU;;RAAN%OJ(7$l21gZUW#Slq-V0}9b>41Yib z_UaVrV>pzL1-XvcT)t#iGz9|CL{eP-Ljt$Nu>kuP;*!o&BMWfLsm{|Q3;q8laO=Da zaOY{V5clg@&dyVfaaiL*Y|m~vEzH>oZC}f2MrAoBk7M!#CMz)i#sPlwZvK-^BLBUe zZDxr0_jYzapxG=*!J+s-#0szS{SgPeZf=4cm?B5kX&Y#|?36ReVocLy*+iCLItHgV zFML0gMwZ~B1=KD>-742+!C66@u0X7^22sg@eyVF1q-N_C6U#8s<;+VT$1E(!QcQE@ z0XRwI(i!r=6-mpYdGf$jc1t6$`el>c)N;DR|4Z=SRp3MFkOV%LBQGbdLw)n;yxHGV}L$w_(u2OBCK7fg&1%PFR1;o z^yh8i7EiC;^Nn7X8?dYTEn&T1+`=6?l)8`Bs4_>ksE|v2o7Jx`rgjwqC?7z>n^kCh zx2W0&(Ha#{A`Wws-6H&EH>kj_PE>qY4n6>ue>|v;N4tq1_0O*1mT;qfp@u6j{T7Cd zi$(K|bBQu8*T%Up9zWPK$-^J1;TFHWl{0e%y zKg`Wj;YenjILn=!DCqC5vT!39s+#&Zil2WCf&) zOgWMT3G2v#xZZyP28SLBZt#|?{FdFUX9M^OKxwlGs}3-s%-US0z23QUVAU!WwDpCu zYTUxmgz}?xP)F@5qX?R1P2SVbO`sci`EsB_?VaoY=?-qX_hg;61u`ULhBUe5G*n3T z`vRoCmNfo<>N-z>Adse)t6Ea$DT9);Gfhq*g{s1G=`x3v2|2-@7O5eSdbAEMQ57!E zyXWh?LFTeYMV@{dt}-XJ3R!J(>;CduZmM^*jxJI4oC!ZW}1&0GxQ&dRUjBbv> zBU=UWNR|r15{JsC?(;9N3P))E63aqKz}>y9gCeNNEy(bHJZ9U3mh z+P!Eg4m^!%F%CS5=}H`U3VMSQV{LLjTL$|WEfHJU-4EbTI}?6}tq+%Y8A5m!Ll`=y z-5e|hybeq6R<%9FeGD!Zn7ANQrW>S%1i~(Avfa@-6$(nNN^8|nof^NZ0V78a>Q{NZb zSk<6A)zB_gejG9dyT%-03#>+eEKV*{eiJ-Mim`7c6i)s*f9WnRpNB9u@vX3b;{{7g zMs})Its}eCF1Ti<^Zh^E#nly*;b3h>Clwfeba*zBu0KgD@xU6a-=wQ?s2n=2qfq2KPwVMD zr}Xy+DzKi=Ck)op3VgN_x39%OHd={=GxhMPr^4@pYq1k{ZvY!L z_)GS1irJCozq$vI&3n0X8GO3qXDJz(_SCFwnoNdsSSz-)^NQ zZ{Ok?$~%QGuWCZ=&I>h4lFQl?*kB@wv#(>aJ_d#KMw5+y5yb7N-X6MqBpH+66hddF!jg{_jBMe!``W6#0;CzISxx}T+ z2Ii-(Q%jkJlMD807JpC6r1Os%HN2xYAyEzV`U+f8IZSbE!Crq){cwjm&|cu$k;BYQ zF4&tL$$lQ}P`iO7>PSgO@K-Bc|CPg}N3`xO_O*QA%VA0w$1(m;5&4_v^V&7+y4A#R zsHj8EM~8~6wXOUA(r9RHWyxx?h|Q~PWS$v^Ju^1yV{0+at{MiJ7gymWRUT7(c9p!i zY9G8Uu9BBj?OQ^@ooeq_e74CSlbzzc`f{nzhsQU$K}vsVJ|mZ z)%!R9qTaQ#|1GnJ-^cfD?+`;nTh)Qq+4S2y%pHEe_PzaW?BqPs=BZ)}%331WkIU+# zNUx`o`#6uR^&n4p`&xA9y^c;bVd9Y0(zx_ser03T!GcOF`|~{Z-#`Z-4q56-$z`VJ z=T-9W!2aIM>rf)qK5KC?6|&j4wG!s=83|(}o%GE-&Ys5_@<^sg)Hl@xVBN3oymCXNu`Ll4jX24?Lh&JaKW0IvhwA;72OfTJNm$2j1l5a7%>ppya; zlJkf<4mz*TagcN45b9Ul%d6QB*Vj(~Ph($RO_Dr&K|37Ew~~z>gWbPds|h*vMP1RZ0P79nGH*;I!KxRotdmZw$g7rU}K-(3@$Oj zHFr6)U|k!p#>fLU&(g*!_F#c0jP(cQyP_H)Yoa!zk$SrBcVRdG0^shuqTiri?bbx@ zc!9^j`GX`JOgfS;-?w?%Tz_27WGvHc_?`{;oC>m^1se^Anr=3!VK?6jXg1XtxM!2^ z+5BNz1p7fyyXrB(ELpR8@7Y|BmNT;;8rkyI;H9HBV9N?wyd7knq8o_3pt}!Y@G)Lb z4=~wcC5Jp~aE9Vsps+$AHJpJO?yikpx2H~E@%uas?!3>dk?f{j-0?#8jrH;|UNw;% zotag>Ytw!S+y-n-r(Xhlq90CR^X_Rk{K?uh{{$`L=zvWLYF|>@EUtu@3k=w}TZN!M z)dc*1Nl0cLfOn1(jjgP$_#j*1=Z^T%gLTHv z79+?BU{c^@CQ5~@o`>vzidcS?|ESlMs`^xHPw5d z>^Av0uf@wB@T#%!xen{Rch19CgB#ps|j?Y#QXN+8XEql9}_)gIn| zgud09!<+WMMOg6RNyWu=#o#0OJny`~^UZv2|9QTy{{nAir@Q8^WHP6H$RGWPXIo2% z>^;FB_31;e_pkiX|8f0lC9^2@65lMFuHcJ2x$J6M$*{~t*Qu3E+Qid#9DhYVW5)?s z{E^vFvnba} zCZ6(C*OL9s9#dmvE74L~$@vp5yb9{cJzkBK2Y8Sp60*sTTu9eGqi|W_Rj$)KNbW(%Z+weLc;`+Buct#)6i_MNJBtDya} zjnSahQ@U13BzwLD^l_2u?-DigWvZSlb`puN+DSO>p*F`WkX2rR9NMJjPG1h{3KLY5 z?*-Jw2wF_1xt|<>(9E^^vJn2p=39*LH5>c2&0@tXk@H1#bOq8Vu4&ao0;0H`14w(CM zt#6bBE&BjHqq%(0+{N-I0Uf(p(rmz-b}M1ld|x8r_$^BWc+uAb>--`)l^&WF_Yuv8x`GQ>{YCO@!y@7paxvN;+i zf#RKDfTf-!(7v;WE_U6{t=a}ox^Mb9NAbSZDB9Hg%4Pl)h*5Q@ncuextZ53IbO zu1pMF#Ymk1s5@(rcCEVzsb>R)2smgO<$xY-%?1cG2KONN`Wk_=eM6aE zgM?zK6Bze(NFhT2=tcMz!nYCbL!Jcp9e_Rxh4>RX!J~lau{E9QBT|Xa)R5odt{So# zch|t{XEjuQJP$eNBa#oJ4CGt@0>U00h~2h5?(1D$17V;VYYxNw#K;&UgL0Gr@lO@gHAPUN_=o$Z2)Yu7JOo_m#286s-rt}v|mSe>gdxt zIx>VlqoccYbhnQFOh+#Zp;aCIcOCt?jy_A#brvD)m0^d_65h-$DrN7K$OXtBp+yiQ zwLAkxwNlmOV6_I`{$2y{&uhpMe743D?mSo1Nz>IAR9j>jK2Kp60H*Tpi#4Ol8^Bj< z^qTst{#qhQggK5X&|3TF{YP2Hu}v+B!2cpGAD57OEw!b!5lZpFi|Ak(I=B%XJdaxc zteO1JY2p6mYDC~XQp5d=)ERZfavAb&MDEShUCI#O?A(at^GJ*4d=okUi6AnZdSo~u zG7P@W2!f;6Y|=N%J=_TGavyB|4sr*8k-Hzc?;mg zb+1GT0@K{`+xok`K9k^Q%<;Y$c&LtF^}-)aGFQR_FcIo=AY4pyqk&S!W>0GXt94`- znl%_epYadmgcTXEgB7MXb2~Qymw7q~5a6{4pm-hfMGKfium)a_z;nTrlq5uL#W!ru zn>LT34qtNpp_oZ?TtY1=2h?HrCB%KaskbLwiwA}2FFi&Tyd>AkUbh8q(Bs2Ro0SxK zD&X`ZhfCfF2}erKCz#1CbcFddfpC=n(?FqTDM|7mS3gM6+lN9ugsk)@2Oihrj_3i}IiddyZrWH*+1E zL18f-fzniRxmJI;8qeZi{M)+-Fm# z$gzSqPB16pTcUH^v@iTAQ92k zVC6foe|=1@Ljmix!Aed|v2GLR0P+iM{y&( zLa2iQp>hB<6->S_dLM{SMFXMCNfw~lcb|GUknP*@K*SG3t3D?*8|W-@l%&mpWRcn( zCBcWrO-M4}5O^TM2bjQKEmk>F@3)Lbyz*~fP z94E>BykNi+B>5s7-5l_KiSM5vWzUoE@ldo@sWaiApiU)yaO#8ZkR(Gh_4YQ%%aJ_> znqC4)61eC@iN*wum*mHM7aSv|$xryMk9jpo{+91LN$*I$CO|R?ik5p0^D-NpiP~B0H%ay zjB`F<;u5ycPc-}pU$ayaY?c#Tvr3tTQ%iRIppj*|%!<~>Eq7IxGJAJE+9{VQ0)tBx z+VyTwzCT$uD-b<%Y1g|!fv&eyQNIe5DL~?nG8+@axZ~SF1!iTm+><39MUQZ(@a&*# zVHuOj47#?JF{@Vm$U2m{QFV?o4^ZaUo-*fZc8s%jeNo0NjKgyj=Uhdn(%#?5gfTPX zNZn}i#bpXv7Ft}Mr+DWpboot8vExV9eYUI58x@$+!l{Yx_HZ@LmXhD}nwcMMAmPgLkZEJ>jUv55*jh$Sm< z4)YkHU6`sg+_w{R=0ZgcCkD;vo1rue**iMTH%J+NY?l`*_z6AA!ikOcKHu7%-_eK& z+Ogr+&z#*Ha+(51F#Y6~t_X%79NU6+r{UKSG9cRcu}zoQBFz7>LtA9%&_;$J_UX+J z+$@f%4f0H=3h9}&MDZ3Y?C}bRM&XR$4bxMr{P&fr!5+mRaDKk#cGTEyywak_8 z^8Tfe7&P9W1gUA$u8_K40~1551Q&IS6_(phTQk5f1(JFrH+@&gMQ2l`wHpQZas_ns zkSrgyt3&t13MiLmg>p2*a=<(F7h~i)U6kO?Qz-RHO0A7kuN>c=S_M3U??F8mYz;}#lM#um&Mh!5k|75&SA&F76%TH6B=?eH9@SdbVng&KVS!X&?0a0+0 z0)pX0g?+TsRaU{Qh7g8b&XlNo`Ayn9d8VW#cxOtTz7(k|$ZK%LHV9WV776#;eA6Tx zu(5aGibjr?sFOjazfAA?eAnpB6R@1| zNw2bZ&Gfod)|D&b8Lj>mGnjdB-<{&i74qPy*j{O3-^iz1=+% z=o*df8~5$;0?ecrbKvA0p!a6i?d-&f+u)i8=9MdKqqTk>r0c|JSL!xqS}MFo z>g}%;fOxCo+@_E}d3NK;l5+}_M)pP5OWT;aEQGIt{Uz*Gf56n-{;f)JUH>+v8E%o~ z2$yFYQ;{)C3e1w^$je=`Bz2gq#^L8+P0KlJF-d}Jcpm=Ae{!D(6JuPd+nL>t*%D?Y zXDY40=@Pqj4^ETdH-+`^o5D}zbg65$q}`TT$(stj)P5-AO-Hl|t%tQQyV4{ryn}mp zkDMkQO_v(xNWeIdE^)8zfmbaSe*T*yS;!oZ1o8qS21C$F1Lx80$*i!Awi}`ekdg@9Vu*&Y=I{U=V)b z2;^6SZ3tbBB^WzCi{LqTzVnISRGmAaETn-x7F@%5Pa z1aoZCMAM|nQ!G=bO;4FIa~7LAJ1u?ATq?=J%&bMvWiMXxeBuOG>PyVk)c