From c9b386c8debf4b886e63548b24bdddc8bae8befe Mon Sep 17 00:00:00 2001 From: jonnosan Date: Thu, 16 Apr 2009 23:40:10 +0000 Subject: [PATCH] git-svn-id: http://svn.code.sf.net/p/netboot65/code@115 93682198-c243-4bdb-bd91-e943c89aac3b --- client/inc/commonprint.i | 7 ++ client/inc/nb65_constants.i | 2 +- client/ip65/function_dispatcher.s | 22 +++- client/ip65/tftp.s | 14 ++- client/nb65/nb65_c64.s | 160 ++++++++++++++++++++++++--- client/test/test_cart_api.s | 72 ++++++++++-- doc/nb65_api_technical_reference.doc | Bin 106496 -> 107008 bytes 7 files changed, 244 insertions(+), 33 deletions(-) diff --git a/client/inc/commonprint.i b/client/inc/commonprint.i index fa9c2aa..4b8ecd6 100644 --- a/client/inc/commonprint.i +++ b/client/inc/commonprint.i @@ -13,6 +13,13 @@ .export print_decimal .export print_dotted_quad .export print_arp_cache + .export mac_address_msg + .export ip_address_msg + .export netmask_msg + .export gateway_msg + .export dns_server_msg + .export tftp_server_msg + .import arp_cache .importzp ac_size diff --git a/client/inc/nb65_constants.i b/client/inc/nb65_constants.i index 883d8c2..1e813e6 100644 --- a/client/inc/nb65_constants.i +++ b/client/inc/nb65_constants.i @@ -54,7 +54,7 @@ NB65_GET_LAST_ERROR EQU $FF ;no inputs, outputs A EQU error code (fr NB65_CFG_MAC EQU $00 ;6 byte MAC address NB65_CFG_IP EQU $06 ;4 byte local IP address (will be overwritten by DHCP) NB65_CFG_NETMASK EQU $0A ;4 byte local netmask (will be overwritten by DHCP) -NB65_CFG_GATEWAY EQU $0D ;4 byte local gateway (will be overwritten by DHCP) +NB65_CFG_GATEWAY EQU $0E ;4 byte local gateway (will be overwritten by DHCP) NB65_CFG_DNS_SERVER EQU $12 ;4 byte IP address of DNS server (will be overwritten by DHCP) NB65_CFG_DHCP_SERVER EQU $16 ;4 byte IP address of DHCP server (will only be set by DHCP initialisation) NB65_DRIVER_NAME EQU $1A ;2 byte pointer to name of driver diff --git a/client/ip65/function_dispatcher.s b/client/ip65/function_dispatcher.s index 3ba7216..9fc1df4 100644 --- a/client/ip65/function_dispatcher.s +++ b/client/ip65/function_dispatcher.s @@ -21,6 +21,7 @@ .import ip65_error .import tftp_clear_callbacks .import tftp_download +.import tftp_upload .import tftp_set_callback_vector .import dns_ip .import dns_resolve @@ -91,6 +92,14 @@ set_tftp_params: rts +set_tftp_callback_vector: + ldy #NB65_TFTP_POINTER+1 + lda (nb65_params),y + tax + dey + lda (nb65_params),y + jmp tftp_set_callback_vector + nb65_dispatcher: stax nb65_params stax old_ax @@ -293,15 +302,16 @@ irq_handler_installed: cpy #NB65_TFTP_CALLBACK_DOWNLOAD bne :+ jsr set_tftp_params - ldy #NB65_TFTP_POINTER+1 - lda (nb65_params),y - tax - dey - lda (nb65_params),y - jsr tftp_set_callback_vector + jsr set_tftp_callback_vector jmp tftp_download : + cpy #NB65_TFTP_CALLBACK_UPLOAD + bne :+ + jsr set_tftp_params + jsr set_tftp_callback_vector + jmp tftp_upload +: cpy #NB65_PRINT_ASCIIZ bne :+ diff --git a/client/ip65/tftp.s b/client/ip65/tftp.s index e39d9c3..7ae357e 100644 --- a/client/ip65/tftp.s +++ b/client/ip65/tftp.s @@ -419,7 +419,14 @@ tftp_in: @skip_first_2_bytes_in_calculating_copy_src: ldax #udp_inp+$0e @got_pointer_to_tftp_data: - + + stax copy_src + ldax #output_buffer+2 + stax copy_dest + ldax tftp_data_block_length + stax output_buffer + jsr copymem + ldax #output_buffer jsr tftp_callback_vector jsr send_ack @@ -477,6 +484,11 @@ tftp_in: ;copy to RAM ;assumes tftp_data_block_length has been set, and AX should point to start of data copy_tftp_block_to_ram: + clc + adc #02 ;skip the 2 byte length at start of buffer + bcc :+ + inx +: stax copy_src ldax tftp_current_memloc stax copy_dest diff --git a/client/nb65/nb65_c64.s b/client/nb65/nb65_c64.s index c3fe764..ae2d163 100644 --- a/client/nb65/nb65_c64.s +++ b/client/nb65/nb65_c64.s @@ -53,14 +53,30 @@ .import get_filtered_input .import filter_text .import filter_dns + .import filter_ip .import print_arp_cache + .import arp_calculate_gateway_mask + .import parse_dotted_quad + .import dotted_quad_value + .import cfg_ip + .import cfg_netmask + .import cfg_gateway + .import cfg_dns + .import cfg_tftp_server + .import print_dotted_quad .import print_hex .import print_ip_config .import ok_msg .import failed_msg .import init_msg + .import ip_address_msg + .import netmask_msg + .import gateway_msg + .import dns_server_msg + .import tftp_server_msg + .import print_a .import print_cr .import print @@ -101,7 +117,7 @@ call_downloaded_prg: .byte $01 ;NB65_API_VERSION .byte BANKSWITCH_SUPPORT ; jmp nb65_dispatcher ; NB65_DISPATCH_VECTOR : entry point for NB65 functions -jmp ip65_process ;NB65_PERIODIC_PROCESSING_VECTOR : routine to be periodically called to check for arrival of ethernet packects +jmp ip65_process ;NB65_PERIODIC_PROCESSING_VECTOR : routine to be periodically called to check for arrival of ethernet packets jmp timer_vbl_handler ;NB65_VBL_VECTOR : routine to be called during each vertical blank interrupt @@ -218,14 +234,114 @@ main_menu: jsr print ldax #config_menu_msg jsr print - ldax #current - jsr print - ldax #tftp_server - jsr print - ldax #cfg_tftp_server - jsr print_dotted_quad + jsr print_ip_config jsr print_cr - ldax #enter_new_tftp_server + + jsr get_key + cmp #KEYCODE_F1 + bne @not_ip + ldax #new + jsr print + ldax #ip_address_msg + jsr print + jsr print_cr + ldax #filter_ip + jsr get_filtered_input + bcs @no_ip_address_entered + jsr parse_dotted_quad + bcc @no_ip_resolve_error + jmp @change_config +@no_ip_resolve_error: + ldax #dotted_quad_value + stax copy_src + ldax #cfg_ip + stax copy_dest + ldax #4 + jsr copymem +@no_ip_address_entered: + jmp @change_config + +@not_ip: + cmp #KEYCODE_F2 + bne @not_netmask + ldax #new + jsr print + ldax #netmask_msg + jsr print + jsr print_cr + ldax #filter_ip + jsr get_filtered_input + bcs @no_netmask_entered + jsr parse_dotted_quad + bcc @no_netmask_resolve_error + jmp @change_config +@no_netmask_resolve_error: + ldax #dotted_quad_value + stax copy_src + ldax #cfg_netmask + stax copy_dest + ldax #4 + jsr copymem +@no_netmask_entered: + jmp @change_config + +@not_netmask: + cmp #KEYCODE_F3 + bne @not_gateway + ldax #new + jsr print + ldax #gateway_msg + jsr print + jsr print_cr + ldax #filter_ip + jsr get_filtered_input + bcs @no_gateway_entered + jsr parse_dotted_quad + bcc @no_gateway_resolve_error + jmp @change_config +@no_gateway_resolve_error: + ldax #dotted_quad_value + stax copy_src + ldax #cfg_gateway + stax copy_dest + ldax #4 + jsr copymem + jsr arp_calculate_gateway_mask ;we have modified our netmask, so we need to recalculate gw_test +@no_gateway_entered: + jmp @change_config + + +@not_gateway: + cmp #KEYCODE_F4 + bne @not_dns_server + ldax #new + jsr print + ldax #dns_server_msg + jsr print + jsr print_cr + ldax #filter_ip + jsr get_filtered_input + bcs @no_dns_server_entered + jsr parse_dotted_quad + bcc @no_dns_resolve_error + jmp @change_config +@no_dns_resolve_error: + ldax #dotted_quad_value + stax copy_src + ldax #cfg_dns + stax copy_dest + ldax #4 + jsr copymem +@no_dns_server_entered: + + jmp @change_config + +@not_dns_server: + cmp #KEYCODE_F5 + bne @not_tftp_server + ldax #new + jsr print + ldax #tftp_server_msg jsr print jsr print_cr ldax #filter_dns @@ -245,12 +361,22 @@ main_menu: ldax #4 jsr copymem @no_server_entered: + jmp @change_config + +@not_tftp_server: + +cmp #KEYCODE_F6 + bne @not_main_menu jmp main_menu +@not_main_menu: + jmp @get_key + @resolve_error: print_failed jsr wait_for_keypress - jmp main_menu + jsr @change_config + @tftp_boot: @@ -423,17 +549,23 @@ netboot65_msg: .byte "NETBOOT65 - C64 NETWORK BOOT CLIENT V0.4",13 .byte 0 main_menu_msg: +.byte 13," MAIN MENU",13,13 .byte "F1: TFTP BOOT F3: BASIC",13 .byte "F5: UTILITIES F7: CONFIG",13,13 .byte 0 util_menu_msg: +.byte 13," UTILITIES",13,13 .byte "F1: ARP TABLE",13 .byte " F7: MAIN MENU",13,13 .byte 0 config_menu_msg: -.byte "NETBOOT65 CONFIGURATION",13,0 +.byte 13," CONFIGURATION",13,13 +.byte "F1: IP ADDRESS F2: NETMASK",13 +.byte "F3: GATEWAY F4: DNS SERVER",13 +.byte "F5: TFTP SERVER F6: MAIN MENU",13,13 +.byte 0 downloading_msg: .asciiz "DOWNLOADING " @@ -453,13 +585,9 @@ error_code: current: .byte "CURRENT ",0 -enter_new_tftp_server: -.byte"ENTER " -new_tftp_server: - .byte "NEW " -tftp_server: -.byte "TFTP SERVER: ",0 +new: +.byte"NEW ",0 tftp_dir_filemask: .asciiz "*.PRG" diff --git a/client/test/test_cart_api.s b/client/test/test_cart_api.s index 8c93777..29c42e1 100644 --- a/client/test/test_cart_api.s +++ b/client/test/test_cart_api.s @@ -3,7 +3,9 @@ .define EQU = .include "../inc/nb65_constants.i" .endif - + +.include "../ip65/copymem.s" + ; load A/X macro .macro ldax arg .if (.match (.left (1, arg), #)) ; immediate mode @@ -137,7 +139,9 @@ init: call #NB65_PRINT_DOTTED_QUAD print_cr -;tftp callback test + + +;tftp send test lda #0 sta block_number lda #$FF @@ -148,7 +152,25 @@ init: bpl :- ldax #test_file stax nb65_param_buffer+NB65_TFTP_FILENAME - ldax #tftp_callback + ldax #tftp_upload_callback + stax nb65_param_buffer+NB65_TFTP_POINTER + ldax #nb65_param_buffer + call #NB65_TFTP_CALLBACK_UPLOAD + + +@download_test: +;tftp download callback test + lda #0 + sta block_number + lda #$FF + ldx #$03 +: + sta nb65_param_buffer,x ;set TFTP server as broadcast address + dex + bpl :- + ldax #test_file + stax nb65_param_buffer+NB65_TFTP_FILENAME + ldax #tftp_download_callback stax nb65_param_buffer+NB65_TFTP_POINTER ldax #nb65_param_buffer call #NB65_TFTP_CALLBACK_DOWNLOAD @@ -176,14 +198,41 @@ init: jmp @loop_forever -tftp_callback: +tftp_upload_callback: + stax copy_dest inc block_number + print #sending + print #block_no + lda block_number + jsr print_hex + print_cr + + lda block_number + asl + cmp #$10 + beq @last_block + clc + adc #$a0 + tax + lda #0 + stax copy_src + ldax #$200 + jsr copymem + ldax #$200 + rts +@last_block: + ldax #0 + rts + +tftp_download_callback: + inc block_number + print #received print #block_no lda block_number jsr print_hex print_cr rts - + udp_callback: ldax #nb65_param_buffer @@ -199,7 +248,8 @@ udp_callback: print_cr - print #recv_from + print #received + print #from ldax #nb65_param_buffer+NB65_REMOTE_IP call #NB65_PRINT_DOTTED_QUAD @@ -312,8 +362,12 @@ hexdigits: test_hostname: .byte "RETROHACKERS.COM",0 ;this should be an A record -recv_from: - .asciiz "RECEIVED FROM: " + received: + .asciiz "RECEIVED " + sending: + .asciiz "SENDING " + from: + .asciiz " FROM: " listening: .byte "LISTENING ON UDP PORT 64",13,0 @@ -361,7 +415,7 @@ reply_message_end: reply_message_length=reply_message_end-reply_message test_file: -.byte "BOOTA2.PG2",0 +.byte "TESTFILE.BIN",0 nb65_signature: .byte $4E,$42,$36,$35 ; "NB65" - API signature \ No newline at end of file diff --git a/doc/nb65_api_technical_reference.doc b/doc/nb65_api_technical_reference.doc index 17e63feef1d3bd02088e26510ef051c752a39cd7..d8a92668b872b9dbba345e97a3e83ef87d88938d 100644 GIT binary patch delta 7378 zcmciHdtgn+zQFOBgoswQ1VMsiOBK~hgT%WUkCu8yL_MmlkZ96Iq9l}ij(DAOE8L<> z?0BRtT18c{Llh-k+EA%TrLIyHf_StI2~t7s_qVcFn{&_k0s#$Ah(7*PcN)|H9T2Mmx`@`lwOqalkFLr*mJOjZYhK zon{U7^6H(CV4skXH1Vxz_SknO#!gALC&$Iw6QZXjSMRpZh?gT`?O`1{+9%9Rj-6&t zNJ@Mo*dF!IhZ*@@n+!CI?QNqIWBxcbq{fN%_(|1Qh)sMWInJI`otqS&5X(j46W_4M zB+W=nNQ#b$jWMdaHmmmy@2{Crsl5C$57fXYunnz4b znL0H#(Q0`7v_HF&lo)G|Pc-w%Q==27P2vnGPqI&ojjgUo`eyH79TlHAC54JoCdrX2 zq|x!oaz?bhRmhl*QjuBP)L45oCMWUXiXwGROqv|~%s-wJVo#dNsehbFRW;jZo*V3C zM6?bv>OLD}Z0y$D=o30^;*5A=)O$3oWL%Q$Q8a+M(2A5*>-iqA;ZvLn7mb9@vN%yH-a@BD5M|1jH2` zB^L-cD}L^6FT;}eTBttF8j@?|Ea_$3Tw?38%l(j2HsWc9a|P)JY{p*n=%Lgqtj3$H zLhJDWQ;hHuKcm-de`EI2-tc!GSlU~s%7|E1Y^qU>oF30Ge!*`j#Vy=M z({QB%@g!Q~X@nyiyRjcfa2y5r9;a{`UcITm_fVx?VdFtXd1*=MrE?{GmXubUs>sjH zKbU(k|6o2JxxDw7pVeE|wo|gX`CokY#iz>kGR7JS_3Qb@H4f~r8pnCJd0eTwM&;Dz z#^(z?`*)sk&YZbljoWCZfnlGj@S3swS8j;$ zdd4Ff6EG1eu2tX`xw~_6bBeRfcYbkx z@wVdn`Ndm4G2dBhGuNgsHJ^yOe%RMNFMgLX{lmWo_na}zoC{4RbK_@KyZMTrnZ7x+ zK!q)*&C)sPm~?8B;j`rj=hct=w9h88^iOTY4ir@vt)r-j>_uF`bu>17cH}wFe;lNJ zBmhm&41pL#Q43@8Brjt;9*&)EY+KX7r!MET(l1TYN~t$uXsNSkL!kDtq7}i360t?q z^=_ozMPwIaDa;^N7i|vGz7o$f_z6XjxV~*X`;nhBcuRo}su^Yp?`o{UdThg&{fvOa zPZ{kGdl`dvb0;le+Hak!Q)LKJu@LWL6|8gjuzd(;QG)C6eSse1Nle6Kyp4Hqq6kx8 zWDa94(qP~!N^lJ~@eA(0$h^}1nMY`b=ID;TNW+IXfdd1Wd$@*2sDgS)sQ|P_IPU#+ z{kO|M6`nkH@@pJBd1&t;^JDL!lb26sZ9BQKy71V^Z69SAi{@`jr3#wXcKbWV=Iv45 zbsNWx@u*N?o^#pS_QYzV(TA-Xst#Y$WeLN6NGKjReD*MNb}&62r>FJcjYf!}sGT_n zWc=mUcswgq`^8Wci+D(|BskM>)$rNZ#Tl`SHK1l}Bpg;Ko?tr!-TE_jo&OxS-E*~1 zH}*@YdZ7=PM2BeD9vZfY>|!j%a_l#Ja{o`m_J!(z{RGhptQF8Y1hNrYykyRni0S^u z_<832{{2@v8KtKhI#2BHpnY0!h{Qe!68{Lp=h$B7@tjbdDnro+ebHZvFdSpx#7^u& zA=5r)nfiBTAXv6zNgSb~-K7++#9z8c7U+Rw&moJSe%z;h5o1s^m= z0PJXs9vFa^F&g9XpLiEbu?)+RjFuo@#pko?Ft;3XRcj~hix@eBi=C6xt{A) z4=bP}37P~oi`cg_finYXfDO}4* zZh`id>@uW7!dwnl@iB@eD}ntT4>5{BxQdTcELn-=I7mGA!d3ho#gdgU=HM_Sl&<3Z z0_|@_wwT?exP`lTG>AzVK{p4;Of*jHukAcm&{?O-DAYW*iu}yMj3O3dG5!l{@BvPt z5EpP8Whlp8{EkXIfd3F?IBwu3e#I@^#vRb9fi*E7<9JZ~wee z!KP0(nNw<0=Dy68nd!?i(=v@-MLXR+`#m(8eiQ1gIzJ*L5*By5Re!M4cIvja)o0rq zjnHPWVLNr-@H|~%YeTjzoeYV2(I@$4vK{6GeqvB+?Qy^pT zuItdBD{SwQmEn+~$aL+#RAI{`E2FXw*|NLT`KZ`U+p@{;#y)&c%~x*g#^1ADbM&r} zzdhL5xa7LFStBVU=N#g+U8f~ZMkoshQOUlW?5kw^0o)iE`%uGnz1VsCSDW^?leY#> z2JQytGicrs9P^YBQR)x*lFc&L_S>a4ZLle3>T}3`PW^@H!UYZ}m^1Y z3a?@UCc?l5G#Sl9FlJ#T3Q&X!1dQQf1Mgu8GO-=a#_~nEBkx;xez|s;chOI0Zk_q5 z(Dgo<7gTt(u;|FOqJu?W?fR-{$7j4hUB701#Gan@b83X=!Gl?|4d zi{?u+OU?A!D_gk<_{P2Jd4*pTlIt=`;)mDJCCX`gz)*D|hF$RjE=E-kRaIGsi)1Cl z#gH&p;4q=I!aIQISfP`UElBUB{dNx2yL7N*uegFIL6hLDb_GO&FpJLrh_m1?j8k4~ zZcLuyRa5rtT6&&-L826f?&yipm%@jS<*t;em^(R-k7 zHBx^4%1;z|oRmiZS(9bTN}JCim;nkyXLLa~@Z(BxxID-ys#nBBMdMA(#(c+~fx3UH z{Aoe{8X&*ZJgdorm^^98 zlao9m$@7mq$jA*AH!dXS%jK#>>Y7`Pi7Q2Ybnb?i|d;yzEDVbt|3f zM}KG0wDUAeUK(y#=E)SI|EpbPRaF7(^-#Ajj?|tUQJ#09mWL{H<&w=@xhofXLdxz~ zM;?>hT~{vaJIUR%&Pz436;}3JDg50!)U4*dD_3sjDy=f}$OqPWC*{0{){#dg_sEqC zH7{RfmE}sAx>VhXtmCPY)mGLlbaUmBk4TxjD;H{3TFc6r&8}_b%*J?F=a~(yrk^7URLh7l-aB!%}VRLa#=LnQ9ewM z4%)%+MKT*>nS}>1j+t1OuU7_SCZ3PXPK?kt9ki1JM{-~s2OYxe99WNoR>Rjk(DA`= zeWX^&Myg&Gso!%fiPY^Jfg|*|c59yIVHjiEbN4|h-_3jI#2>PHUSqwg*E+pSYacqgweH~v!>3Z^=P%%$cJ5Rcv&P=Ce;ys)}IqqXo zFuxA?a*l#Qt3QSO(btc^?D^v?SCK2Zt6KktI_9PSKQ5j&~Bkv**WYAMMT`U9*jNov!5%D%ow`(sOk`S>bCg5LMyz=V-N#NC`FC z>84pY%x$t(>gFvM;3cPz&%T+UechfPpLS}4>^r7f*58jYBIf%Y52KVYHS>+7_VSph7@6cV&Ca=@X67mDs1w~t zW{M7OwRw?d#{tX+a2SMarYWO*80|5bL}<1IoDcq z>-^7s=I8h<>(TnSa(@~tb&^eu+x7MJviB&C?H+ktdz3zDLOt5}`)IXjsY_|R$@LC3 zQ$05M#Pm{Xmo{OZ;pPF)uwi?nmGYowHf#C(U)J|>T*>`PWl-_02bG#ce2MyjW~4_` zlkFMjaVgr|@{Dd<&Ccc&PHV-VKUud5QR;ws!mCXaxr{qM(^_%C(Ou;L`TWmf*M2$i zHZ~`BSL*L3Az?_$r5>&l-NLmdInM0FB|mj?-hrc%Fz07bGuQfCzM%ZTDngZtlQl7U@l=<3a2=1t$aQ48_p|!XC(8Z3sXdiy z?5)(`mb8HHuLP-JNE}vDL~FB8 zvpM}}fSD1IY~tI68^7-1CjG&CjA!($`#Q{hes)q=-zURnCC(0;JuPhdwC7)(9hSg$ zQexPogxLvUQ(l@iJ8Wj+#Kh<4Bu?tsEzEH~I?ks1JAwzCZ|TtE`gKY9go`|nLR>`& zLhhrXFxZGv{DeAqL@4D8Kl~+%fkDy|@dDn*Cfq;+2G4pKK-hqd_y}3pi=)^YqeiIO z>ayDE+Ula}Z>!IrIeFydna|IBdh*Q4Gnr@3@7%g^=lON3m#O&o5s1D*Iho zQ_81#QuN%5&h@8bw>a0WqBa|kw`O=YZhx0q{MJ0(W5v>zfp5^DCGN&a!`frINmw$> z#J?PDl9vRVol9(H*({sm*wQH7zM+&CqXbsNrLn7#?rwDBD3h@49@BN9zlnLr-;A9T zY{oBYZ+0%SnSAd6Ihkjs=OM?wcY5l_lwV`gGzgg(MXyV`Bvo6{k2F|mx?wd<)+PUY zVEQbJGks?HXZ2~Q+i3YVle5^%VwBnfa;{!}K&jnm$bg=1;!1-| z?EGN!>WaPyb{t&MSEtBfb2&r~ebs&VUe5@a_-1E{+wdTb^dj2S=-qX zo;sJgCv)evP1`bl?>WY3i{zHhv0461kaIm{c!3$Za_UrytYOP=C5eGaiC$Ax^CXF) zd>1qdOlsb0pgUH6uKm%z3smf#>Ho9TORA^;ehu83CU!-Hc_k^>k+N!mZY|}~4f|kq zg(n%6^h?%nnAYhLCVq{-BO&d59V)xV!Rm~CY+r{YKQh)4nqH`bCHYaXIzW;yiN6wS zLerxi@oRSJa4Y#kN$_)6gx9eYtC11s2w!_ir$`61#vlyF5J;WLka{m-8m40gUW0Y6 zx7e2REkhdQyz8+6o3I(%upOD$i(LGKUvO_LiSgBsPA2Eo< zKs<)GuoQ2@j&<0At+-WJQ&(ADS@hl4*e7|$~x?GmMyPeDa<~z3T zSidH9>6)c)DCdW+f6AxMTyWfLPCJ^KryPGV53g@+UReKfxHYzV&mEy6Gr0Z@kTy#D zvM|GR&j@mK%P7!+5=wLTBL@|57jL9k!b;Sj4rIbA-bAs4ZP5{3A^CCEHr+Blaa`LH zqyw##jiX4?w%R(x(QO-NkT4xpOl%WE>OhCq2$F-e7y6v6$mOM*7 zw_!I9;4o&Ghjs-yVs{UsshiCB| za`72H$5qr;logd-Mp4O^ zsd9dJv;W2A4c<%pAw4rYc&JDVaL~$zG5N*mTn5?DBFHeiW($ zf=Ey>LJ^Lc6n8S+w)mT28|OG*q9-yVbIS}E2LmpPmq5D!*>(IF-{=2Xy8T>9rn^o*QQZ_Wz;v zhaz==)qxW^L^|;hhjDFR1nJg?2=DCA&_}tia33CovxO!$JJ`gZ4mK%^Bb@UIF2J11 z@pc?L60K8Y7?i_@!~GBfIZbcGp&$BVA`+H$rnzX2 zV6;LfbQvViM>e7{2xBk_lkp-}VinfnePmz*KEyFppb|f$3b#=Y|G`SVf!d1Fioy$L z3(g)da4u(EtAgWa59hd+U{?D--tAoW@7T0s{d+6le$O?&&9QTlrc2&KF5g@^rZ(ECz)t~yO8KA z`jVnVLTUFDNZXIPit;HsN+`|!5(TJq6%|D4kUwj^7cFmu|Mb2i{`4+wlU`2GE*oF^Kh^zPsY6wFH9`Hf~_@N;>pa*`zP255~)KC%& zFEl_PTB9qvBWfr^Z7&<2pusSjiw+1GPV>51{fIr|NHVHn@*b++;BO2Bgn8{zum+G<kr(f<)scog z`tA#Sg^-c1Yro0aL+GnebVefk<=TI%_D>|7jHwu~!M{%1{k5*P4WK6yST7xvHP}mM>J%9huVFE)3N>tB$1QZF z!c%w}<1hiqSOM##AG3V{a&phn^Z{%bgak~$3z&`>SdWj;b__Q`yD>btu^E?90iP#$ z7~^fM!F$+_gXs7qF9AG?!I+FUumpRs7oX!OigBy%R!wDjF|$+ma`|G>*F`m_Z`I`2 zoXCqiQIng^lDTL5p3UnvuSrWyTa%mmb?V!x3_E_LT$%R+KT_nc403Bd;Bk7E*sI0A zOj!L&&q4Dl&93^6-Fry}Y9OtbR!ifyx*H^okc*UyDgpn-HGTX&CV3`Ph2Lep>Z2EG z+wTX_g6mo9=jw~v{*j*!)E=1)bfFQp*?%39+)EkU?4y7<(IeoHk031@}o(9 z6v>aCj5yb1yn?mNqh+EjvtgO?%G_3_Tr#hcNt4WiWNIUG7@4TZj6oEIRyY68B$8dd*eWP7Bv+uX-IK3e2_Hcc#PH9hjmhjc(e64&mJh0rCu}K@Zy36Y8 zL)q)2zI)TJxi3eYer>I{kGkxRnk|y(iWM!EsL&madR?NcR+%#@vZ9pvQuw`fs5AP( z9i=*>YwqZ@Gb*+!E|7X9Ry}7_YSnW_WmY|BRBqKMcx>@F%OHv#`Rd=!`0@ z$l0c!ts_f%lZz^MH0no*s;#J0q8jT+XLQ{iopwey+)=Z#-lI9R)~Z-073-{u&gd7b zqBFW_RVpsyEqH-!f{e8C_^cNn(>5Lc z83#_}z&RY0k7N!E=Af-;<2=y5_Hmu(wa)W>Z~O95`W^c}N9j)XR-^UUHc6d%dE++s zoHt{#FXM3#p5eYr0OQKObF^-6|8%r&X)hVA`=`9uiOCE4^$#YZk%bZ}NkvcP^#={O54cDEAN74y+pHT;=ce5CrLS%06V13e!4Wzp%yvhT3E+nawfY^2mV9v+QUNA}U)#w%F=_r0=5KCL}f zW#g=#JlM6}Zp@U@;@H2S