From 93c959cf505ddfb1d8f1c9b05d33634bc0d7ad27 Mon Sep 17 00:00:00 2001 From: Luca Berneking Date: Sat, 20 May 2023 15:50:14 +0200 Subject: [PATCH] initial release --- .gitignore | 5 + Makefile | 47 +++ README.md | 33 ++ assets/controller_neptune.vdf | 664 +++++++++++++++++++++++++++++ assets/game_actions_480.vdf | 19 + assets/libsteam_api.so | Bin 0 -> 391056 bytes assets/steam_appid.txt.off | 1 + go.mod | 55 +++ go.sum | 716 +++++++++++++++++++++++++++++++ main.go | 47 +++ pkg/cmd/daemon.go | 40 ++ pkg/cmd/gui.go | 51 +++ pkg/config/version.go | 3 + pkg/daemon/client.go | 66 +++ pkg/daemon/server.go | 136 ++++++ pkg/deck/info.go | 9 + pkg/gui/gui.go | 94 +++++ pkg/gui/input_window.go | 363 ++++++++++++++++ pkg/gui/keyboard.go | 151 +++++++ pkg/gui/keyboard_layout.go | 528 +++++++++++++++++++++++ pkg/hid/device.go | 35 ++ pkg/hid/joystick.go | 122 ++++++ pkg/hid/keyboard.go | 94 +++++ pkg/hid/keyboard_keys.go | 129 ++++++ pkg/hid/mouse.go | 75 ++++ pkg/ipc/daemon.pb.go | 772 ++++++++++++++++++++++++++++++++++ pkg/ipc/proto/daemon.proto | 39 ++ pkg/ipc/proto/generate.go | 3 + pkg/joystick/joystick.go | 77 ++++ pkg/joystick/linux.go | 45 ++ pkg/service/deck.go | 126 ++++++ pkg/service/joystick.go | 43 ++ pkg/setup/install.go | 54 +++ pkg/setup/modprobe.go | 10 + pkg/setup/usb.go | 174 ++++++++ pkg/steamworks/steamworks.go | 130 ++++++ pkg/usbgadget/config.go | 114 +++++ pkg/usbgadget/function.go | 40 ++ pkg/usbgadget/function_ecm.go | 35 ++ pkg/usbgadget/function_hid.go | 83 ++++ pkg/usbgadget/gadget.go | 141 +++++++ pkg/usbgadget/info.go | 14 + pkg/usbgadget/utils.go | 55 +++ pkg/util/file.go | 28 ++ pkg/util/root.go | 62 +++ 45 files changed, 5528 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100755 assets/controller_neptune.vdf create mode 100644 assets/game_actions_480.vdf create mode 100644 assets/libsteam_api.so create mode 100644 assets/steam_appid.txt.off create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/cmd/daemon.go create mode 100644 pkg/cmd/gui.go create mode 100644 pkg/config/version.go create mode 100644 pkg/daemon/client.go create mode 100644 pkg/daemon/server.go create mode 100644 pkg/deck/info.go create mode 100644 pkg/gui/gui.go create mode 100644 pkg/gui/input_window.go create mode 100644 pkg/gui/keyboard.go create mode 100644 pkg/gui/keyboard_layout.go create mode 100644 pkg/hid/device.go create mode 100644 pkg/hid/joystick.go create mode 100644 pkg/hid/keyboard.go create mode 100644 pkg/hid/keyboard_keys.go create mode 100644 pkg/hid/mouse.go create mode 100644 pkg/ipc/daemon.pb.go create mode 100644 pkg/ipc/proto/daemon.proto create mode 100644 pkg/ipc/proto/generate.go create mode 100644 pkg/joystick/joystick.go create mode 100644 pkg/joystick/linux.go create mode 100644 pkg/service/deck.go create mode 100644 pkg/service/joystick.go create mode 100644 pkg/setup/install.go create mode 100644 pkg/setup/modprobe.go create mode 100644 pkg/setup/usb.go create mode 100644 pkg/steamworks/steamworks.go create mode 100644 pkg/usbgadget/config.go create mode 100644 pkg/usbgadget/function.go create mode 100644 pkg/usbgadget/function_ecm.go create mode 100644 pkg/usbgadget/function_hid.go create mode 100644 pkg/usbgadget/gadget.go create mode 100644 pkg/usbgadget/info.go create mode 100644 pkg/usbgadget/utils.go create mode 100644 pkg/util/file.go create mode 100644 pkg/util/root.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0da842 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +testing +build +deckjoy +deckjoy.zip \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ed4fee9 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ + +REMOTE_TARGET := deck@192.168.1.156 +REMOTE_TARGET_ROOT := root@192.168.1.156 + +.PHONY: all +all: zip upload cleanup-remote run-remote-steam + +.PHONY: daemon +daemon: build upload cleanup-remote daemon-remote + +.PHONY: build +build: + go build -ldflags "-X 'github.com/lucaber/deckjoy/pkg/config.Version=0.0.1'" -o deckjoy main.go + +.PHONY: zip +zip: build + -rm deckjoy.zip + mkdir -p build + cp deckjoy build + cp assets/* build + cp README.md build + cd build && zip ../deckjoy.zip * + rm -r build + +.PHONY: upload +upload: + rsync deckjoy.zip $(REMOTE_TARGET):deckjoy.zip + ssh $(REMOTE_TARGET) unzip -u -o deckjoy.zip -d deckjoy + rsync deckjoy $(REMOTE_TARGET_ROOT):deckjoy + +.PHONY: run-remote-steam +run-remote-steam: + ssh $(REMOTE_TARGET) steam steam://rungameid/15557043747182084096 + +.PHONY: run-remote +run-remote: + ssh $(REMOTE_TARGET) ./deckjoy gui + +.PHONY: daemon-remote +daemon-remote: + ssh $(REMOTE_TARGET_ROOT) ./deckjoy daemon + +.PHONY: cleanup-remote +cleanup-remote: + -ssh $(REMOTE_TARGET_ROOT) killall deckjoy + sleep 1 + -ssh $(REMOTE_TARGET_ROOT) ./deckjoy cleanup diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed7b412 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# DeckJoy +Connect your Steam Deck as USB GameController/Keyboard/Mouse to your PC. No additional software or drivers required. + +## Installation +### Requirements +- Password of the `deck` linux user + - Set a password can be set with the `passwd` command in a terminal +- Steam Deck in USB DRD Mode + - Shutdown your Steam Deck + - Hold `Volume UP` and press the `Power` button + - Navigate to `Setup Utilities` using the D-Pad and confirm with A + - Navigate to `Advanced` -> `USB Configuration` -> `USB Dual Role Device` using the D-Pad + - Change the setting to `DRD` + - Navigate to `Exit` -> `Exit Saving Changes` -> `Yes` + +### Installation +- Switch to Desktop Mode +- Download and unpack the latest deckjoy.zip +- Add deckjoy as a Non-Steam Game +- Switch back to Gaming Mode +- Start deckjoy + +### Steam Input + +If Steam Input is not configured automatically I recommend to configure: + +- Trackpads + - Right Trackpad Behaviour: As Mouse + - Click: Left Mouse Click +- Action Sets + - Default + - Add Always-On Command + - Command: System -> Touchscreen Native Support diff --git a/assets/controller_neptune.vdf b/assets/controller_neptune.vdf new file mode 100755 index 0000000..717de90 --- /dev/null +++ b/assets/controller_neptune.vdf @@ -0,0 +1,664 @@ +"controller_mappings" +{ + "version" "3" + "revision" "22" + "title" "#Title" + "description" "#Description" + "creator" "76561198046212892" + "progenitor" "" + "url" "autosave:///home/deck/.local/share/Steam/steamapps/common/Steam Controller Configs/85947164/config/deckjoy/controller_neptune.vdf" + "export_type" "personal_cloud" + "controller_type" "controller_neptune" + "controller_caps" "23117823" + "major_revision" "0" + "minor_revision" "0" + "Timestamp" "-986673008" + "actions" + { + "Touch" + { + "title" "Touch" + "legacy_set" "1" + } + "Default" + { + "title" "Default" + "legacy_set" "1" + } + } + "action_layers" + { + } + "localization" + { + "english" + { + "title" "DeckJoy" + "description" "DeckJoy with multitouch keyboard" + } + } + "group" + { + "id" "0" + "mode" "four_buttons" + "name" "" + "description" "" + "inputs" + { + "button_a" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button A, , " + } + } + } + "disabled_activators" + { + } + } + "button_b" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button B, , " + } + } + } + "disabled_activators" + { + } + } + "button_x" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button X, , " + } + } + } + "disabled_activators" + { + } + } + "button_y" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button Y, , " + } + } + } + "disabled_activators" + { + } + } + } + } + "group" + { + "id" "1" + "mode" "dpad" + "name" "" + "description" "" + "inputs" + { + "dpad_north" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button dpad_up, , " + } + "settings" + { + "haptic_intensity" "1" + } + } + } + "disabled_activators" + { + } + } + "dpad_south" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button dpad_down, , " + } + "settings" + { + "haptic_intensity" "1" + } + } + } + "disabled_activators" + { + } + } + "dpad_east" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button dpad_right, , " + } + "settings" + { + "haptic_intensity" "1" + } + } + } + "disabled_activators" + { + } + } + "dpad_west" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button dpad_left, , " + } + "settings" + { + "haptic_intensity" "1" + } + } + } + "disabled_activators" + { + } + } + } + } + "group" + { + "id" "3" + "mode" "joystick_move" + "name" "" + "description" "" + "inputs" + { + "click" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button JOYSTICK_LEFT, , " + } + "settings" + { + "haptic_intensity" "2" + } + } + } + "disabled_activators" + { + } + } + } + "settings" + { + "deadzone_inner_radius" "7199" + } + } + "group" + { + "id" "4" + "mode" "trigger" + "name" "" + "description" "" + "inputs" + { + } + "settings" + { + "output_trigger" "1" + } + } + "group" + { + "id" "5" + "mode" "trigger" + "name" "" + "description" "" + "inputs" + { + } + "settings" + { + "output_trigger" "2" + } + } + "group" + { + "id" "8" + "mode" "joystick_move" + "name" "" + "description" "" + "inputs" + { + "click" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button JOYSTICK_RIGHT, , " + } + } + } + "disabled_activators" + { + } + } + } + } + "group" + { + "id" "9" + "mode" "dpad" + "name" "" + "description" "" + "inputs" + { + "dpad_north" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button DPAD_UP, , " + } + "settings" + { + "haptic_intensity" "1" + } + } + } + "disabled_activators" + { + } + } + "dpad_south" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button DPAD_DOWN, , " + } + "settings" + { + "haptic_intensity" "1" + } + } + } + "disabled_activators" + { + } + } + "dpad_east" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button DPAD_RIGHT, , " + } + "settings" + { + "haptic_intensity" "1" + } + } + } + "disabled_activators" + { + } + } + "dpad_west" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button DPAD_LEFT, , " + } + "settings" + { + "haptic_intensity" "1" + } + } + } + "disabled_activators" + { + } + } + } + "settings" + { + "requires_click" "0" + "haptic_intensity_override" "0" + } + } + "group" + { + "id" "12" + "mode" "absolute_mouse" + "name" "" + "description" "" + "inputs" + { + "click" + { + "activators" + { + "Soft_Press" + { + "bindings" + { + "binding" "mouse_button LEFT, , " + } + } + } + "disabled_activators" + { + } + } + } + } + "group" + { + "id" "7" + "mode" "switches" + "name" "" + "description" "" + "inputs" + { + "button_escape" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button start, , " + } + } + } + "disabled_activators" + { + } + } + "button_menu" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button select, , " + } + } + } + "disabled_activators" + { + } + } + "left_bumper" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button shoulder_left, , " + } + } + } + "disabled_activators" + { + } + } + "right_bumper" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button shoulder_right, , " + } + } + } + "disabled_activators" + { + } + } + "button_capture" + { + "activators" + { + "release" + { + "bindings" + { + "binding" "controller_action system_key_1, , " + } + } + } + "disabled_activators" + { + } + } + "always_on_action" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "controller_action ts_lc, , " + } + } + } + "disabled_activators" + { + } + } + } + } + "group" + { + "id" "18" + "mode" "switches" + "name" "" + "description" "" + "inputs" + { + "button_escape" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button start, , " + } + } + } + "disabled_activators" + { + } + } + "button_menu" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button select, , " + } + } + } + "disabled_activators" + { + } + } + "left_bumper" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button shoulder_left, , " + } + } + } + "disabled_activators" + { + } + } + "right_bumper" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "xinput_button shoulder_right, , " + } + } + } + "disabled_activators" + { + } + } + "button_capture" + { + "activators" + { + "release" + { + "bindings" + { + "binding" "controller_action system_key_1, , " + } + } + } + "disabled_activators" + { + } + } + "always_on_action" + { + "activators" + { + "Full_Press" + { + "bindings" + { + "binding" "controller_action ts_n, , " + } + } + } + "disabled_activators" + { + } + } + } + } + "preset" + { + "id" "0" + "name" "Default" + "group_source_bindings" + { + "7" "switch active" + "0" "button_diamond active" + "1" "left_trackpad active" + "12" "right_trackpad active" + "3" "joystick active" + "4" "left_trigger active" + "5" "right_trigger active" + "8" "right_joystick active" + "9" "dpad active" + } + } + "preset" + { + "id" "1" + "name" "Touch" + "group_source_bindings" + { + "18" "switch active" + "0" "button_diamond active" + "1" "left_trackpad active" + "12" "right_trackpad active" + "3" "joystick active" + "4" "left_trigger active" + "5" "right_trigger active" + "8" "right_joystick active" + "9" "dpad active" + } + } + "settings" + { + "left_trackpad_mode" "0" + "right_trackpad_mode" "0" + } +} diff --git a/assets/game_actions_480.vdf b/assets/game_actions_480.vdf new file mode 100644 index 0000000..cf1831d --- /dev/null +++ b/assets/game_actions_480.vdf @@ -0,0 +1,19 @@ +"Action Manifest" +{ + "configurations" + { + "controller_neptune" + { + "0" + { + "path" "controller_neptune.vdf" + } + } + } + "actions" + { + } + "localization" + { + } +} diff --git a/assets/libsteam_api.so b/assets/libsteam_api.so new file mode 100644 index 0000000000000000000000000000000000000000..4217a2e2df932ab85577afc0b29a26f99578b0eb GIT binary patch literal 391056 zcmbq+34Bvk_Wx@sSWq-9A}VTFML-CPhnWh=?dIbzH$M zxPpUPw-NW?@tL4_bNpGW%<-;g%<&!z{FEv|zlRp?4+o9|YSEAJiwK|Gi|`!oX@5G8 z@V$1z4`li|4->wN1I=-a-|Io#URzkE31T|c0s$FUdl_befN(nj=fFZ%JG?kD%q9Iq4od34eq zJ%suHMhG-d{aD9*x|xr}|4Xl5lxE*dd6sZ_JzQR?|IT&9->#JRo;t$YxLzdxIVTam za}e>D_){NXKCFK<6aCm@KH(jEWXiMVal$8^gqQWZ{RzT*$_PJ&`3zc3_})>O`EK8x z@IBv>eq^~SstK>{o|*4M<%Ca`X5xQZMtINunfT%%gtvX4$$#^GgtyNidZ~xV5rhx- z&BQyoTpE{aPcGL6#yc24iSg@eiBEjbOnG)PKB3^J-a+)eQ;2>t^KqO?`0l}kha1t4 zjqQvdktye?s|asrJ;RLkOfknq~TO!=#BBYa{q z;iW#?w-CN(S|?*$hSzDw~x?Udp~~> z>w)c1(r;ipjI$l0o6wK%4`F&OQ_im+CVUsS-;_PDojVo&TUgJ%ik|;9j`;K_a=v#m z;a!R!u3~+LS)Z~Wtl32L&Z(Jl9=nn7amC*5c!=paE|PY>7Z{3m;rTAJU3VolZKO6# z^E_Ij>t#HtN9%)%5cHitXVQCX2(Kyq_#v%?ckM%RO8xh;-%cv+bltv0-=);=KGO+r z>yugD-^LQYQ_-jY84XjHgSD;Ck^C&aR{03g_i()r<{M)VB7Cpn-*&M5*cCtaIqN^J z=>Ne|;*1WeRw?SQ^ud?))8Lgeo*o`j_WI~)YlES61|=K!3nHSxGTs%KyzI~@=N`f z!%^#gSkc2RLB{V#^2mJeYbJd6uuS|3?S$`A`n40eUkG!*Ank2i2hoR>ID8oQcb!W6 zJ$x0*qxkbZn7>Qme+t)cTq)PX+>dl~KO*D0@6TZQ75`Ar?ZuIm>vrEzQCv5)-#r{uaKi{j^|B$1Je?sY(UvLq=ONrn7*Rz}xNS`oE z{W$3!!aJrDUd+*pW8fOXCr1-r+SOlgCA^F6e*}?h_g=yL4<>r)2gW_c^lrk-es`lFyNl~x#^HZqJ`N>5DI3l5D0cV~^Y2vnmveh} zaC;xcdKl^>J`Tmdt+|Wvy$+I7#wP^R|)Zv_Va6q@NvZt zOyzcw^?Oe9gi@3dbxV^w#>&N@sMBKHf7Qe5L{vYm6 zKU{wxK1uGcB_G$FgzscKk#a6(|LIZu=V1pDeL}IHFOi*)v+E72mm!?*E$o+^%6NG? z^LHuy-&;<6oJ#*VW-Q~CIPJOlgbyom<5leUlI-{PVtJa^5Pc8(VJYW7jv{=g;{PMp za=vWOvR;ny6TVC7XUE;g{MipkJsd2Ht%#m*nUf#{P;y=+6Z>i+6kl5;oC*ABr1zV}_i*D?MvbW8$o=XNLU`96++ zx)uBX;!i}MIFjr~_RBXvOn8mQv*_mZ;{*1ej{i`;lK-rQL~m2#s9D?|ySY6|Jr8Gp z)y4iw@_FqV=A*R7(QH31#eNRIoas4^lKlI!ecBcKY~PQ|rPRBFY|1I3UoM(`J z?xkXD`*Xh;SNhGpMiKuW9{)-^tUs9LD+u4k<&u2bSU8la%}us)@JhCW7k=dmOw%tk-{ z$?<6U&r?Z`CF4F(!tj})_B0f%KUZ~&wgzr?=L1u7&*QxY(=bppzEAjsi z8wua7ls9=H;S=nCP;&kFbA<8Add*eG6F#YoBlbCy@SRFOIHiK{J<53Y*C^o~%KSs~ z*~~|Ymv*w9bg`XCIcIa6lTgNY9}HtYN?h>;`&AqJ2WeL`IWCPeUe?!ZET^62l=g5C z^LHuseAXPw*E4|XSMtB;7{c2XJ1kyLcuncos#+M&b}somcPHVyI9?sh^)-a+H>}ie znB#w28OZ~;qaWX~KkQWeVaI*M-=^5xZq0=6RK{7SazEb9>z-0?i7uiKD|{MQpWTXG zO}vumJxYJqks!QNDc5e?j*|1JTnA8d)s8-q=xvHWj6XzpJI~*u`t)M~w_lIae)qbR z=v|6GoHLT}38kI(-^BS&C3&PCMm|jVPR>{2kGzcVT?+r_xnJmHzq%L8xgYm;arQsb z9=^Su_#~Bit?k?%dzAKg_sK->RK{6vy9sX_O7cs;{l+7N?>Q>dem3xUM_WMnA6d=_ z(`!sG`@8B{#7AR2%XV}e+fSEbKfAHtb}8-UW;A@^pB>8h)XC+tbGan{x7bcPPbN8k zVL6MrJ@zW`-cq((k7Bn6bNxEFex?39*l*hud-#mUeICV+4S1B~@8)sqIF@HD%c(8Q z)Td`W(Z{(Tm*u+7Mfk)(qL=dccpT?Z%5^fw?QvzEXZk6`C#=}RyQM6rGH-d?euTGi zd?@`-i0doN^(Eu34--W1QQGefJnraJ`s*R=_j(mS^AX2~PS%6uAAZJsonq}tly8^f z2kNU_B`D@bl*rzDJ2q-u4o{m)nu7mx{ZXUYTdGak+Yx@lL;K zMBl}6uk;&da=#N+`nA_55xrJK_8{AD*9^jUD&y9VE@63;@z+3Zj|py%lFxs+UYttY zbsP*pw8td-E7>p4;Bsk7JpbZUqE9IC{C@0zx|Q*WRz~zrC4N{mkMJHP4!H>e3wi8H zzjp48gzr`Q+09(AuN>c@pc z2=7$JK_ghtE+yW+g7fWF>M_(td=koh=_GD<-HLuT>`(NaiXV7s0pWXi{!O;`qqtly zrCi?}$n-paDE-(;+#lH#J-bgNdb{GsJg7KfSBV`(x?YZC{T#52@V%^m*&Y|NzYQz? z_EaR-?elHo<77TX93MKBcJ$CuL?2iBm+%>c_wf9@jIZ`)`87rUXSg4CDt>tW-NeVn za!P&v%=3N@#ZR7cG0{5}KlZm%nGgFD$!Fn2mQ$m4A@SoG?^5u4JwWt5T#tvae&%pL z*vsQD8K=E9is)U+{N#4#-^u)?-*|EZ(Z?0Pl(s$E92HBxIb7MsD&?~`57kHt3&KR8NQY33(ZhJMxIQ3H12nXGk*1Tgzr@5 z6~C(|eAiwir?jgz_Yyuao$%71|1yg39>xC0aeR_g?Ee;yE9^>KaVq=CE@ho%U)E2(M+l!hlFBQ`HE+!(yq)Ed z^*Hey!uKflfBHj&mk;!znEDako%t(q#SZR|G^JnuJI|}w`jb3T56>P?e7Y5TTOT2O zr!wxY=6=M*{m5LF=L*(;Qqlid_A{E|pBHx!eqy-yK+Vj0!r4#pRu;6iUb%J^$I`>QVYlW1=G z@dd{ry-FN1>Q3SlSH>N`p&<+YNu~dNg#AE*+lADBl-sFIX{Ud`miTy-cC_Y6!gnpA za{a<`PCS(Gc4eG0@Gz!V>V0!7;p0laj;+jx$I-Gqez=bC&T*OT;$n^u;~XFE%koU% zc+#%KlZ~T^Pp9I?mazTA75kadK=d|cAJMi(!Y7pVyOza-ciA%K{AB{+o$M!N|8-Ou z(<}YpfF#Y&CvPYJ4|COzBPSDmT+#E0nS{5OP`=V%wR69dIE?UUR{C+q=|tbDjLTNp z2_II*P0wK@Dj*+=|c z%wOv55st?+B_8{^lIT53ztFdV@S4&dE0|A@qKCiVL-b*eXQV!>?_)k3|4Vxdqq*t* z8t)rFgKxB+OL&hxQ_oNA!+exDd<)aZ75ch!h`v{87ehGTM40&h$a*gE5WRgU;iX+A z&mep!+cU(_kLT_sd{QazbncIw9^&&k(-pziXOYs9OtY@2|XV0U= z$HDVj2=nx#GC+8{vd;d&R>F62|2~N6KVd#S%m-rY#~<35UeS*>kMK^#-g@D;Mf>gj zg8HM;%;y#a5(01M`65}~<3}-`;{jP;ZQP$amHxDZ{YcjGT6g}K< zJ<<0l@!04l!YA3k$#}b)=YL#EoPXK@MDJAM`95*N+t?1J-`l^I@UA_{P9*;M6@*VH z{po*M9=l=>N3tC{6+3)`_25zFfkTbNKhEWn`OdhX^HuzC!xfBI#?c??{So#7a$F?q z_k+WUKB?5#r$Y(f!|h$xd+F7L_i($Ge)Tc7pYDH?{rrdRXEC?ygrd(<_N(F3NT1TL z{%|?*53~Hzt`C&)+m-d)<86fR;qpp9dBQ=2*OWN?fz^a} zjLvM=cX0bka{H3@zsD@1?^fDN6^}!ExPO;+wcvK5_e>%Fleu1oa=VBt?czD+?@;1| zH)Q)_|GXdbxynQQ<4S*Y#|4B>j>@#RE9bL3JYJCcpW-Eam%`_AC*kc%|2ymi!gq5V zBKfSsKuPZ}*dI##c^e2HR{Z}y?B6_!e>@x~*;C zII3G2*PP1nj6)gczV9Rcam8=%-o^PU_VCGtgzr-9=guby-?@YY`GxE2H1>NvETG&U z_&EF9ZpGgw4<#rp45^nY9l(c3tV zg&FI|`!T}D75=YmCVY=FuJkq$zFV|TU%*E^Uts6BVif1QxQ@%q^7Li=zc3IMe7bmi1b3|;vv?elP{t9-)0tkG zZ~63Q!Y6tBEB*YMHH6p3P`RX>6S;V&r{X6c;<(Yn?NRFa zgPVwthvkv^?hOLHyu2PH@h`Ccd)hMfzX$8brHmir?B6;$zB-QOOim~M&OXFP%D*N_ z@kyN5J7jy@x{d0~!_R}*nU9a<>{jGF0Rjv8HDw&S_Y}hS6lLmp1W0vyW&R|{c)Nl> zyqV||ieEbJTEaUNKhw&7pqu9hz*avVnZWcMA4)y^B}Diz&r?bKWVX+4woj>_AK6a2 z6gvs6Cq8zi|GFIsMR`5SJjP>OzrBIX`u&RSyoc>v+QTvD5+4_rOX43smGBzd{{SxU zFgw#L@ywfCF1s?mxQF}q9;JVOljZCVlAJr3|L&I&|AaDbZDM~LSN!eHbBNxd$lt_% zuSfBFXB|THJ#6Q)UhaaU5c&+iSENfK-a96anLN(&C~?IYjyE*MOFlo zhL!m3|G1yh*#ArY_xBT@gff5f*olO9D)R%!dI;}fJxjkghx^UAV$TEaCHlA$-~POY z`73_x#?uJzQvBf!4>0{)+Y&{tjn~+F*StmRptTC=YG?}{bpP!AIE!KFWLuG zFQtUmzH%{t?!V-?z|HYzLWw`CpC-Tbsj}|#!)BI8>3`Sy2=7t+!>d~euPJ(~`V-;p zye>JK<=nyXe76$6Y1|$=m44@Y)f(KAP}7 zO5FGbj|bxlpC%sfxRieUI&K$Ti!=4Vd=m5Lenk3#e;>y3D|+bIM0l4{-mkbGd$}HE zd%TJLNw?xpu4OxMDt6*Lg!p$We)5A6ERQlS_>{}lc?y;5bFSaX9QQhvxOXPovrVz* zl4q!$+LhFbcPGbE#6#egki5lG=6#OY1kM*#g zrCnXxK=ckCCrbRZDB+VzJh?N@{1toHj)Z!?pHjbr_%5X%*}jVK z9wm-Cvz+ObcHK3b@HVAgAF!10NnZDq`JQzX;hoC3bsgJzui{TWV!!9$aiX;Im+ZtR zuEZf7cM#sr^FL@-`mu`bs(Ui&e-h)TTuk&1W!~u))=#&hpJkU2eV0OiG`9<<(k^Nc z@CbYBQtbTO>4bOnA$erEig`Y&Q|Z@^okH}zihp>P?Z21(z;G_tAg-70{={Fl$GKcD z-AcWbT}b@HiogAZ_2%IADD`PGvm4-obh( z&Xn`)qY0l>`nAU|BYck%H$E)mkQ0f|FPyLac%n}zsk8G)6XKjU0IJFu#WJ(Y*(_q+|$YQiabfSbC)vD`FfD) z6+1i-h9Ts%^Sr@e*3YF=3E#>3mv(r|G{W~P_{T3Nyp!kmq?{A)VgAba=s#h?ce6aw zk9~L%;X8S}DfLr+FXI(H4{arU{AkkuSeE~xam=6nGt5js?(-2o%>9Ba*Yn)(cXPil z@fXcwdc{r-+MnwcCihobYy~f4_(0;VvZ}_OqTl6+U0H|LIZe>f9%h_tJ{_a^?m%KFZKSZ@i|oAd+q%Zc8m%xg7YM);)SZx;?Dyocjq zneQZS@15MFpls^C7wKX zHQ_bxe5C@D62sJ#QJ|H6D-bBkPO%)1DKkJ$}ykBOWCBIL{ZAFn;1O%x5>Em-TfQ zk2k|gT)O=NqVHDxVe^s9hvSOA%zt{4;ucL=zgT!2(I=IBZ#{?bHhZQ#7px+@Q_0tk zj!)>VQ)!RY+`nr||6XM$dXEy{R-z&VpD?e}Nk1Io@s2|o?>x``q?`ST)Z4MMiBAv5 zhq8YEH=povZjX|_j{A$G(qFuPHqj@QcxIn-3GY<&Q|lzWO{uRTn+e~k#M`sEztFh9 zK=-O2$FSc^Dt>R|CCo?ZH>Yo-`kl*hsf?4aX(IX_j-O?D*KZ_zLYW8n1_~GYcXB)+ z{pZRK!n>4y(8>CVEBd*Z^%GX~vxeKRU1`7PK0y2(+%9%!J$yQa@OC9m8(m8HB){Jh zVT^uU$NEVs`q|0xq=(~4JJX-X?bpujcQ3}jc|Gy(RpOa*xxI8N?PVCZ_ud+6FLK=7 z=VF#$k*DZN!n?R#$aZ@B)r9Zjc?M~>uX25Laeblt(~o%^zlD|f?Smo2$HVPW+QYpG z&R41Tm28K0w!>SQ|MOg~&XcHIzc9Y^TH<48{YW`?p2++;{*?K8x!!GD?~;BR`~NVv zYe~Q60Oq62$IhKec!x4?d@#qoaV75E)qghu?F1iJwaJvfm%_1o7#05`Ir^M;j{$uW>&m%Qb-Wbt(DI<9UNF z?%$<8YqqfbJfA7~d{;&IF6J-o;bfMxyEaqK$+L*QhwV_(d-VQgPc3xqw3vA27< zzw1%@yXV=i5{lifV>{7!Kg=P_zjY(=?^ODoyVxE)iak8%BzlJuN6oHeypr!~I11sH zdUlXKAneqS9qi9F?gyoQzGeTGQ2d()$1eCJlyP4xx6@vhNBYST9-?=$-;?%s1kZ!n z6o0bUDx%j$QGLnw`vn+^d|gWY{(LXtyOsH(LrNI`7M1r)E^oiB%%A6xP<;L9LSUnp zOIcsrpX0qAW!&^L#}!>lTyYh*7njmr&ie!L@8Nhx>cPeSzmxsH^yk~-MDJ1NACA6& z@SUoD7~iAdHMct)YmNB4P41<>4tFE2Vm=^S+Co8hH0F)O+-|M0waFjR=G0V|SGcE5 zo;LX~E#PlxoE!~Jo++-PF`u{1?G5{Z(cJ!^Kc=~xanjt?_4D0LzKE~I51L5*{E9#* z=&ScO1dxTeXMV@sxY8>acmw`3g)Ff^)ZOBXxxGzIX(^^_fu=(P{$TscLs!n4dFad; zLY!$@q)mw06bOZVK_OdHfbi`xf159==_jpisUT`w+U#!*X>~&SGFP>GQBaCg;SB^D zyp2n>h%e}E^J#7Pj|U%`BR*f6yv`S^k91UcV~wqnK8lQ^9c@~huPy3}rAcd90g?`7 z5=DfjS3*}SB7UeGvT{WTfgPIr-!R07e2DuwG>#GXz~M(in!pfoZc|S#I}=h&Sw}KTtJxx;8fkLQx+w zio`H}UFr`6^n|Dxk;?09Y+mVgNfJrqV0j0 zXfC?&sg#)wjHyz0=X-6B1_ZRC`6EDc0{yL-D-?wRtQnk z3w$eMQgBO#v(dzBywUuGUn&SuhmrXbZJR^)@A_ zBt>-)eE^)gy8>m5`SicdUg3@0Xj;LL8!gBi^M`_PyKwdXh^)_4!rA^{)4~WP7wpq4 zxO_5$5VBes(H*w&7_BDbZ;+WcmFP`|CSodW}_^F@~XB5r8X;9~^a z<;|=rGb&vLnW7oU(u!4(x_(>SVLc`JjWmT6nY5n9yE9w4;YN0YHQX$(^EI|d*k2m( zDVwRx$_TfrT@}LgD?;tkrJ2@InqRu6WPNDDNOiNLGwKIQ_Xn41FdlG4KdS!=G`0%Y zrC&F;wuPDu=4Mb38;S5bRORg9^3hdv!o|0FmkN;t=jLdB#)dT18c8VG{;VaM;|uyC z{>HQk7NGh%2$i=KY5f;O4ws&nzXUZ83y9=CxSYh<+^? zZ4DJ#C~u^()xxMmC1y4MEbYVemUtr~x|lCQn-<^PP{`1aXNXxYT~~E;8M-HgZ_x@7 z_*pgelo`#fuXp8;l1vYMK_nCqePkYuE}B!3j|5d%gP}}5via>%e`7&JREzmY=hn}! zscVnuon@XvR0o$Mw8#k9GIX8N-C}=}&oKBgCvtg39Ow<8He;yDyyjaS#9$y_X}~n* z$5<>+Nk}!WfY%Uv>PduabT4;Io33)uZ=itGMVon0oua%Z)Y9T>T4;=i(~OZ`|AXjD z5l~MoI&D9q7t!{)lR(7cuB>v^R#lYMSDB}(_eI(;p55hS5e#);bBja+suC4zDicH& zvQ>Ix-r1o524$vH@KKT2;*h`5SLu5BN;3VV*n3B^tOcqzFKI~D!a1wSZf_vqE_ntjZ2NgaI$f|8in*AB#POg z$P!;eS=evNvm7IvpnuS5rPhxVheF4QeSE^+7PyzA0gOb+p{q zie7y|S-mMiSrY=*m_MppswqXSZ&^DA09C=pNJlv46GM*N8Y`Sqw2pFbQ;V<0x7=qK zqi5EDXxqMsZiCizET!n^d_sAPYHLhO1BJL+F}t)FeSNb(yYJAe9YpmjLbJV%;8qnB zrkmBrNIIQabrag0zqv!iDQ0a&W-5gAW0s_aa_3=;Sqd25d~XD1SrzcN_#6BIf6Uj+G0Yw&7c}I1y$+yx!( zt1Anw6Qa*$S&A5#eQJO??us;=#4J#gN`Z@|%N#~j5%31{Wh-d%7hO>0DY77`_hva6 zDKC^FD9RdR{^f8Ox>#8Olq9aicsUUAHWi*CrE{I}>`17sF4P{u%uGI0Nt5Q$LbW~} zR2|IC7ha`0C>o3&57dP*6=vdjWjS?ia7?ujon1zxnCW3mNb5tw(_59$Gy#GHm&sr& zhdlKmz2CCRMR-xs2R1G7V@l3K*(RbO$j2OGrQaI}~>*uGeV$9t~ShVV&Mvm?sF8(z^#zh z%o*n~XXGu)gsI;cW){SprDbbG9oPCBTU`iqePZdwqU;rzc#jl9m};xPoA`JiIS?yR zOmje@JUZdpBeN((II?1)M@+Njh&4n-hr(zl#5!*+8ffztkr>xwZLmPnT!PLE`GdmT zDq6j<+7P~AU{TjKKJRj0A%vpj%#S*?yk$quM46|u>M*I7X01=qSV@~9L{7?TOxsZD z%bGCUwPKhiTY~XRXvIP-SD|MR%dl2S1c~HLiSi`S=Z|(N1=^|bHohPRU1Vrcp1HDY z+O@uLprbxS)J$wuf>fg_fDW@&VqGX_WmiWr)CXCP{;?|9-sZyy0>enA%;iq0R$T23 zf$XU()`y#DbQg)fM>GXJRLoK41?_F3HAF=RCzk)bO|FPPgcY)me9ATH_R=##^`RDD ztW|U>`8z_n`0k2?Vxh)RpgPFGLU|;#V%I_dV|G0GQ-=;9dvGWf#y+tMqr-66hq8y7 z{Q+M!7H}<%svNPY34IP78@S>!j8T6_ZmD34CTSM=jnhkdb6h0m^2KP;kTseIK@D2GZll_4-<~$;@}kpsK{ux39@SU*ik5WXJqD5|~b{ zr|9)sUQ}J<4YstK3{j-2bTZ!cQ5{@dYhID&7KPmdCX=bU&UiaXb+Fvu;zB$OrOgjD zS+otBhpw%}=7olkH)3IV)zQVZbJ1NOPdGD#2Jo^uYZyZBVhWbJ0<);z8gF~B5gm%T zLd2v$OagT%s@j|;-+eTtYS;3a?5p%)yy;|42!hrHylT(x-#B}MrM1Li(r6PQ&O7=(rqmVeJ<8z zO-)ADxkyD&vD{ZUwLWKF6KZJaFpH*)@sI$yPqz?`AQW;0id5KXH*p5!drOzdREO*XPq_OlefmnUa#TX7p zmmwdWk)P=Z$SU&!YN`_38)Lp*5`*-RPiR@VLaBpIx)5S$G#_8?(Osvd(@T*eipUO0 zjp{JEn?gkvy#R&sg@{oYb*4IM{B79ESwI;{dwIzv_j+uD&c#;JiH4_dIWIs2)7eC$ ziY3#62(g>ZM>U;8PEx+;OyK6im`U`x2}OUKvSV|DJeG+?yzXnN4>6Nn%fHr#y}Qv? z;WkBs$&uKFfa7`Tl6IHu(=14+gXh&F?Yk-r#j=cyDWZB5yN-;<1ITz*$l z4nm{9CowP7{+^^*-)h7neJHYviS;e|tmy*k1RW}6Q0a@oDP&DNWqFgRo3^T)(2i+| z0y1D@DwMn$_OCY=lX)`}SyJNX3kIl=Q8#m^zXgadd{N=V`j(Z#HA#&17Fd8Nq<2cA zs8es4)ccCPnW8~XRXG}N!zZTyVZ)s_d%`h&AN`HsiHUE46H~Q%$dQA6MuIv##*l-y z49^pU^}%KiA`ETCrWKKY4ysxoW?QkNNDiVyL5rF&lnpzx(*JVsM#nDb=7yrN0`!Fy zDSd-DX4vwJz-?9bHl*epq6itlzu6yY(?u5>_i~Vv)$D2}b?prZ%Sa}4rQ(4dB|b{4 z&CNa8l# zctL5*!{$uU!8^Zif(#X2tTHr#y0FSjk<(+Ixy9Aj?xU?s@ca;-^a%??$en->5#C4-o(eV>V?BsH9*gmSUO{z1xo7BA zrq8V=;nj?_AyXBUYet3YkwufycjVDCSElJ>WSN!*MP>*o%vuZx^0mH{d>M8VE$(<#i$D~WPfx&Ank$UzUiuz4i=LCs9{W9(){ zu9rq=Q>;Ym8-21S`OK?JqJss($|))d5MTNKQE@r!;4y4 z+42_g2$|q&oh^hbYit$M4f@L(l$uW$6&V*qr||;Nre&K=A*{uQ1d=Mh(MrlZ#>PAi(&o+IKwDtFi&=vh9-Gcn^~P6cV38RQiJEV| zN!gP~P!J(XX`stpADOiJdZww1lnRcDH*OfX*M%b3-yCbrehY+E3N~D`wZe1~IfO9e zBxEyrNV$+S7$6C<+$Jou#QI>mHcS_I(^)PGi^qBl^mTZSH%C)MYxOp^8tBod<+MPw zK|E}=u$g^4Pgq(f;pRo}$S{~L2G@9gHHPV*+)9{_iB3+|WJoR^6|v3~79n2c^Lx`8 z@{w7yxYk)_lrv95gLtG5K?hfo{bGxBg=WxJh7kAXsLc#wUUu3Q{+y(MYz!)ru3KM} zkLLG`mPr*?Nxdmvh#@AAXQ`UUZr7+A&(@{`e=CZb(2B+W5Z<|!{bY?~QqcjMZWOO9 zQo3+Su(+(E+>6=#tf?TK0BYyvAm+oZHdsp1)XcDvII9uJoYUn4#!9@(0A5`Z6)$eG zmaxhj!Ry7aa)GDzTJc&ZY=mBE7A|CGES}(-jhBNg@%fhGnGjPUpn}R8^BD_bNCaXk z*kbBlC*x9pm1!=JnTlyez^xFxV3ncfh0U_Gcs9wDW4h|fF^6N;GwE`P=drQef=55F z>Jo{xhqHo0oebPk^-$>xo4iUzlHn}NYJ7DU&G+j>uEk?F=NZvyc>Y5!?-rP57ZUJ7TVBRYC$`TNAi6Lt;eoMNJYOlRqk=GHRt2Px3-2Nw zJ@cJMkxCXh%*)6+2N^7c%4HTD8me1(MT5ffQxn&8^_h(nGWpN6+7D z($GRT-lWC8&dSG7Ip3F`%cxIh0XIcf>{h{$Wl0zP&%2UumT#X3oK*7NhG03Np;UBUaG0YE;>Fv+A*P^hNIu0 z7+PHNU{SS2oeGs>AVK(rS2D-+C)5fc=Xgwa;&_#S7~qAn-#clNk9eZgM-RdkkPSM) zg%LbRO&db-AVgL?!&wBI3}vxkoIxWt=jczh!t(V;K1>r;`kL`Jz&wiN_bK2}^n8>k z4v|onq>IcL)=-V6)YcWGke7#=ItpbiXvM(7R0qtLw1r4ZDlBIre%o?^3Q?yV zli(yecLT>-?+RfWLJm{GsToutR`rYh%Gi2u@}44{FH2hpsc>mJNAZeu{ksaLd1p#m zAY((>MCe}Dg84AB;ggvz^w%HyqKo<6ixuL{llq$ovH}K^)MX7(M4~YtH{&w5_PU$WpLrJk%P?C*W=(g}Z(UChoal~x3$dC#-xbq;c*dVL9^mr= z{$|J_rndC=>Far`98;esFuhy8#EZwuL;8DL&GRf@;9HR~R5vGsYIywWYZ9hLuRG2f z7En1TkgUX>t45!A1B3{`a*|Sk^|#OFp@$qqEbI^Sc-gBNB!cd@^(A64j0CZDvFJk- zDX!*(S^R3mdqp+8E?NJqh6Vdd>_*4y^o(*)I@8KBPzM4jdGrJp+GA}j=e;f{W*+KF zybu)A{KPz${=hkd*35a5rd7&?m=(Y*ySW-c3^xlOD{Rszi`l3r#lH++bO^N+B(#w< zr_4sed?L>Ybc9lW`Cy~5mG<7@FYX8HY;PEu+lwd9c~O>I!6 zwa&58BWzUA{E*n6(w_D1tdunK@md%>ree~=5(!TgEf$kXX7f7cR8=c6D{1o7Vg_}c zUpzrN*NeTDjk*UhuNKiZswyk+98p$;MeIo5a#D-hjYLS3W?(5!%79L62(4se z!v~fOUj#CBX(BJtc#w%9p%tmol*JTqz9jV#Z!n675ZL|YX#>eR!$4vcP8di~r}!s6U6L~t$V@?1ixQjLvVmAoX7re8ZBUG5 zpcSIz>AnrrS)5J#VnYH$hpg`<6+T4o_7wv*Pm;F}HzY$rbm{VS0T&%lG10B1B z99>E_aRL2Mb|TAO*+42x-=h08QswF045YBe{9RloefnRXelATY_VyLr*<~h6S%P&h zl$k&pTcPwu3(MWx8S*Kp%QMiU{ONy|eV^{nuyYUni}9`fIyyZkmrXzpS>S0Z0@Irj zed|0>7Bh1SF&5g>v}SL<&P5O3AI%Quloa^O8$)tPBv!Ub_?&`6n`tVu=b1&`1|n>B zG|JH}QPjNtFF zYoa|8#Fsugsw+(^C`Dt?NN&QKP_RX6!zB0?qNSMTvd$DyF565IWm!*dA?xw5YCsg* ztO`<0#mFFrMFC;Af0ei+@u6Uhzt)@{W46d2ILTuqvIHOC`52HU;5>Nq7l>h5Uk)mJ@T68qC*VQ)W!r zmqTQWU4No5B%OF*&5Q+VE)o+rNitIs1SudeeFT)XCGO`p>2x-EY(fy`v3x;d{fMYR zEGyRs8x}m+JLM=hduIy@-F#`*gq|8OqZ7kuOiu;^B1c1=ni>@m7eWa`QOL|PXDTj+ zXg~R>OdmEBN+l~1x#r=QMQF(~UEUm;Mj^8L5wl_tvw}#mCmhcJi=tSYKBAa3fz^&) zHa4-ABZ$SrsAi&pR|bi<0ngZ&5psp$efRQxS!PsewbA}udcUiwMWuGX3-Qc&86M@e zB7yXXCsLer!!ykcgoROwoJ7B2r7UrucXykujtQ-E^~iNaE3(vmi{h7y8Kcw_t2BF9 zr@v9nOj&j9QM|mPNisE6g6>si=ZMitquJ+mbi2VE^Y5j@_>WzRsSGe9?4B1>$HD9w zbdfh%P;MG9!S3$dG{h$_87j+2W3u@~2t`lJTUnu)IF=PvwWuchmG5K}MiOCS#+`dK zYekVm;wlE@D!fO2+qh0-MIPo zht!8$-C}l1a8etykqw3)c_`+D%F&E-@t6}ri(FyyqIF|NXi&5tBp0WumH1Y8%s0=w z+#A(jpplDsJ~pNzuFOj!9998D;@LQS3O#pG^*rYzrpe22Nvkh=eaEN}ghTlx14-`k zfdJd&vX-=r*}zWbh9dqm#Ric8b}ZvdExBX|Irdm!9A6+YB$yp)H2N92!zXQri}CUm z^Utyw1;vMw@TnWrK-l!TVI!%_8^t$gutZc4wRp}rXOYoN^3lL2;+dhPWihL-UKquL z4tWm^`P|e8-4!A+;Z+AkRSuC~Z=DeD zCqyfhbdC=j+7Kt_(>d0G#pj0FLU{<$1*OQWOJbzTQPUV|Me%Th$kLh@rrBD>#7G{& z^l5$U#1v#Fw4*d8pX^Onh?uPwl$9_qbg7Y812!L6%fJgsGDR*zY+cPGsc4`&CttH) ze5c&xWt(Y1*>bSSlxa<75_3@JZ$9jS=tIZYCMRtmontg#mO0YqOsac8`FOCkPFX^1 zp~XfqYcgm`gxsi9{X1p_knbw3&eB={y*M--z-l)crljkN0@?f(gq~0m(SlkI3DTtH z=z|I(G<`WiT1rFi%F6*%M@Um5g_j@W6!B_pQ3+{5^*%_S?Gc^RwDb+ijqaeP^$U~e zo(S;O^#~eSqZf-Q*`Iw&FZ@pd~5n$iO4lHz%8bfsbg3b8RA6J`EHbrXQS@pL@ix7txW}D4p2~b#yvJEIc6@ zj~PA0JCm_=`5&-P z%v8sQ$~Bk$ENYvrd>QCScJyAU5n zOkV7ouIp49JVAkP0U2__D+LP9svuf23523zKb}dWG7%~eiW=T-Uw}9wfw2(zlfdhz zMG{m0W_!D`MT6!1o@5!hnK`jMITe;fxTI8DPm5WIGExb%0>Es^W(V4%t+Fk_IqX8s zW_y-hNSvYj%;svxW+S<5VlfF_){f5&gPy((kl(J5L1vOdwMP!ng?T{J z4IV>ZC*st*9yV#;kMKHF|g47^FGfbOC zn(!^P^e2l#c$z4Ga;^pRnHBBPSg1|^vaXo@%%7BPPQ2U5m^Obx=-%)YqQzurw$4S{ zuzq+kn;sGKuDBBH;IF`vm+3Qi+06A<{pug5VEJczm--5chfw%JNwWhksJhU^$cF|3 zlmHELSGq*XS|nXm>B?+TN)XB)xiyR&W>{MQ5sQg%@fuq0t@Du!AFsO^t_2agWq%$Z zT}70MUVb<<#^>|#sYDoHV|yhUfVjvV8B&@mOgFTlG$M_djK=%yR``6u`W2}+Q|E|Y z%vp#98eIL|6;<$2o=M91f2og+I%HPLFj#{ zurWk)`o3V(_l%loAS+2N&3eUJ2QQM1w3#yy82;Vw*gBlx+8Fphhi;*H$7{ z^&v)Y4`u}YX+iSR$2tN5Y@pI3k}M~a$y}1F-VeY8RXJ`!MG(1fw$Wi#JV3UD!4nq2_rS$}nA$u`( zJ=Ytp!P-5Z(MP!G9u|f+QKMggFj%h6UHEun3Wg$MrSSNknz$pLzw4tS5~=dt18OstFksO z8F4`?=$5QXJhPMZxxKABBX=^S;#FkMGZ7lCLD=9kBs zh#vJRau_QrB)sFaN;jG{GE)rrqhwu`OR zK5wvH-Gq`_PIbrJcm=a@zFvQpalThPKq5Oov1kKAw04v{BOZjd(Gvy5Bh7_Ti0Qa= z0hHu0b^UqV%syWsg0T6Cz=?em+(>4l)#mQW=F&S4V3w ztyz!Jx}|o}G5A{yw_DH$Ql5V7r!ueKEE-6a84zYspnftb)G>~}l8@aH*g~0>)tsn0 zim9rZnDIT?7YP}fr#Urp7UcPu5}9G1%$%E^cU~&pD2$VC=FC_$lWAFVdQ_*FNzR>r@rK>a5g#533TpDN_yYy<-^_pXc3F8Zih-KE z4jBK4>og1fR{Ad%O3DOvme0g&;l_CAj zyq|fyIhwxP*xH6KZ?ZNT@z8ThQ-O?s(T+B3?^r4+jn{2UgJ~9+ZpBO_^J@=g(lz_> z6-d5^z3>P_<-c|;hG+gs6{u~gL0yqH!=EPc09{Jn##a5-$@O&$raIj2#+56*4gTd* zryh>$0OlawF}&T=?Z<~Gt2Fn?_0_+J>Ik>H8RJGo({A6&MxXw@c{6T@>2!_0CU<*q z1-3ge4U)G6+l6S;99W>n>$EU?2a)_Sw=WV2MRc;o3ko8uE0oNrjA>9z)L(!;au8;! zciL39dwE+yEOSt1lz9d!EEI%xU?Pn^cK{C;SgB`bCMoPKlepTbq}+TnNb~W{P=0Q{ z8Kn978f$B+sTpPJf2yTPF3vP?Q(g*Z|Yn&!rJ957Je;+id9R8;9K88=eXl0|(=MnAAmsq=izxptr;O6plxp$w2Mz4H% zzWMYc`D&yN@P8Qi*NZDrCq^2Oq{rDdhId0L;5n+JH7k6k-xze(EK(T<|)>&oU7 z7diJGUNhI}tXwm|xo)1bvSehUsBGl$Vr`}N=_s4MvD{fQz@}-Po?)GqIi>5Y&+g#vump-_Vtd^&X1Rl?>BVP zDwI7wCi&-)eT%hj%`s*0VCQh>*gk{%6=|a;*_t)YQFQTzgJ0B~#bp<^URk%Qc)yYM zO9z!ForCIME{<j%kLa-(^hG=!6i=3UanoK zm5#jh{u5CheVvJs7ggH#8#8+D-0>$AjUQanr?c2O$hHTRq7|2x_bnZ7OO88W=+)Yw z;?paNt}HEgZ0c7UPgZH#@KSBOt)$r=x39WgD=%7GoV;LK(V*g0YX+<wox8uBmuUF!HCg5zqIe>Ek z=K;iVKArU6)%d*-a1mfF;9|f!fH*D%Tn4xT&{1LE)apKg)*ALCCo@c$XW zvw-IS&jbDs@FL*P0CBtocp2~)z^j1Q0Ivhy1iTG+7q9~$j=uqV03QMV2{0c2!k&SiiK{x8y1;d(CrKOX1#hI>;#iIIg@B6y7c=fsoG$}h4!8o)2~dx#6x`MLe=Xp8z-y`6py0n-4}0W$#Nn8|1Q zm;OB*_*sA>0mlHGfJ(q@fH>yhJP%LLx zs}1;zaD6f062LkGUZlGe|E~aa0Xs;7p^}s{QnHsp9B7F zz{z~R1a7B-{~G_l1$+niAK&`{=b!lcXFmVJ=U>ISkK~B9jq84Z-56JjbAP}9{x2v8 z;(AZOAi&;?AI9f>ao(S=1?_=+eGtwg`Fa%2qXA<8V*%p;2Lr@`p^J71Ur)q&65vq4 zWX4Uw*#Vden8CQiaGnX61vnCLG~gJ3Jjx8$<+!fo|5Z580n7zd1C9sG2P^= z7C}*8r{sTo2d)5XVh8-wfCYxRr5q2kjgG zZwK57*aWy6a4+BifH)q+`A>jH0gnTo0EpvBoSysRla^5=QjXv0^VZW+kAcp=l1~b1OCRizw`M6oO=Kt0zLwK4EO}_ zDd01}zX9U-lFvJF?ge}e_!jUTzu8hbjCN~>;tp{{D3n6#v_3LLB@p*_m=T>JD->Hc_q#rd~M`&F5}L_ISyE5 zpt-0ha+T2V4OVM<>o#@%1%)zK+k=Wdo`Nxy?>hteA zlkcAQ{L71Ot$8D||DH>~xq9~b{UW80wv0Ue@e|(|yx#xTt%rPi)QZ3V>#`p!J}=rk zHv8VU;~Soz{n3vLAFUfXamljIr!M?v&eA^KBkqf>?f%P62dA3D=z-+{|?@5*=H-?EPw6&RrhFjEqLv&#Fv}q?7Z|lZ|}tK z?%QYnX(!%%`a{2cc<$fg>u#KKjdsh2C%ohE9{bA9K}WCCCLHk2dv)iWH=}CJ9tX6Y z-}HiQ|G&gm_Wfb?mUH?Y|9smd?fX?vyGJ}6nRWHo$t%yJpIU`y&gX^adXGq-;O)#m6gx_VezH`TgP1xI{5R~|N89sor?!OchD=>ee`<& zTSuR?tfK18FGj9iwZ%5ucmD^`b6?I%97bo-_Qo*#eM%+>RzZU6Ay_wU?m>t$c>+57b&qYnBoF{QX-sIB~(0e`w@ z!=5$Y?_68|*nlf1?fux|J}15R&tcUikR)BiVj^%q@xZw=HQ_R>pjHKjZH&h*~+?SdVh zo#Vdyq4KuZ-ub+r`$^|dgIh;`9^L-^MdNqA{lQiHj9=5yarM)mJ^kULpFgee}?dapAj9|5v}}AJ5)$)MMBE_~56(?&K5S z{`agu#z(JRKceigXxrvbzi6#Icx&v2{g*wzX7`VVwCt?;&)qNowY~H7i96=~y6UNO zF8=73C9l0Scl-afu1-FA=XJLpca!sw&ujj$ssDpB8WL|l@QVMlH@9|#7R7e|{<6cb z``5}32ff!lz(AxU*CMUt}!q%@Yifs!Y@^b04J-UWWKWWezcb)O~w~{A( zQ(aROSv{hC`qP(=JF+=2=%3*}OGhr*^yBv<_r34MP2Io#JYdIRtG<4C)W9cBI`+>u z4c~n9s3$Ia>+(k*ymtFF2Ys>b4}CxSZSc-*ThB=xaOv_Pqjq!cTUNf~@1Je{`G*U- z+An$YzQ)mi-2U{x|NLIb`*V+Z^y$5Sm^t>dzrFhHkn?(zyDfP7g0CA}mX2-vVf&O@ zdOx4FeRS3E|GnG&m!{RjM=kvL)ob4``(pIRM}6_=ALbu>)q$IipYrm>{a&bhbkWl* zp4#ifF(tmeCmi}z!+E#uY^{6$%RB#e!@$wSZ~S!Fmq(rcpZzXcH}?AxkKVFo)kE){ z6moq!b=;f%w$EJf@y;`^Ie0(s*6n|{ZJzmH?`5%#_l|gcTED9=Au|G$nO=j`>}>#V)@-fORYJ%^tC zAKBeC_0^W&CG8*ZUB$sYZ}vO(OZ{X0>o#3E=i+$l=_l9roZ95Wl$##9Y4+UnT|RQh zFFoBo;LCQ+o?Sa<%k!hUOntc36P8(1mq_n+?Dl$_{ZhL<&nMox$5LKn+4Mtm<{tm9 zPr~CNWe>J(u7v9KydV6JNMMcv(YqO`lyXc!{j~+j`yZ`wyJ9`X! zKl$BjcePwLF7czU9(bg~r11w|omqTkRr!j0xAb$JTsZ%W{fQmzVehvZmX&ht7x$aT zJ`3J*aBTn63uYfLh}^boYIKTg_P2K}3C&&g-5qhs4~%?0H~Gh@omO=GdQQXDx7N77 z^A|T|otrxE-qq3fJoK~49y~0~*|@>hQSY8Sxc5q{)}tp(f3YC@(!CL7bEk|SegCLB zZFbDfeyZht#h={%ubb;eOlh3|+6QxU#+-g;VaunA58txk);4pul%IK{`>DkXI~(TR zzht>3>AP7c*3D{A=Y?HEF2DI*@yVf)&#Wr{r2KooJC_W9uFJ9)qT9ZCTZ7jPZD%by zTl8`1H|D(Z$d}vo+Pole?}L4g-v2MpXWxBwaN0|u_0QcCDYZX+O9#J0$&>39{N!$Z z=bjIBD zCcaR1=gg8HKJ~m;Tz>VbuS+Usow(|n{`m3%Kex9@Sa)h@O2R|Nk1n>E z*7Wep?$f;|l)rE;sPW8_giFB<&l`qscw+y>VQ>45dh4mfKZic~;1^-<=5OCRC%RVW zpHDBj+0uUh?h`dS-9BUEfEthAIXugG%hsmL6N@H1^K*LCz26>kHm^1QsqnWWPfnZJ zWLr^q?QUJ#-+%eOMwOncj~;pK+OZLB?X6ADrQbR~oSB+%b`a$D5 zm)+SQYGduC-<<6q5ghsR4{yy{bNimcyoATUF1&DQly*G>nwt_{%0BT!`GmYRQ))h$@WGQ`>^MAXV*hP7)%mXH=?y_r%Qxd{-dEVJ z=egRijW}?h@#r0E0?stpdZu>ZEq9;T-78voFJ;8t_a6U#;nRD(To-U`aF5(s<#8) zZk2obTK(|)rnT1&ejcP zZStvS;Q{-y=O*qtQqpTyzi%dW>p3-LT*90QWjFn@yI;V#jz`u%WcNFhQ2zW|*P5h< zJoe_pOQY(@i7y&dFHhd6PndnIK0ia@PiMB zzqG6Wl?_WeO@2N0!NhV$dUm^x4W=zBXlC)RX-W@ikeyiL;^Uh;_vy6n{l~1B zXVt&O+33*~BR^?=@JikEucfZ*-=A{pgc;{Y?->@J6Zqbh_vfzZkvGJ%|INNve{?xN z?_`g9V1X(8F#WK{KfiR&VAv3 zZDOzWqtATw$^0REoV%mu-t^6ugaadbp6szDCHeG-70!tnVed_U()N|R*1!(8jUN~_ zI=6nk(%07YaV4}_Id=K0$-xh-UNGhPK1U8YP_8I=|-#-;|wb?W7sXwR4lcdj=orYkv7s z->g>C*4I3P177o6zgg?q;v>J#39I|_roHQj?7Qv!eW!0be)5ZEYZkrn!I$md8a{K^ z?t$UCN6KEEc`>N968q$wzN3y$japec>EY+ztT}MiM@1WFWY^qw?_GDbfAzTmJ03Uf z+_&@A^UE5=w|X{v>eUYGzkfP&&D0;4yu9eniiwr)jvg^#)XDafa*p)qx!3;Cm(Izq z8n+r&_*ZTkHs#sF?Q-wlF>%qr*IV6k`cSX5B_BP0>dH&CH+5O|(=cn>*lve@7@W4C zc>KeUUisH&`HeqIAGhd>+qb=Q+ZB#kam?#(lGA1rA?$U$-xRZeOut{%g6fMAiSqogA`ix3oH=ZO35~K0kZM z{@GVUYPNbjtfqhd#kbejP5B{STD3HEYl_`EWAk@X58Io=I_>P+AV0p&mg!j+N6v^? z&~wVSd)|v&GI_Ff%k0xPO+GSs3W&RSfnX0N-_YSsRFbZ7bPC09Or}@yJ@%5H&^Z|=)O0n z{IhxQFD;LnwCAbsx=4lrtxvsZE8h0fcb%Pe4onaJ*Uh!%O|`#oIl9rEMFs7YD=Ruq zZJ6-dsnPB4N`8A_y_n#i7XI{L=)>mx&K*m(e`L$p@W@AFMtrtpsORCxtYph}yL|W4 z_cVU8ZvNna4{G#z?3Il&(7$xxh~;m24y_xJ zaAjPiqo`zYvyp@9E*%(rPt0~}#DbZ%8wMl~4$5>tHQ73)&VrkwT5Y$_v@M7aKG-KM z{&MFQ8<)2(S=!*M!H@d4y=DK<`}RyMURcp@NU_bgzhjk?!Ti}{OH-;w=RC}>x6Fa|MXtF`=0Lh zVSqiY*QDifv5QBXnA)RpbAQ|-%70a#k+fdHl@AQ{pya8YpZ|R+oR39 zr7JA8FKvyE|17Eb$D7mVKRGseX#05$B0k!CU;CD`9CxIbjxN1>+2`vI6+JQPlL5`b z9_n=VmcpNhrRT)W$$9FL)Ma%pXMH;T_Di?Cd@5#S#;hZYKKjwIKIP`&i~0>}vUbR- z*WXz;;qAlz^Ln@bDP@3p+PV*(F0P+C^3_4Mg8Pw|nb^{Qa-;RLCp_En1Ao6Q@5@gG z#6C2pQEu9UU;dchEdIim+XtQOlz(bpOrLc_hNee)YX5Sq-Nx6m4dGL)1M)9tHQq6~ zEA9^mKWU8WRPodoX>*#teJD@)%Cn~H-iIDF#2;L|aQdSMhvzM9Sbwfzcl(ex?!I>M zkn!}|u2arz&z-TcMZL=G_)fPc%{rPjY~JRg0nuL_KbE_2;MMe@rI*c19+-1}^URlf z=cEUX{K0c#n<@F3^5KcgZ)@4LOY)n`uXcWUWZUAxL(e?_P_1nz%w1<^J#TyD`F;&8 zk3RNU@7kj-y|r}wL)V%`pWpIG2S3{*b)F2o_M>Cmq^X-$E`D|Tru*B?95*=H6fyQ< ztwB4B{F|(u6&d}>&bj$6rSa=G-~Xj+nE&jT;}X|?Gx8;Sbe*E7CtVpZBxmuI_!iLK=Jb=c8;z_8wd36g8{S z?sa2l?7U-TN8Lp5Hv}S73;(r$K{zg?(sjUBgX1*}0p2ii1{iR(hx66K6WY`CjQU&+ z+%@$2yBbHo)pHs7^EIoa?PvJs-#;brPYL`}0{@i2KPB)_3H(z6|9>rkN$Gdu0JD18 z`Q6W8G;~7PjQ>gapLX+zS~Qkx$FbC=MAtBM!0?WE8jl!(BmDseI!{FO^f%wBiPK+( zbKoMn5sR;B_yS?N{2=Qq+$k;`|JC?F7EhR(UJy$UB;%*Er5e8)UrW%ZHq%Gt_hV-a ziJvZ&zm8WPVY#%Ci7SZx;+#&tO6Li989y*sAmDC}y9K<2;~ND${4@1{T#10^alA~x zwarsSz;n3#3IW&jxgg-0K8DY$=GVZ}4HEDqj)w`jnGSQ{imC=r67VZ@-~(4mHMm2- z>+r)NGXy-Cf7VwWbZV~WLIUXk98h@04&*S_y0bjuJ1Ofk&c(E&l2!W9G@ZJTR5I0;M+K!C*a#TULfGRIKEN9zvp;~fbZdW znSlSuaYexQar}aSdpK@bShal}?+23vyffz?F5vfbe1d@6IPMVecrHI%z`Jw)83Nvujt9{}GF+PfAIWjEfRE<5MZi-y z9wy*pIUXh8<2Y^;@E16qAm9@@o+#j&{~0deuWkS!Jc#2H1iT@~ z9RjZLXA5{R=bs_qAso*Y@KBED33zLcy9K-h$2SVN#$PJncXR$S0gvQ(g@AYF_yqyK zpW_xfIE+hc?>#skUJY&&@JBd*f`Iqqc#?oW&hZohAHs2mfDh+*b~Sj8fIr9i^Qyt! z0zQ%RmsEq73HW5rUr`NiSX#9_r*eLCHMm8<-{bt@)!;S(pUwFb1bjZn9Rj|Lct}9^*MidHMmW{wQ+btHF%PMx8U+qs=*xs z-j4HUSA*vWcqHe~s|I%qxQ+9dRD+iZcn{8BQ4Mb3>x-Iyi|732YH*8y_v8HG)!;S( ze}eNTRD&l8cmiKn94_G598VGOoR8J@%n1T+`$WYZ0zQGi43Z__Ib6?d0neqEMsUp# z@I20+E8tps1p*$%<+ugBjLX?5;F=yK0!VDYC9?ua7_To7`2RR1%GMTA@G~Ws`_UMc=&57o?Q(-L%?mRDu0fEJ9xUe0$#z> z%@go&ju!}c8JFW0aIM@n3b=!pTZw?@(F<0%$^=}Ks|dJ-*KdV@+cP2fuX5OAOT$39j;0F3o0WOPxTR0vj;1&D>2jK#)=@}*9ZhFZXmrcMm zJtYCx(hk!dco-N>--ZKQ;&G~Z#+9 zZmxi5bABx!T77Blt3con=jGrQaIM@n3b>8)mk79)Ua5d<>6Qt25|^V0xW->0;114j zSXp&^?$uksZQMVb1>C{$5CKo&c$k1|`LPMOHXcY2a4kQ>1zh7dxU1^#r3L&DeNX^b zh=6Cm$lI5I=bTo@nKl7;a6C!G=|eWS90H!i`Evx^#`)bM&iTs(+`{?Ui_J(!vqSa) z9ncnf;aP{5(Z_Xg*#z9p@gxDa@N^vlp2PWb1l+-Kw}2;cyiC9iT)ts#RXuH--y+}^ zj@ty>!0{vjFVo6j#80Z_kR#x3j=KdshvQ`e?%=rgQn^G&lb-av;Z=20J9Kz+9iFAbZ`0w~I^3eeXXx-2Iy^^* z->$=Rb$F-_&(q-{I=n!Kx6hLffUZ%s_>u^PfchKP# zIy_v5U(n$ZI-GqB12oy;-8wvoK9Yfp@OxND{W0tC`*e7S4#&$E-piuH@Asmxe;wXK zhllI%UOGHVhtoMg?Xv0cL=D7U(&24%c!CbULx(5o@Ms;Lq{F-F@ZmaqfDTX5;Z_|! zL5FwK;SL?%U597saGMU#*5P&?K0}B1(cw8dJW7Y>>hLZ)JWq$;tHTR)c%%+@>+ohe ze4`Ghvz*#hqQmdfK-^1pct;&xro$i9;ffBA)8Q35JXVKa(BTj0aKjp(o%ht?K{`A} zhnsb{q{BmWcwZfE(c%4dc$g0Fr^CZ__@g>JN{9E>;Wizfpu;5{{;&>D(BY5h@I)Q{ zkPc7M;qf|rxDLnRTkn;k!|QudeEUm>)0QvV(z=GBveh#DX5*@CMf7lb!c$g>tJUX3 z(~!th!sri)rlF9>&FJ@trXup>F?t%&)Mz|8jDDSH8VY%`8T~5JG^Fu382uvARFs|+ zMvozys?L+d=#fNIm3a~v{S?t;3LYDyA0wJf#uLuyM~Nnr@K_lA0MTTE9y6o65lyD& zF);c*qG>4QskjE9)!jtX5Xw`==(a@D5Xn=*=-Y{=p^?YU=$nbAp_M0((G7{FD)rUnQElR*!?xFB09F=oCheAv%oc zBu0-Ux((3@jDCt}8ZvoojDC!0>he9|jDD188ajC_jDCP<8X9@bjP6D>buAtPqwgb{ zhDe@@E3E#BrlFCijL~h0rXi81gweMXO+z7%o6$EDO+z409-|u)O4?9nub)Ka7OPS zn!0R{h0&XdrlE$%%;>d5(@@J}VDw6&Y3SmqxXkLGXdBUGjQ*Tx8cKLd82us9G(_>Z z8T}s7-HFa)^faQWOZ4P0`gNjd28QqO&>cTt*M&Cy?b%CCWORWBh zrmoUc#^|<0Q&;FIVf5`pQ`hTpGx}ztsmt}`F}fkqeTdFsbZw%k%lBk6+Mj6Z(mf7F zUkU|HUB4%V(Wi-~p{gedw6b>(KD5og~d;jVgQ-5ILXFYz;&@_Q6y z)sWI(&NZaglAJH+Dl<^8aqUkWv+nj}|Ds6~n{PBkj+ zk%T0lR5n!4Qk%1USUt;;i>j1K)w8VUEQ6|Nnaf$Ct7mzIv)oiY%RtU@W^1*Xj^ZpO z)w2Y1mW9=`oW7uD`t|BrwsDrHt7lojSz@YZkvU6?>RFOG%QbwayPBHr&ROj~P*Yn=JZNfkQ{SkWzFyr<|3*TMxRm)^$_<=H#aT5+S=F7s#L9=`FZL;r%Lh$WezFx9)8Hq$KcNhD8~^`T~XT4 zh%r<2S8y__>?cV!B;vtw`|*q`xC6UIZVW~OkdTCjRn~h|HBO!gToKoMQ)S+T_-b3k`)!-VM zTqrF;C|e;xsRfNG4}NEW+=^6`UIZ_}Px%27k;~FKsW|Wl6r|#qP%1gpM?fnInCkqG zq2d4e$}{yYc}>R$AS-{nCs|;~#-OTzo zN%tE13o_u$y@v}dHhxJ#pr$s)aR}lz2{)xSDIgo)LRiXlr{01v;ZiYUmWpkvc~cUQ zmx3skPO-5s50)R~=}Jyxy(X#@|4KAUJdtd=EraoEKWK?fZw8Oc*pP0~7gB@q*w56i zk$A1jGSh7i;$6q@;P>Dg{Vk}IUb6J4dk3pFIaB5C`z)W#6-2^SDlS%xGr z8_}M3SVuI6#%Ue;O6k_uz=cvglv-DETBkvta`gnNcQ_2P$Y=+j-(c-I?e3X~$mHa^ zw0ua;{vndHmqn`4%Y1IFA1pI92o6Vb_Q7hyf#ht-8CYr2@fXm_!n%L|`E$L3{7^W} z%*d=9m=>hz!Pas1VVbHIt5+Y+f82B<{>9buuj2gX)nU-j)9+h+xu2CA(g~5|mqLS- zG4wEI5kZUv$%c435F5M1^hZ~fSMvt)ZogjMzAe|uv-h|6v-h?4>DwEcgvB}6SLs zC^ovnlnFP64(?i|8QrHcN5S**2?S%&E%+THs#v-B4esgl-Qe_#i#~43pirzc-V!gL zh;;^pO3{U;%moaiB!c%~S;D@`3;+qmID5f3Z5U^km@=M&?ZwD3p#ha8$U%$~A-lZ6 zlt~L^ynVzDxym?{?#C)CofEsn$eviaIL;Xt8YIbU*(0&ej-i#oy>G3dX0AIP_B3Y_ z&`waVVFL%-2iXVPAGbfTF58rk;y?ytopnR)l_j2F#%QBQQ+E<`oLuSS_xw~&twU$; zwk&HbTG=|mOprPC;`J+kXYYfm>npjQ3+jz(B=bhIGQ$a&GM0nY*)JegK3QHDnPJ5N z=TJX7!SeD+P$A$-I~^B&H8sfIxxXdO6n8aFo*3RE;#~Qic2-Eyy)0>WKvU)FVXBkv z;jzw1VW#~4K_y4i5BSAI9Zl^(r4)TNZJXrmZjIA)fHQGC#@UgCg&TLr|cHoJCW6l2S@*5j{^7r#w@3-;`34bKNY9@8L z{*C;i>8O6*l;1Nb*UywO6?JIun_hM;R=#TQ6X$w5sIMfSrA8f+)l;kL)Pc^&>!Pg7 zgT2pFS5M~8>N`{^m=l~fleEEQV*kM zLe!dp5B2s3vC%Jwr?!og4=azd##^jBun~`@j}G(GJ)rZ~%opbWLX8u?FjnpnUS6oP zck)%tkmc{V9fzFjm4E_BM**yX%QGnRzNd%ZB1a~)8q$+ zv360c3@)O|UgSn1eoSs$^ug3E@Bx-sd7l)$AUGZ{}N<42bz`h8<73Xy=ezkW%TxE$~W%E=r+ThS`Xd) z2?P;Ke%NK*#74c?t_i}t2_CL77`|l~22x&ocNq(T6&d~6SjLq-16je4?Q3Fm)f;`u zpQwCBMZU|ZdL@jqj-ZjYIHD7sh!>zTjvcEH=L31G9X= zOkeO-FY8nvc!PK(Kx2Hmida(q#l}~BPfYX$wRDP%^8otc!4r+WN|y7W@(#_&T)}H- z6yl1U0T|`EAJac2?hqSZXA(pMQW{yvmz6Z?)?LHlh*V!Q9939uB0sP5Kb;dTulV|# z9udDt=yC#?3wpWbz!V|Q7mT<2F1up7S(NpNzUGI5j)b%<=tq!>GKyc@E1Lz z*S~aQe}NdA>HT8mJ}8R6ITGea&T*Y2tL;~5Sq1jCZ+pKuS7a0XWiESPxRRa}Kr3x! zZB)ZR1&J0Ajzxtckg)7P++f)cVnF^Yg5<g^kLi#*0|?dy?DxqVOd z)(baq79g(J_!@rcf(fPjg4sM+WTa%kHs2Tbu`ftz@`q>hAm&y+@CNZnXBKBDGQNZm zZ2Udn6Yu(hS~^9>4FJi;Yx^Ptc~Dt`5`c~GtxLuoxvT-*tXG3zdczN3M3)7NMtSu` zH8?0vzKXJr1?TMDmftl^rfAT`#t!&pCF81BaTd;}*x1U8aOYI-VIWXeVUwERd5o%eiMZw9kBE}%^S%o#>gZY2kWGpwG7z`0bK<$G(Z(w2CH!q$EQ27y4D71?b)WI**4gT=;)KJW(d5b!K#C^9~b5bA^Glyo6(q%WwYQ)HY0km}>7`d+>tw4iKU zhoJHbN(c4vSuLpVigW-%VQw;_K05A0eY|^v`k=WdyfN$CqfjCD;Gql*R?sJ7NMpPe ziNdd$GL{jYZmmI@nKC{F<***61B%`J)(EG(OF2Db|ZNO{BIk=dC^(L^Jv8j5ynQq0# zCb+3;MNz2OcsB` z6W*0B1plQ4W#f1RmAg?wu%&^4l;6mXfROE%!LBGj!j{U=d&B=Kx10jen+%JM>m1fo zBquG9mcr2AlYcHF_rUyf1Bf`vs1t)dg{LuJ1W*V-3E0HiHTq5CcF@JfV*E1AT=lXl zSY530B3fM>0RjOIy2-(IreeKm#4QJRCQ7b%jKvvU>g`{2D>gcDqpQfcfI`K_PdR|N zIlkcgzTiwAEH-}R18)!y0q<~zBI9_3P#16eo|xtfYUvai-2kaBjJ`;JEhrnyYol~N z-N@=S{ z%Sc7Wbka!5Jf0egxg?t#InzRDH|H%jKf`c-v{~uTb$@vUM*~e3xNf3aEo#Kz?iE$JYaFZs0jiG6NNYiJf>n${wRwbma1|%tW0xDnX zTTc)d#@M34Gv0=CJ#+&^&hkI@FCj?M9d39*YHYc^_?4nF)npbDRsygDYtvK`Jgp zxxnM&gvjcE0N7YeXx(tm7oyx>ik0#78o&+G|XD&~0(2WD!f7;^Azyp zY)Hm)6=RpPbXWp|=-0nB{nlt5E4w{I!C63#Mpd8D+WNzPzP@P@MYYOmY<9H)_Q)(& ztL!+J@nbfDi3tr+UjL5OIqC1SS+BiHlaG}m<}Yq*tU;|HGh=1#Rh&1$)fA&C%Uhta z7aav)C$n7EMbwd@WriqQcF|tce9)vlsmdChSO335`G-I&vHYiD_g~FHZOTFSCc47c z?+^SP^sjpZ{ddz|KC6KTc9Q;es?pz$Eeq>HFr(vvEtxF_!%zuZ|6wFk&IkcI7Js2SWV`6IMUT@L&) zDbqb5)y!aEG@JhgFuK^TOzo3BB91E`WPF3Tf(miSx(z?LvC{r=A!ghtU^{t3=D{Dg_$(jiPnQm-Ey|OCe9QM0Z zIxVmK3uQK<*aX*WuTEn>EW-Hqft|)~RcH^!Yx@0Fk)~pnubT2Nt;FKBp|^B?A7*Aa zwvxJlyf8w zkba>u*3`q}O+O>l0;{7-a^X-L!b(R-h;uDq5oE=&vWJ>{LuSp)j>&dC8~hBb$ynns zFoO7S7;;-m+hvszh4ZR(l;+OR8@2%wA8w)Yfutvh{p7aFN^ayhdvub~%=S&0%g~Vm zayCKN7T|i7dg}8#T9l~$vC-MA_~;KfYF6Bf(93^cbLZCw>Uld02^8;^7n-&%;(qA z$ifu!z2th{U)~8lw;FcxVTJ3F%FG?FbdrYbko>T`gEtD$NBC_{_xMX~He)hLBMK}! zM0H3t)A=WC7^j^^7WY>A8?ebiEt`zb+pdV=PbG(}%XIRy}0zf?#_X?uSM^q{=v9;vhL9`&= z6a%SFY-L<>cD9-F&nl~N;v3t)SmuYHW3n_q7fXH)2p_=XoOO*@`Ii+PNU9s9J^h+L zIXh&pHnOJ#;{;H|4w6Y8@M$J?ElDnaLz(xjPJe1EXyrKn>5ptVtj?>{+KY|D>5dD* zzC4(E4^E-{dIc8A_;)}IV)p@Hj83DOmU2)TScfyRARWPRTK9V&nWH|kg+EgM4ebXQ z{lE*it=fm~JjLW=Qj}1fQ2-*`I2?h7D>>f}B^nlUE9=fonW0!ILpjl7ssPMd>H>)KU>B*Th^FLQ|4l1tf*V1K`Fu` z=@*)hZ^FA3YV6&BVFfC449pD0{s}}Qg}IbM@nqaG8%_=3Y}~ zIqbB&1?;^LO>3l#grc;Z%#k3Fa4<9ajEIJMAsub9hdGNXNz+^Q6qNp zWdo|!H02HOgKNLZUK9}3%6&8-{eTI_aT+&7M-=5pq1qJkkCXS6w^hqA*7VW#u`7M+ zE9mh3q{){^-uUkJth!N>yqkPzT=WG~=6E#USicK)RGepkTHYv<$*cSfniVSVqqaOX zeEpX+i;RDCd7y@&xT8+!IecPv3Ttk{Yh z>l&QRYSX%3y7htcFjsUmLulgU?2JlsqfXi*d55(hi<8?g$34{w=5Nn|8U01NwGl<7 zw!ssU>|v>}?kWvZlnQ7;`$n+4frBesdGHYY7oAV?jlUh%izJJd%}g1+VB~O6!+|hS z$}99nNV@fLVlidhM$yjp1CC=n9gYHEJm8m@%JCSl7J=Pq4F@|L*_!Ye3N(q7Ik{D49d<~WlRFXC`sde$7OfG%2B-dqwS4 zU?UHXaKBnz&XTT0~X`6V?fXXI&Ga4t3;Ji}9RS_5cBrP#PhLyC?0Jct2&#=R(XXK(?Y%G_bf zyg-GFbGr!`CtAM&SZw?NzjQex$0BK*`Dx;1_?$7Y!lCCdp40S%+2Mt}z{>0bif~$! zz=`-YE(^h@v>;f=DrM`a>_?DZi0kQ7^&TEfZ2>7eXfKV85c9wf##I35D!+}+M>wt9 zy$o-t3}15wj0L}~0Tt9CfTAsJ?zM_Dg%l8T<$yioTTgB%0Gm1mm%Vf&KdaTOtxKU3MD=!%Ut#DcXlExEI`F2ZS#qX=6gKt%|LW<5jOd$g=DjTIMkqaxe| z6lz^li*Sv`L+1>2#kg3-Q){z4nMoh{WDFLa&f&Cz(>ei)Vs3CJl?j~{V*4CUW9$+1 z2SXr9>kq=$Qx_KG$#aw6V7#97d)54*9#{p)M^Tgo`F?mc$5(cpkUq|IvtmO8)kdhM zYA2wcJMqKEOHONqAFGe)5aJ9rfN(~hqd9Ta`q+7rWZp(kH^dJ_UL+=0y;%0#Mi9=( z)xPIs<2*dUjG5<8W-7Q-?~Fihjs;bgIIYhDLP=og&5q*5sLjW-?+5Sxpws$*%2QKa zpZBa|anQ6M(t?-Cz{|X-U!0xt!-TSL?})wSVX(6z;|`1m{3@jM_9YZBWxfYKY!ZJB zD#LBcaDpr{E(E2{+AAbeq486Yu;;Wwr?Dl8cTei-Y=0ax|A=$w9cZU82r#l=iJ|BV z7<6Kx?xbSbN45GPvI}?OQd&U6^4GNeO{eksL;8A!CugEivC8h%D9r(omYj{Xwz|~c zcy^-oLx0`@8a(S%+G(5yHK5Q<#l{STNC!_7Z2tMusYU5L1uZrENnESiPYNaT4NOnx z-?3ikmDf>|SBwpJpS%^nK;BTDyc90aLh>%}uObij;K9_M9BQc3co@M<_k;(Ejim@t z$+6ZUIUQyY=u_bzt#eiWG&)@oPifJHrl$KSp4PHeq>>HK8`}`i2pvx@=g9_-(>R8u z`N|#8ASl`z1uC_+Z+sSU&a>$fGkSIY4taYHLf*4Fc^kPr8|1-4L!8Fus?595(@#8x z+`;+5Jv3rx@7S9-A>MUd^3fr_^dSEQlAorNU#iMi%D#r-2>edt5-eY2x?gFIv;uu; z1(N$kyU@~s+!mr-2iH3ba<%md_&=Y##pPbzHF@6sF4kEXzda!6dMDSlqoyl5HGTgB zJx-BS6h{E7Pm~vSS?fx2$53`uxg78PfoThv=zzC`mnEDOkJe>mMMXc9Id6T;6y>RB zoNrD8`k40Nog8?TeQ(Ga`Yy15T@X?3P0^~}NOK!ov67!+1 zV2YE^6&dFqMGJz6_i>NOvQEcMIROsXNdrh-Ny84>+jG^smWO3*$@J_-_%(2`aABO> zz5oQc%PHD4st_!%6w&54&PLVS!Jok-z5NW8?;8$bxuf1!JY0$g*U~c<2wI|J_>hP3 zdf2FS#1IaKnK}lUhZQiaCk7iB96E*+3Y$i4Ak8a?A%Q}LfS8g=%4B4ghU8eVK=Gu8 zhGN3%N#`+9-k8c2ss^b%#^#MV#AAl> z7|9#+4Ug%^V-mbEU+@?A_EoS@gXbX@Bpdd0XF*cP6UBm9B{g{3vtUgM-p+zJ^=a@lVL`Tf zP?rU35$eZ+w4dfVhi=op3MbVKY>ZpYzJKUoeAt8RHrm3%7F-P3sag7(GPw?~3fZ5Y@5gOVS84 zaID@AMmng6c;Fbzqer!N#)1YoZ$+!Y*t`cQB^M9Spb_h_#w*aF@;hwv!S5J|Rh`V7 zrRh0saj;V0i~6Qa`c4&XaR9}#o~}aS9M$s7!uxkFHNKFLO3t)=FKAkxxD<%{lQHjjPasyruk(_3fbN3&SE2pu`kF-yK5YFucget2j zug=a8MUS8)GO2>g8++5oBen1dF{_KMSc@|Bpn=|FC^D~3^md^ibb>mRce#cs;|avm zBTOYz#z87=mvPt6toVXynMpp)Gzo%spJ4cnZRt=|HRTSdRn!9ysMVACB$UDmK__bSC=)chDv5n=nE5X616kLhm3XrX0Ksp=7MarXWojACNs1 z8_U2#SH@oUCJQ3i+N>*b8`cz&h zc(G#VJPFxXZh-{smzy#kWrbnQ%LUiHI<6g1ML7$AabYS<>AyoA- zb@D9JE(&@n-LYM$JbsjB2nnDugTj)N)k-S6B70(ohBVJMlX$1~4hGljj#Qk%r!Y-- zMxMkk%N!1W6f-Mhj+kBxQHu~KBi`7}iwU*7xs38d`xxjUjdMSx^6HEw!nY9eE)fFhx2@aClM(FiwF6#6{`dC*(gSye$NV5KWPJb`>q zE}t@_ukn>T=k!o|1g@hoa~)|` z+Pw!d81JuP!+A67ALcGr9rgfZ7ar-uT_l$I?AA~=!d<=``Nb_yIlqufkLhdg)0>J2 z@1Em-@NBHB#JQfv+xKhJf~kmT6oHj5-FhW(>7zjFVVT zWS0v~8D&gid>Zc1&DI<^gUB*qc ziXk^Uja!@?d62DXL@7(L6GxMTx-s%LrDp})yY(wX`K4l=+FC?RXbyI!cmi!4%j+-* zmI{wrsNXFtkCMM9v``5L)^YM-7@R@gjxRD{ce7Om5>7QbF4i8`7s8%rDN-*-kJ8=& zR-()XsqjdYRI6C|9K%4R2L{^ZHDSLrazTEm8x2s~!hbXRG5(`2 z`1oI!l+-3&!qWT7FJ>1-=K4q$$RZvuGW{*$~HVXq~Cv*F?$45@sS`=erNt)*J4by_MsV3BH-NGp*) zx(rf_5~=&{AAz~9+BRpwAa&|-|Bh+DSuDV|8QL};Xm>?SyR1fD}$IHagkW)CJ z>N3WHFV4v}GMYMrp9Y2MF=>7u+d4^=W!Yl>Y4)ou{|=Swij0|qX+6%L!lPk^MAdAA zp&|a=D)BE=jdw*x%%+awGER>@u0?tKOZI$!_4&rC{Ai8IE5DN(AEd@Ny)M3q8ec<= zzetWA;237w7*Twf&Z+cyP6E*%tHU1*L zBPP`U4wnBCtOrv4Ul+eyjW1KqK^q^a#%X{QqrLnpM+{(4b~xFXxB^;1jZ@V1}U zYJ3s~Y{fPH-03aXcp)Yfwl;Am_UbDq0u*2CPjJ2 z-;i&s5|6G#pc4c-QJ|9qdbmKR2=s)ibdCcZicX&#T3yxC*#bR7pmPP925S1}X^f<& z-2z=A(4_)hCeVsN)A&p;|AIh=U=XX1w+M8YK!*!-lt9~bw9ER!HuRq~ZA&@>!^Tz~ z4WxYQTfbvpY%;?JlYIOY*aPD?EVI${7vIi`p--LZ_p<_-{aCd8(0UdQRp2cH%wOj$5#wjRO6jwpYw2y!Ps{Ufja%%W&#{U|+xIzcl-^Xybp> z=WqM3KWaafNq^3Nnbw@EOiI!09qXJ#PpC?B7sl%^OQ^4G9nJHrzUoa)?s|L9$Cm&o?wr#U-E&P{gxr@%Wq|lBGA9*|Md1m`3ch57typz zsh9uT{!-PCe5R(K-d}3@C7)Sce)axS@%Fj-T0&K<5c`fk3-;w3gmRVAZ8pqT{FZN(H)1pcR3x5a0IWQ!BedG#soc8?Y?f?J0J*wkb zvPZ4I{d4xH_TOgM7|GMhCj@kL<)hlB0~%a!pTBQ^*Ug`Q&maC?yf`2KL;Nmln?l~6 z@(%rv+S7IUNeU6>Q~%BOg!)LTx*ovhi^F~XkkR__G_6PI#?zHm)&pGDJ!`c751Sf) zcz#Rzn1%BBdzV*L|KqZ*Sgqxs4*2}h`TgH5|LS@{9QLFvaX4>7n{8oiYpyaO5$zvG zjp_2u@6rF@L>*h-aNsMG|L5~h_3`&wulS?=jXGXX$3Ut-cEd6I+V`Iw|NOV-mz6pC z@ek@NCtNokB05T-Z2~O`bb>%93Urb{4;Sc^Dl}U!n!q5NTKyR7q8X#L;M|FmZ|{{7F+XVmtp&S_M8$rIY^e|A3e|8{#u z|CP_}-M#l8d%uCbtM=(&HmTZYmSCSWchdJS`uUPJf5-t(b@K<+_8s8(@7ur2I%1jT z|MuL#9xy-9;&o^xViN zEzYM^dBeT&DxKn`=?s-`{sjA|GN0!9@I7|bAO4nqrRRUkzA66iJf6V%`66x~Hacb` z&Y!V(*iV(*VE~G{rV8w zPoO2moC+||c?+ypgQim~>b8U90wTQMyJ2G&I!@BVwAvkGT8+~!?1&4tybYM9@&hJ% z|4Cz=e8{6wiz7(osS>|@leUG;>{m@#OFTqVY`tOo`^R58U~oGB^T*D!Y4)B2A=TNtHpIMvy^$kj z#-4!yoA0RYK|LkH+K1kr-QWSc-k$$n>v!m%Kjrr3pf56t{vP`SpYZsk8^!u8s?7R0gr zyu6|t_;cv{m(I6vK6(yM&sI%(!hGOAyIz6({g>x&zvut?NS8VR^?c4B^#9^{RA$d^ z=90NRtIpp6NVQ+p`TJ-L%~j&X{(uzBG_!7PrpM9hF zzq);pJ^s1*Enjct>$xQF&z-mae{6n>{^vLBm-b7TrRv8cRqeIP%#`u}w!QxLc*bS* z%;ffIF1&$#{#pCO`m!dUeg9D$&;MEZWPiW49**{h?v3?7;qL!I{-FRQCd{|L6JB-x z`Ul@H`n&ln^mmmx)cbnpH#v-O;8*Q0a|Jq2pbG@rEzp0g|Ead_83nezOY5{Hc zRnHHqe`zja6MdSK{zOq-_T8MyYzHhAj+tIR~ck{yh;AvCzWJ-wHD4(iu&V~O`ra&H{nTL3SNEe z1zLWq@^Aiw{NMaR{%wDdAAi6359$Byb^PeBavVVUcq_))Ga%0OP>?i!D)#?7?~BUl9#jt>L)8p@-MaK} ze&h}Ae-f78*#G>_ewgS_{uiI#LU&d*HM#=pyMY|nq`dJ)q9lk?&0)_4Al ze$M`8{H3y5&Om&7>1{W@kCfh3{U&6pk$t~LN}m)&-)95C%D-p|O%CxlVCj>vwo`6&LDkP;&K?ZY1^ z!ne92+;F0gvMyG+^aQM6h%p@x!k#QH{Q2alEm$D)92ZufPkc0{+z~a1_g%zVD-$miO~7pc-+G3y2;cU|RmL|Cl=XC|Pp!cGI-A zElQ}c^yO<>UNc#Si|UCg>gA8lM`i`=tsi_xX-847hfuFmMv+)tMZNAK@?(`U6ivaO zx}=JqLT-&N1gZO>D_NeBgB!OB{%7%E5Os@s)yI>^h^I>ZIo2>~sra5PldIVot5jFB zX0(>8{((f(@+PRr$o}{Xk2Xk&8xmZGg;%XoV+#*ZrN;XGfO=IcHBR1*T8(h?dcFN| zRC&OWcIVROLq}}Y8RL_vs*fFWTA+5e+lF5%WQJ>T5F**HGW!mY< zlj=@F5-hr^J@O9+#>&6ammTpJs;oy}!$&poM~Np zQ;dyrpfEXm^&4~g{bf4L!9+n)4kR%-pFs{upvR+B3Dm`s1p4bhWjg;OIcrMMfvINJ zrr;A<(Xw$+&mbwfS5Vqvv>kJDbT4z-kND!)Pwab?E-U^Li$Sr`cNOImgT0B+yjdN9 zdCQyg8;Ren@~iBX*~C3qowfMd_lgEUBi#7gfhbYv!5p=&zNG>)7=rL$lE>pOZ82}E z%MZR%*wvg`%WEI>VM*AaSD!!oJj;)K9-mn{!;1Q)f%t2_^!dvKd}8v_ZR1Q-$T6}T zfAfp!4|iJJ!$6sm`vyp^w4n4$L1S+|w>BVc7|hD9&RBfUVErqu(S`I2$XVP?7siyO zmtBb1A#YD#i(zt?I??#dVVC-2PO49pZ^ZdsQ+|zeYyGo&1f?!BEstq*Em@Aa=~{U% zj8bhs&Kh!$AZJXYE5NUmXJ@B(Z6LXN1<@ZzH%O))YsK~{6*NT5ZZy-~D^ z31|Xp6|pK_DoDMYkKtc5B*!%;PKJ)w{rRN^J zVg8_up7YAP%g0$~X!(e5Idta09G_?TQ_BbIng7qo2M)~+_zC)TRLsojm-fCA2nq8E zf^AJLzUBat?TevdP>l>+5~D%^CqTaAL%92FpK3at-Xg-iPwlFZ zRbP7b2K8CG-TTg1+(+bld}-tXiz7f0?hE}^plndhh`DbaXtG$u& zF2dg(Uu^Xe#JbD7y-#&Ztr3v}t`wZ+jH$(Srx!;zh<4*vO8##zb!JXg7hBwpan~p8 zWcfF1f5em>8WU6IRNXIT3AbuB z>imr~n$ri?3GL&HMdYoi>U?GOEbL`U!G(*|vkV!LAgH0AB^HzaKJWQKF*nK{d5*s# zZ>~RM@%ZA_b!8mQS79ukT9Q~DDQ{h0+HVyPeR$a0SUj+9wHp6Is<5;qiBE6_rF zwK`?(ef!h$LdIo9t6P3yYfI_EiG2~&k|jRC=SL~7(DFrcKK+o zIHB%TuI7et>( zrhsb>xX+B^{(eU_f9(3=&XICwBM0eU$$g)1J3pW&pNe+butS#${l$G39gX4-nZPrct=j+gwuoQCb2riJ>aSR_meK=9##0dnM0zBu9JyU{}a@K zu)NSoC;JFhJBy1>^ z0}F>6Nql{-n7F@yJK6YU+R+E9S66pZ??IV_I?Yt2o}3yi_sFPAFj?DH6~T6xV2~O! z9cGE0I1*g9a9Z!8w9*u6GusL%zqtzAeS%+ulNw6 zC7;|Tx24e1H>4$cd4bllE#2m|^ebxR#?xzaMV8HtWV1p#c&C}VU=7@>udGp5ykcMB ztl!S!26!$Av|ewrHge_Zy>QnO21Rq4VzIVUIY|C_I$9K17`+pw@WB)X?puK1>0u6O zPK*L4f!9bJ$uF4hSP|Z$0;eyZt8??2tH)co|6<)nbJ$~HZqG16@i#ZhEzk*24=g-3 z3=e_m;7V@vtz=X_ah`0JZ#UzxcKtRi-@eYb&p0OE`WFsn<5<(ZO_l@r+oo}$u+{2e zP|`jVMXeC|oYq{q;~?XYaLGjHv-bbPV?hXyT>gWa#vLV3P=p-*4@`p9D@C{-)bE^ofl2Nyjz6jjI)vn0 z$NeJ#)jsMvzB7@9gkOo^fN#sQ^@L2ixMhodcg{vCa(lFCvj;D8J%Y6F2m^Wkx!+iN zQrP;uA<-EbEV`p1);ThAwevjC$SeQC@twSjz9BbyuXRqOSUCCD02CWzC}ga&3;bNz zK$A*;U?e$Xaz);0X`FrAzk$UTgi$ zelXJ6oR4bNg&&C}j5SK8giEQM2e>(s-`Dcqv`Vg%YN^rOUT}LpKO{Cs4i%aVdoApH zOYYB!sCzEC8Q{LyNqqNt^dpElM%qZe7x|i^@doEj4Qgbc*qC9w0ynXWPsz1jrPV7% zbcGyiIr*96(1*vy6icO$A_YH?n2&)Wz#xRIHC&Yo3*R!%SCBq=QEbdhg!8G;DTvr5 zpIw6)$lTSN+J7FkG-n@v3Y`5im9(GLrLFx{t&$eL%@gWPkR!lYyVvI7pXCVW0~%DU zkn+RH0a+%hFLFOqveHoC3{1UGWE9-+v0 z)k&n>f8}2yzJ!^&xl*p-*73vZI9^z82nd?${yxr6pP}yYc}P{|ium770PtFeqtT;> z`O#5~3!#a%rtiOUC6aS;Sl?k4>4R#M_>%q8N`VfIJNSqy1~WPYS3wXEp3eHIT$sS5 za6|eBs(n33R2D7pyysM=j(;QoX{^s-erPa?anExW5|vdHVDBxVbMie?UBQ5ymeULj z@>l-VS41u3SM_QoIU7tCM=OjwmO%-=7P^!>vpo-zDUAGWE~WQ)xmMbT zHsxk5ms!c-{gb&Kul7Y^fWijOK_|<5ewp^o3MMeuSM)^vU@l;TxL*Pa4F&DG3lUuD zk}C`7H+@@nK9%?0y%r&MY-!zk+`R;e;-f5Se$kjmni~4(i8PlpHa(E$H(AmozkoDn zcL;7>Ni*G}OSLq<1G@M^^1es9d=t6|%)$_Q==1Q-qstwO^2*Z-oO#kD#JB-j)V^kx z9=DC%33{9(th_y|>^wc1&{)0FLmJ-!JvNP(_q&xImzy*RJ#L3ZGxU&a?yQgHhP$4b z0xcmIZkCDW@I*!7e-bh?PuNl}A~jC>1afQ|?%8OX}W zfmsg7Kb5(}M4S9KPJ>*t>1$0pXu`2+!;R;VVAg9^o@_ew@WMVxoJ+ z_`pDK!z*)YWdJgEuV;LJ(j$K-om*&F@NGc6yz$+g_|E^R7kpnhVTbq*ZQMin{)~nN z-)j)3J%I16>OgpgF;9W1@!kB*9pZb!*gb^rpJ-U{{WW@g58!*x2fd8%0XxQbynhej zyOf3n-d$5M*Bw2{vwOYxbN`}kA;DHuHwf%0{5Nht@_G2T3E%Ai|C9D0{S4(Lx?y{iCp+&7xpSCq=e(bFqP*|XDXY$mHM+Z(alW^Ue|nqq znzROT-^NaP{0(bw67J?2z#X_`OxGe^T(bKF(-R9K^Q%`A(#cq5JTpCIdHj7s8+JIW z$(oWYBaH9*u=O;-qffdb-Q63zXiRUzEAy3J{c`EAEKJg7d3p!@{R)i>32wx634iY) z{NLLV{(Zjy|N1?NKaC6ize2_ALHys{5&j2?jOCbqyYVM@1j}>3SWolST^)ZK7yMVC zV)h{Z=^f!eR0(yj?%GA8_ay!_F8HT0b>RP{t=f+K(`VlC#7yZ};QJo>L*wh8{1xIyccSPdh;UsGZrLGkdC2AIth05{Xq|9Z z?|8Q!*Bjm^N~smXO?p!q!fHB_8h*$-|Q9Nf*s@g?6JEY-%}u=;5!L@ z$r$mU7cW)bs9D@CYFtAB0eo3J_`8mTLsp5h%YQ~f(%kV$jl?HB5|h_nsai#lB?XCd zn5n<351dNQrVrcqsRu@W6fMhocS07fRRnfKZcWWdyg;dr)xRq5i9nX=#tGZYC2A}b zIII6Bgk{x$=E|fiS-X?u5icD5SK(#2sgw(f&D#1(NzvulO|!P0>ujUP$jgTnIA-m` z3b_cm*FYvssXC{sI*Cf5LUy@0`W<*p2*nM-60L?Hf{RKT0zFwS2wNIWx3aUdGw;1S zNIwg(Qr~6uT;6A%O&c1>GKh<|>vVo0-7{8icn-ue{^qZg(gOeeUya}S>v#9~?MI6{ z8oxr3&ECfEAEUl_{JyX1{Qow7>!o|f@4KE)Wb(ILvXM7_UphYDRljx-J6XS4vQASj zKdkjFCWDxkF_k+EY@IW*H>%VCPMuM+UJ;z_UXqDnH78bXFJvN+qFHju2L*&|*`FZ!nZ$9|1kf&?NZnD$A z+AGzIuidAzcaP|efsch|qT9VZZtxaet=C<%{M$W#^EDwq{96aEyTkw6PE-f|q@>i|5@v#UoiyJsR=}zL%qWJ$WTwqxU1+@a~@U4~k#y zSxCRSfAH5=gj0I=57rAm_hw(Fd_!LDST?Iwo$j*vf94vRGb~v7az3&Cbos`$!@-v>6mch3ui?3P_kPriDU0-TJ7u^=+9=LM0!KQH#A z>w?HRgc(>6k;!9F6h}^gwuPT*!t8wJWm1wD(ha zaZ~HtVJl=iUdG>Lw?yqK zvK&)^m`~Q_$eX_^K<;V0h18M0?htmbiBZhvPYRzoNIqb0SSlT!?f#beLw3AmQ-ZLg znv|%sK&~ha<3*U^Ixg;rG??~P?4x`?vLADVZ$t(QFTKQ|k`!Mi>8e5Nf>M8bP|gOB z?JeciY#j8Kh7*EaLCE|51oi!4e6McuaBf$PNKSlM5(lM|kE1_N|K6;*KM#EmKbu`IRruAf`pYtC^o;3Zc_@_m)>PW{Y z3WBVWI=z8od*sIZ++J6mhfImu8P$zGQZtWe1@4m-;?`ux&t;(}|5^6uIR}D>nVRr` z`B1_0|9SY~-~DBYSu`*#C#QAzW)5g#aB57eG%Rdm&sn1aRMrc_wxy=}$dEN(uqLgE zUsUP~)vk#24O@+?q^{k#Wf~tWcZTmR9F*-}Xy7b>P_l`S+}Nu8KBsa>M~83LaC>T1 za`o&%>=28b!2aRLIBTjR0sEEM1QFe2+7m12B<*m1j5xT$EA8rl5BFVSX_EJ5+EXi3 zp;aa%asNZPbI)Fyo4dh7&h~)uBp5Fe?LuW!_B&pAQCmpINucRqt%BpdM9Jv1|-1Q`x=w zq9;HxLtaTrd~;+fn|J^%zRsxcZ!9=VEK+K~503J&jikk#QGA2xTcOS`3j<4PY$;TJ_z|Xl1D$gs>x#81AEvq{7U7%?4Q^oqn~PE%tb2%2sYYm;K)?v zIbi%#8ZJ!#O6#eB>ZdsULoa?Do*}P`(>GV)^BHRM~;wv0zw#_xg)!RYCl)I1|ESA$~|o&Kl8MRR%L=x0nFDK zxjMsqSJsb;Oqn&Xz9Kpl84yNthjYdQ<6GYl9g!(oLrf^cU&exm@JZuupKB!l!jJUc z8TvFw50$eL6f44CnQxy`--2JuayP~Y{wdWh*_f>-aHXP1N}Q{xYFb55puVz+yn5L? z(ixp+T8*WY1q(u{DZYh`RrQ@l;%8t|-C4a-_H!L08>>ufV;;5I8xosmziIkjG_5&o znkfvY#`=W73mb>gQdtOo-JZN}hJ9^lJkz=c#gkFj%i*oJk@EoCOLR%YVRL5R_$WApK|hC`kPjN75#6PdN=}M`5W)_ z8Oansa3MUc9%e_IZSLH$02^KaH`_Xb~52}>CI2)X?F4}3v^Lhga&&TjA`^otEPkt`x z3n=Llb?O)%pn$HK&3}%6dMUb=4JH42;A*$~XlouawO$Fg^yKZU2GFDrX~% zYb<`a@-9KrdY|2x&e;@+^Bz|95|uGtIpwq$zrHez`dUN9<>9*fDi`s>h(E%=vSctS z%+AI$!hCqH>>9f$XRnOKf2jNswW>FRMRu$FpHXl@nmp?Vn$NLMv9C8FZ=uBZ_@RrP<%-#FWe((W08P&F5u02C|v7N zd4cSaa!-S)(F+1_sT_wQFwVT$=}!bv+iMwItm2izFb6=OypeTo`n1clM7Lvf>0C~^ zX@i~BxY4~z#-3fcGPL1Tpr%vt@VWGh^)nJCKOkWL__|WKXOxlrlQ5Qo!?@{j#bhO$ z>)6GKG*k#v)HGR-Ga_L!=H;>;tpvlCP+4@Xein6Wc2l>ZellEdVvS}EjIVEq?4ukt zRI&NV8s3@KJ3+0k(Q`fMpHROxrGNDIZ*~8^O-A6N`Ij-4qmru;+XPY^>8EzTE)~?R z4gt{oWVKmzd!_8v5&HJfkJ-=Db84FthAY^V+-xLdVt7jG(&G!7YY(D`7qG9&+z9g?sI)caf*sY0`8@(`kulxkablz7ERUP9|` zR51YDkZP>hT;z)sn|9*^qAHqEUW?qrg~!=4dE#N(%4U61FwFkP)=8~hm8~7+y!w$a zRpUm}p3@SxFLCTkUX>Qh!uE_6&J}znEpApmcAi(6Z4Taex3u#`H%XsTb9VQ>LCG)k zEj!QOblw(RQG}23x0^qp+jE$mBTeuR-*0N{?fYl8|9%Q)Vb+L^@yqY^MGxm7ktJs8 z@(LNv66CHE*L}m;?){Gn&uIQWd%0=PTj3DT*8RzRp?!Qz5ahs_MVu0FmouA}NZ`E= zvvT=yKNe^B@W#d|r;O)m9U$JMU$AwyF~+G$d6eq9l;xx#cXs(F-caguY(8yQXO5Yv zi_R70u=Ne;dxPWu5T<#GLCtS(igU6Wu^RbU{DP6&PcUXyWM&PiE1IRH7vIq>Zuo!} zucO6JrdOrc=ZrvZIFX~iNXo@*ops?vmgBPQ5*eWhJt`+v+*&b z1n|fK3Z?~pf6J*T1<|bk3LqTrCQiQ|f79CbAR{neP5P=^R5$JdI?4L6C4V_C7ccl6 ziz*zOm8~xE0x=V3yVq`F$g=GdZ^(U5w|TUdpOY9vrGq$}XRY|IY5aTP*TQMbossA} zezWiXYrwcFy;hwkTKdo-eE29xA|1y+y*E2^-#UPS{W(Tuk;q%FKX<@}Zn~fgyyAhK z?XKLI2d_8J6Gyy?$O(IS?Kx#QAL>Alh6X&ps%d9)rbiY(HQ$$T#3zvv5<++;jQYTh zAtggO@y^QwK}gN}2wiY+hTL^p9=%^$QZ}5Elk(`<VoS7uB2AP>PBd$u zj9eSC=MtY;;~`257X_>j1c`HHH!%kbYs53V|7Gf#;h%#8*423E@6E0>eQ8|$+Lyd= zlQmYR=2e+_hVla!neorJMV^z#@4N6@mEyNEfhEg7{;47BMgQqdR-3o8A&&ve!?nwd#EigVdD9YC#-gyQgYjY-&CSkNQ zWK*=b{cvK1#|Ol!H#ZI{i}bO_msyR2LiS~);kt*lqo0_IHt>QlS4#ZRNc@cw6+?uN zHaJrNLKMl}s^0DDt0lBw&=#mXg2+b)e~0q-lkl()@RE2s(q|eWH4!QE+m{WJ?5SiM zAGl-jX!G!wdDJY5*x+m=PLUs+g;XlXS;$`DfPGDwA1WKSd{3+f!xPyHEDDX}bv!E- z9ZYTx7=ew! zi}~Fkze9FlKpB3qC;`f9RGHNhYTZ(Lzc13i$=)mew9oorqFu}ZBy7ZJV%ACUVZU%~ zo8P#tHBb|-Jkm%s)6xBf(E*`&=Qbnp0RY6;e;%^0A3(&7yPCt+ph|Th3CDZ0sDL=Gj?PDz~0PwS(*q~{I*)piLd{3dYEZxyrU3>0X<;MBN%JMI`eN1mN8g= zq99;x3|b%Is;Xg;q#79nH_2DXhcF-WFOT`!z+9NmNN^6Ia%TQBKruWmfEGPy@OdGSp$w03Ka<&} zXRkzx<_+*~e#{q{tOhsI1eIpb4*NK>A)`(3ku*zuhaV@WibtEZdfI6u7Rb1(kgoOy zIeJ89FHQz8h?QXx7p&c8)pr9ji(G%CIA#(>S^Xcd1``fY@&Wn6_F3NOaPBR#ksBOF zCS(_dt+i@=24}{njl*r-6IJKh9(RQnzn7jra!-e#T0PcXRB9wUh)1|`{?CPr6hDRH z+qN0W2Pp8y&%G~$kMN_agYdbxsL~jX7I01nuSSW|Sr~=|ts)sI*w($6O6m)DI=}o6 zN}R&KRQ^>`b{JDZ|HJa=J`5qG?3Uj{sXW37Abu5RatkE@kYz0fR4HtqTdbOvXRoad zpyZEl?jr&`|4skDR0mV3r8wYuP4WQMvA3O)WT)(nx06yGvE{t}Cdarf6DH(2bU&WPi|TngM&K3B$>2dUWwA?$eA>IWJ7hN%n00biMsl%KKoKWI6rf6#zh97m zNE()$DX*;mqA~}GUabIr^zNIYcf~hA?@k_BM#OJKRx3-CS4{dq6_C{H4$+eMTfWF2 znHq>saG=plm`s*oKl6F)qOo2~c=q^|({kQFTR8~X-@Sf%oTfh#iC zzj-{9|Kwh(kH>&JPd6Bp^?x_RCuY$1`^_`7AXF*llkVkFl9Pg_Zp2cI+XFEa7~ zD}glAzKE4Nv2^{#6fng7X-!U?;gGYbLJJ*_O^qoaA}d4U>_Vz zz7lOR?XfWHY`W`+{v&Lg&T9a{KT)@I#UKKxv@Er7rV)@k63|pEIb} zmb#+uGVkqqw0E6Q$w(-;1&b~K$Z~*L$C2F$0huURd5ClEtkaoBG4dJ+^s4&`Xf0xt zO5JIB?bEiqO(7iPYxTJr!c70t6yX8}x7+w%Rpy4USJ`@U25`HOB(gU!l)1A6XWieW zx&FfTL}1#&G)(j2U^N2)x5GsktPaz9*0fKPfkXamKRniIBsTIcWQFJeCnE$mZ;ddw zK9#|EHajR`JH(vPPvh6(*TPoTM0E5x21v~xYx8;1sXuu9D z<(CDziqTJHq9DDV3>I7E+r9kpTFlP5G#Iu|rMcBoG@7D+QPj@Ie9rwVA!}9G=BwH# zjKq&94O>qL($uY?u0UzpCzpkxQ*t77=Ak@b_rpxd?5Zo+9wUiJOoi1?M&CeG1@;Kq z3Dt=%;+!QRHsGENxTltdXi2~hTPsCW`^8V9TcN2CM(LAgx>d*|#_=maK9GGKGxB}4 zM0!ev90_R#Muu#(3)zGe43g^qX&Qui=zqQZl0if=L>m`-k^BV{Jb+5{{VC<{ z2a2bGKyJRduFM^)WJ3lL?e>g&ta=0v-V24?m-KsD)ePe4M<_^uYODKDUVYfIR82B~ zu*^LZT8Vs2EOW2S$x+xESE@V%79KPBs>$J73=1BV+lS9&{uZBIu2GrCi#&a6Jp(?- z5BQ*?%G}QYz~iszbQ#NxlIJsz&{_dA61X-Y#KNpQ1x7xW3TE9z$S8qDT(v=lF(}v@OU6zZ(uOG_XMO1-_O5N1}D=@kzP@+ZS zEWUErW&L_V(mhBuNww>F2^{6_HR{J!^+wx^RLPqpZ@~N79udFu9h!^Z4f)L%$G3m< zvNAsU`}nrzSr>#;1K!<>0KTq0YH{A3q!5N`vX zPzAHK73~ehP>t}y_$q6Z*kjW&bN4}r#Vrx;`b*Z za-#mc!*wv#(=*eUp}!LSSNa`24)(ON^!wy{k!oRc@K$pxK1XA5@lOBO?e|bKV7(?4hT#!?5HkL}+H8I2@IdXd>qj)E2E|OvNDXSH=|F1is??RM z8e-e}&3s~sOf`nB!jMTEmmu$Q{}S3}@}0TY!%Uq@YQllWQok={u8M8#H)|hU2p#co z#qR)~bXyL7|73VuSbLl~W0fLgJInn2Pa{8%13xRD@H6jG{LR7qesWpI0)Wa-GVcm8 z??Q{LlQRvo{w;}^y2$f`$kY_g^(k38_0uE6>MuvLrRJAAkD(FhZuX(3J*hl4dV2H# zo#r#CTvA^f2%4qem%j=Z5%Dek<_D;F z%k5He#uL56CO*a0Aw4As;eq?D1`JGN15n9*Hs;dlLsd{3GJd)7nm)1l{Z5ITQv2+@ukWzO^!GLX>ARF2UFHK`K?KMF+qfkN zanlRD@wBhuko^OOwOQAI_q>(njAaTHQHPBckQPd>KqohE#E@i=Xdq>{p=^IJ_dNYP z`>b=t;1Zc{d}`WPG{kDgMr(58Ej%gb6E~JSS1OaYeyOBSsj^mQ3}GWR&u)Y(#zvks ztxKTPI*C{?ssZZ3ezDP$qL+~ENJFB0$NE@F`VQ8b;f${*_w0_A=pho3IY25Ki}#Da z(-`aUsTI|LF>V=(baB7Jf(IkgZR)QJ(Gijsm*5enY4Kh)E40Rif$^F?P^)lxY{QP;!OBNx#WB z*4rEFBf!c2Q(}eA~<@8|UYgJNv?G za5>ecMK2A?Hdi>%auy%*fafEOeVqy_@%mu2z&!E`Cy{b0n)-!| zrcSqKsF0&4>MMFk`fFMKPi~$!n8|Hf{C$<}Q4&9CWQ0{_fYtmO{Sb(`=pys1=E9Fw z0FWrcedVPo*KN)&OKzUsKYed|Y!vSVUTTUyWZCKxQ6it=@ErB0BDr|N`Y>J>o^`x< zI)Nl=*~wq;=YATArQI%q0biBGYeq&2!9!~d^o4$ZpMD>G+r-fk$L9|+aAKDLJ&%99 zRZrPOO2^MB)>T2K@0Qu8VMzmhe4HgctA_bI={$4jd%amy$Ib!gwhy<#PDNqgI}>TaIDnlh^Sr#~F#6iwg>7jg4#p>$yf5&V#m_&tzGW7R9^f9cU)@Bbjzi6KyE$aKm~oq=Wx zmIrF82r;8{L?eV*5oL&iGxZFt2?bgv>zL7;a z$!aG~Wi;%nJ19CX#-Ztm7|E0QgpV&Zk`3O+LQ|lWw+Y#8&f5YbaSLyk20hyRR?Py} zFRjjC(CXYoB9{zSC`$*yqhhlzJvI+s^cR$vg6=AWqi%siq~@IANo6{tc+* z&iO_{0vZt6o>EmeWrUI1TLA3h1~%aaR^kR`9NH;W;?P1}hzDos{8(0SaBA(*_Smwx zQ|Jqt{@6)y@SD`KPaDMU7aepmZixemTEX5Atm7T5lKx2Mw&?TvtBk}PMzj7xBXK?) z$mAe7SHBzYtDhAqtv}C5UP{5z=^lj^FbxK+kk#%yg^)4E!aK&|(%9A@NYh4aD>5@# z{kYgjj^txK3`g*bFLkC;jQ2WIiABW*AQ1jg{*&gU*xq8g*Ds}-P?59xozKagl7VQU z9c4fLjct;bBuzl)m_8adHO!yQX~w3vAq$BlYi5aui@?SfX=+KM1WkIaYct-S=Yj(o^w02vSpyii(=db0=YKm}}qQto(Qy7(b62lo(Y zDEdnV09yTrclDPUiKqC>u2BXt+E;LEj?4ul*3V0wQyDlJZF-T&|3Yd?Zzu3q@Lf;p znULmJ5~Ik@Z?qnmZX`~CzEW*(3D{b#>BqD@OHt{8T98C#h&UuS(S&;)P+qvmBdSix z)Nr`{^5ID6S5GXh_2BpxaJX@3tH)>bc*2crC&G4!M#9}YR2~>+$umT^cthul8p*zN z>2>&-I4{6yCd;BXA_W#kNw(;U&xSf0>ew9og>oM2Gysp18`aOJM^xf2Qr-*^gn(1ZkGhY47 z{4aK$*smi|7LE%WTFV%teESudtqs@QE5Dr4dYkd9zZjc>ymZJ|UMJQWUyrd|R;aA= zl=AvW)DZGq!M%%I4@ThnG3ue1B%gsg)6P~g8%0^qU&@GpJA9}hUQmBU^e(=^l*w&5 z8LZv$JSch3)}ID;)K_-Yv=7eO(O4bhekA<`?C%cp$3MrAwg`=G`33*-OfHz1EPi21 zWsRSkLb0UDe^VHnBid{dg)t1PkrcN<@VOP{<1ZJ1Ig*&9ezrN+s-N5r0}~xe&9L=> z(qi((fOq8iovb4J+?D67B1<2VFFVa}OU_=lPNM;j|6GnG(RgPl;SAhYu^^;%E;>xO zP%bAEmoaj_PFPiDEd7v2-;9(wHh!ygqP?S5l3^7+L75RDJtjep^Va%k&Ejui(J44Ck8osJfJt@V*++`n1I|*TS!`n{Fdv5vg-K;M^I|j z^A)Msg7uJs=at{GtcbZhlI8wS3~)wYk8bouf9RRMsCXL$SUKV$cp z^J9KU7iZH&$Z7*>WpyMpt*p-Xq@s{qISx|uSXGmYjoAkFK^EqJv-LwrR3Pp^_ibQEV;v~)WBFKng~RwIILQ7t>9#}OC^nLnb$Rpn*Ya7^NbWD?d1hk@ zt#@Og)o%&u9(UhSJSkd6E`-y~$}Ay+u=C(1T~YQ^Zg0&lqf{XH-vEV@t9I$~EZZqT z(LIm%%7#is_iAZF#qp?8K^3>Z#u9uRgq&Wv6vFgT z8Ggs4FuP+c-gZuXQ&5vRrUY@$U?G|*d8{TFIYizbu10nlZ=>8L_ZYY6s*r}zAjD?5 z2ASi~op@yeqz-pzy-_XxkMtse%FJ7L{TpK0bx5>OQ)<5qo^E!((RT3=FKhsAic`lM z36iQ{P^{nm{IEWB&Sc7knpFDvS5RJ(^7iL1sk-PQmaGcQhj3;1?_ixBv6L%V;g0oUUHR@d>qRV?lkk&KL9WtB$QdH%y!r55;afN^LgsOBCf`* z1JqZ3Bk>0wS=ICM^kC*mEIl9xVlQSRS)x=9kanz$opI0mpUkI}$V+N%;(A`!!zzq+ zcSH@9A|6B08Vf{~#kUqlNqLx0oU`|G0NS`-KW4-G|b znQ`kh`V@z8+0v7!=6+LYu7bEbSaINN9`iU*rMAO??lj7qhzpgv2Vhs1UgY&~0rpyY zmh5yre7{7Yp$rUFVVV0Pwe^e9Ue#}9t5$g>X13%){V^^I@GoR+^>Q!o7R=#I-0{KWC@?RZYpwS4lq9=nQ9(afZ;o#{zQMSo%{8TL zky?eyi>Jf#{aKE<+Lu@vF=AUQBjEo5JCJIhoBsxku9!T?m*$KWjD&v}m%GanXp`{x zrW}u(?VtqMMN^2!NaEOPmGv>D+})dzz;0%V0r@0X8o zA%up^3|+p>*gFYJ>BfUbvRxk3auaG+#etb3rO`F9j`NL##1*A$73(2gAHBT~^I2yc zN!>}gc47x>>Vow~Y6ibi@!%oy5Vk6xu!1%2Fm#;&M-n@~PsF2St@XBufFZRqVO1JIlC9}iU4ndDqtT7C(<2nW#zd%Dnf;&+GHQw=Y2Hd* zA$4w*=N(GKNiW04GLmP?d(Y2u!#rsD$%~gau%027_|Lq|z-t3dS9tMIkdcySD93Hd zD00hR+U_qCQ{=u05nx_T-z;%Id`(ECm4NJ&%M5arf<*FP+@A=9qTJZG>--HL$J?)K zB!=+DP0?=kN>|SM5L#dTs{CzrAMNszueHw4I33NpgtfP&jY1dJN!W2-yG8(! zi4DVksLm&1|Gb$x8BU*o6NzZZgj>O*@&nZLM@73>ACGWDGm-PP&Z(Gv4)>E*UXpcw z%3?L+B9}QeupWG``Xin1VaeyDySRI3e5-HP!8&nQa_Xguz+thMNX@ip>K1!aP2D6k z(T&oIJt04FVUi>+d~W(82aKZnOqA#AtBK|t+c&hC0h@JkA$)M%9lr7S#3ygNHgLOLYOij9)G@;zv*>=5VcZ ziLi}Ys)kar(IL(Tkseeohb&;PbzagdKPuwvX_>Xo|-OrxSv|HG%?pR&7GFNw1w(biGzICX_oH@lwT!WSw2JNvkvWIl2@AxpBcgkFA$eIpZ~s z7r-FtpDCjcMK_60N@K%yW6rVJmTHBfja7Axp!5=(T=LDwB-F!*C#qMs2cZ*ot`P-s zX=Q19uwVp_5Rpia5buY}F0`hUkvl4hPvyd&Hq``%;R*&eL@EUrpIr(|{zl5A=KI10 zs7`_=iPh|TQO@loc_a@+eL=R8v3T``R`Y)DHGBp273nXK7P{vWqRGf59B|AE$ki_3 zLN&9LR6=Fuj?4S}$Q^wCKA(2|Tu^sEW(JYzz2#-0Qhp|**Y!E?`3f?+bW)X8q5?y_ z(JXfVme<1fdu!q0cYAH2pLP({f%m{tAw-)Mq$8BGC-@FzIdu!pxy~EJgeMXi(;riqGi|U+^!E~9k{Fw}< zEAx68MXpbem_~Z?eMyZBJ7<}g z&he@%$=nV_4kqhNN0tK?)vQZu*i~-2e+5#t%&bCa2^!9*`Pr(IoF}w4Ocp1wDr1kn zCx+&n|1_N|M*1%8pcsp~z^yF*BAiGKyE_q6p!_?@!c#}aNOav;T-A_Z7W5k#woG2< z;){G(04Y!2L%H%KIb%CD;HtI0f<@M*Vb5ACTAf3EE1uoLsTecRK>b7P$6gt-OB-rx zBL~czY_*Z)`-09iWlw|Zx1r`@U-S*lu5;hmo(1x?$T4&qW@3x;$H6uFd!UoEMmhD; z{VtA3)_(_VEWN1Ei{!k_nbGr7!Bt}2WSgmQ=Co&(lt4bENH3J)ym__q2eY}ild9N? zVkG=jYA}{Qk6JtySmN)nZ^bObQ*?*Pn^@-zBl!jnK|}2GnIs1A%bpajZm}O-z4e<&ZT<&!_Ck>#jWMkeRSzoRzG zMce3 z2W^&AB!B#^&zThwC)mdyZ~GcAg9p9G@r|GR7e)c9e;eV^E9eioTkm7?D;kL zh9Q=1{IRWMCH$Ilei33H#Ds@m6Nu~bOFYL&M7&0+t9+%|`g{Wt5p))DQtJ8RNJ|Q-TbC+rGsSuP2r_ROQP$Y5ItkyG2$!y}Z z!ct%K51=+KQkK4NQGClm-X&}C1?g8a z{RX7tbW*LyfpMRJ#o@8)+MLrJpV;JNF68~A+m>Qrg-Tv+GDlP-ESpoWf3I|iIdFMjs8dK zs#EP0zbkvf@W~ZAz$5B(^Z5OWqet2kC*QDDLi)1^3{EXi`F+;-$&wL1E^@FnVX`cH zQeR$4QD2=Jj@p$S49p*{^PMTjib^zaQn?qESnCV{PgDlk{?7Up{8XEPf{}x&TQ)b+ z>F?}oCZ|s6Oy85!hu%byv*hyV!~T(pu(h|t!nmmq?3-%7hY;GV6YH-a--u@+(QCL% z9?AN}YmPAzvOXt~5apqc)p?#>*11y6kC2OH+;lq?C6h@q28bmmPvx(%*zc>q)JR-} z<0SbvW27{L>FBhgE@DfMROPz!Xo%`BUx9&Yu zQE_NB4}u22SCTHPzY*5gH#igUPf%$|b*fV~6$xfafGC>lTFE)m*~XfRYRhpRyt5i) z55V5`0fc6%;~nLZ5%rBmLdMHje5NloiJ4xBu|O7~IVawFQHocaGX~wscCF6!i|lb_ zB>5VP_qC|Ucd`jG))9y-7YEBl#{Ekukzr*BgR+*WmRSBFL#;Q=rRvohBDuvKxv63h zL(4`?$yb)CB2vRSJ`7@rupvh2)Iv9<+GdJckp6{Vow}UtDD;?382AL}4QDIrYlFzPt&bVLr?FC>B9r2e>~)<#3)i8x!jZ zCB&$fYJMixQEV*G$4j*qrp{PJ=**+>+%XyY>NVP~n-LX#BL@p@i7c^mm+lp9-J|J7 z+3ldL79rQ};nH!2pM>^8)O|z&LQ5G`U@FwG%o+(DS2AyYxaLIxv z*hmvk;vdtTh`al!{MPzUv~<`=E(a{}Ih8-kGLW&@^u@L^&H5`ZVq3ozi8|}vk)aZw zBXVB6W+(^Z3*DJ*`5zoZC1dd@_VryJ-QP@^KL3L!N`u5Rjm1|2^$K1Q*X&TeaB$-x z+4;i+A}-Gui>oE_AeAC#v49YL+tbH@5IHupIECJwRPKac6CB0G+>3nk-1Z*P=z714 z==H20Y8DFg50lXHEboW46PF|!IrnmN#wL1X31=pIXo#E?w`F%+PN+(D&TGnF(y?X} zU~dh`Wi@M^*~_v3xYsGe*8myNL*`)dRwdzxr(unzOTbfx5_pJLryr^AOy8iMa{gr3 z^}ie5bs*6BX5OufYbe0Mlf3a&kUL;MDjSM|9C(pDmml_C-1rgS+H5Si5n1$GA6rjw zNa=T3lVRKR4^7|3&UIc$zt%|J$_HMeqlACGWamGU6&+$phaLZNKZvvPOr23GXUWL7 z?6=19oZLW@pe5gnt`&DORx=~Izoc+Q4N7cw$7Im=aQ<_R&6oy00dphRO9IpZ-ZYLBvQ#H zN0j-EL#e{}jq<)0o=J>Q4vgyB<&Ku?!Sm>~|LUE{lB^%hHtQ8QB-!FCb z+wpCM#)8k78nG!jA%BGh$lZF;?SSuc{G7wbm3%tRTV=igc+MnXGi^m{|K`UzAoA>L z3S^S2x4IAa2Fx42p2tlsU?L4xcA$>d19i0Iq;T?OeI*H8e*-+syGN9lp_O|)uqeC~ zJk_r-BQg@@%)~s9L}ht^jM^DMbTcjLU_R4I3ESxIgn6noOjXWwy3TZGhczhTZbpDYatJ!X&6BASw*Mg@ zg>KmPIeap6IteVizN8?q@DY(0IgB?kC6Q0_*uq2 zXnDxcK}kbC|7#&JEiEF&VtDqqxC{rsAE2k^Q0JKZJ&%+O1%etLnWZw3}Es9zQiS+9J> zDSjOFCj;>W`-Gr7+gOd{)WZQqhvdWZlY`p?4%EszWq4#D%TkS<;nWc++z_4yrw3|# z>W`E52BlKd2oVOXN9twV^MvJ%dI(s1edXYJ&u1hsHDDZ#_?HS(r`aGC<1f78;V;V` zJ&eUukvkZNYEL(?U!V08Zy+)%%46@Y0;QIst5>V>NdFw3_$i~{od@DydO1DA8V99^ zXZr7{Z}_0>;9cYcdfq#~=ZNlk|8%vc;}`UQ)o$qj@jq{m`JkQbzX}oCB{=~9j~>YN z|HvGL|1|@8>;F=b!#`>{e2wFDNkXOn;a^z}>HqJ4%Jsk0qrcHx|K0S!nB74Cv-9bH zg&zOXo$Nn8WEbhL^pG;nSz{GK17u}#SL{E^R>zFQ=+4nbcaHN;&2DO5TAiy~o$G$| zG5RYthSGx*N6K-#*b^>j# zsbs>^_H{AS$C0*(CHFLMp1)aT+IPwXp>2VhE+}Q*d1wA4p{ujGy?Ynppibv{)wnVo zd5wQXXEx&r=A73tLfzTK`kTa0G3|TAA2HjNw}T|h?Mb-fENIGIQ=>Q@qq;@KD#P}j z;t7R0$uhgWpnW`ve@q>1BV>Ilr-GSQUykJ>PkuhJ*Qw>*_bdDm-D_HpNRJ%7&3?Dx z!_}{1&OVa!h~-r83XG=ii2HnmCp>30fUPcdrsH(_<0lo18&=9SJ^aahpq#P=0#54O z&QR+cB~8AM(j^y?U;9{)BYV}^hv)4UEs^0=Twjss3N6tU&bI+B1Rm>}YTt@3G_AV@ zQMW&vn7j#dosm38wls-XQd3U|RlQ<5lbI|#_mIyk^SKvg2NB4z#f7*|SWXw_qcFz> zhV65iEI0C85UXK#;G;}>nDeqb@E_`zL?wUEFZOI{26HBmh-_LfdrxysdDr1Rja2V8 zJj6T=xBU1;zhz!I=Wi;O>D3rO4c7#mjK7}2-g%B`Mcx28%XxOcCM&n8A?MLh);IYs zjpLgP_#(MgdgQ(RchNquYE-OzJtKJ)x-mTCB51`f23A3DV77}AKaU#U(bO3H#r9I^u@!@MkskEF{x^C3pRfDx zx0C%pvfr-vza+nZ?Jv#g`^Eh$e)z?{yWamH`TcwJKje%1SM+~!pIz_&2fxar|71=7 z@-OaR;s4Fu(En%mZQuXS`s+U*u#S!Ioenm4>MKroyRzr zvvw>osf)_NPlt(b#Oq)tEa3}N7EL+V23Vsz<@$JG3U9{U7T7;)mR0|Rv67b#ly-+C zA%_3q>52-M^vTVacclMWrufSGU-?lw=Bgv3`m!(`SPEPJ6el6X8NCwr30q_V5o!us zTQVUj{ITen?m;+8UVI@y0Gor_qxD?GZ7g{Cu!4dQjVlVQmj!-Oqm5yKL8B-!Sz8Pb z8Wl?QKY(zebYct(4{9KHke`rQVy%+NFVWceI|p%)&&*%tTl0_UF@WFH5Y`TxI%>Xw zq$g{Q*}ASIWdtL%j)Ri<9dit{QU~&ETSR2k~|UBZAdPX_C~mZNRj&~+wFpK zADN01llgHE;oAx$c|J2k%^w6IIn4Ua29C`DJ!t?ZP$G@aNw`?-=LN>D%h}`iPaS{47T2OVZ|yTxzUjf=}(uU+R!PCa zyTt`}pJ015{QUc$2p@RT?M1!z&GGa0aRB8%joDTEMbC%#&~yJUwx3F_SMRm!dPR;i z&tmTR4+rjK|KHgS{eSkOy#7yDnq;T__nUS@|5xVs@A)r#eKG!O{5SR6Rr+g|+XYt6 z>7996AI#Hw=eA0i`#n;4)uR}*n3TLii4Mx?p4HU#DBNH;@kltq|FE7yUDHR_9D0=J z{P`;}XIJ~sUv2){G~-e)450uS19ToQ${f0?$Mv3E|KZxSk?6-aU)g_B^Z6OL04fy1 z&RYapB`^GKT^14!N*|a$s_XhgzJANW6RKrCe=T2rWq8yYx@>Y+3w1c0O0(;A^z9ZC zO@TD|XZmx0#Xs3~R?p5YBBJP~*XGu2_mjfQx)pwY7=>qeg_(t7hr-=rg2gDFt%cxh_k-7RtS^Y)_|<#CfMO|-h3qnZ zwwIf|UNS!DU_o$}D#Y?;%y7p3zIS`h{svMFJ}xYtMh4$kvclT%dd4_10}Fg`y0S-k z>0*B)wRyIe^ZM1|`VNyAyva}c_jyWcr)EuE1JW`$+2x5T|Lw|^ZypNS8oCv(lEUE> z#*^ZF`jl^{c^hT5@(z^MBRE$?|E?)?eo5u{A5}e%mwMG-IeLU@(O**|%YQ_|@%+om4X;l1%~m$mB13}||6 zHc#Tq=)ruHM+kTe)^w%Afl_$2SD2wgp%k9owGc##7b!$abpDO=YHotq>wKy!9-_~8 z9gp+r*M0QG{I!`)Qglf-fbF{*77(!biaI?rO?TDyVil=TPC~Ep(M7M5 z&&PLpzQUJVml6xYcA;69x!yxaV-&!U zmx0Zxv*^oW1)Nc=B z8ykBb*eZA)smrW$#XD+A6qO%au3;M=W<(v$%7I?}`T&!P~*< zL%n&oq+Le#lRO1UrIh6bb+rilE`rt;#g%%~TaF=P3p7J+{rFI>hZaLu5c)974CGx1N^D)fl#W>4+^HCa-eQT$_djh(tine>hS zDtc~k9{fJW`F1L1UbK2I?v)pFq=ntSzu{6ntH0D8yhoak>gJ>FBW#P$q^oSaN%)8T zC5u4oK3<4l8UM7ek(>p=WgN@wQEXgDUc{s9@F4$2er+AY{$75-tIRSAI6Nlt8nRYj zZ6r^m_@QEW9CF?q$B(*f5pA2Q?F7a0Vo1I8n~6kEr;K%`k{#KXoIj{y!kamA0QSuI zgkR2X?#eeGC0^`K<~gi$*z&gEoJeVXvyptSLtO?a8ZUCFyOyU+ewgF|s2=Q@uh`T2yrCj#%VUfk2aQ_v_avVmT zF(;1Sw<>*)GEhUAeyAhMlS9-mNqrm0udh6+T{s5*7rU9=dh-T$@hi8S$1mBi(TD8c zVsD7MH`0+mVVy%q?hCTHOOH8+`F_^fxifs9x8cS;m~W8RvRD>s|I#^HRrSx=&>k1f zB4d=Wii50$?#l%sdklr$F@L*hQ_=T!mm7NA!Ra2|(Ca0QPc0)8MpSNS-B-d0IFT2FPC(ePi6fFeDm?#*yOp5Q&+u4d_FB-@fZ%bv9FAwWTEx#Zhw zzv>}^oBVe$j>FoZWNExz683$f&R)JtrXksL!xI*dew98luuzWCxMKQ7Gc|r)2xLgx z-O@gOqclYM)O9pOHm@V`0b|&KbWVz|m%^*U1oUnUo4%1=X8z2rmTqK=q1GcRrKN`j z5|!eT$!~FPW`Rz{tOBeUH+a?MJn7_WcF^yBN2S8cOVP%l!o8dDwH(rK-V)oIyK-jC zsgn*mNcI3C$56asxOgh zqf#pqWIUPGIkDEfWDQPDb{|*l*iMdC{#$ul|xvPN@DA^`H$d&UfhR8u*-GADL z6-8&E=Wjfu>#jE#N=#YGr^~?Na3Y92A{H-{y5;N3oSr zxvQbBo-cyM58%!E9xY=4YuGLF`M04m7!0_z`!i}`Yw&6`Ntx}UF)48h{j-C7?~aUq zwgpD4c3~%T^|AN#q$~QS%*}O@( z*_rnkVuWs(*tCT8eLr`8zjvgZfY9?AGY!goHFeWn5o<~;^+m`L^q{ib z>!2*v2OK2Cx`AT5zjZWm9t%up!w-#AB2^*i8lab#6;-m_P_4?qKa*m9hMnWf@uxyzAsxp z-El}m=iL4^dUN?rIT_`JD-=0N-S#gIBg=vm|V@`vl$&5v05Z9$2xQ+C~QZT zWuGrYai+G}h!0N~<3-+tcv(GY@qSloE)!1u~Lr6vznSIIsK9%-w0|asmYySh#r|4EKisBFEv{!hw($A0)K}7hVT8S(>E9&AH#r`T2~T1_o4O_KQ6U*%`YYHC#F9s=9XsxKFsby7{8JVGsr)Og zrACy0EVcSGY=gwfed*gErH{gz-JV$olKLX0M^EiTv16A!T7Bzds&_XfXO58fFK3d| zcsD=qH$P`c`^uk57nf4=)+|i?VHhCoQ6bvnq`Lyd<=7}0_dc0_vnh#x#qxjh1)^rA z;}=4ZI*%m$ksQOg9qZ{uwLPEgGS>x_G9VK@?X;B)d33T}7xQHTMkeLSxl8AS4xZs$ zq4vdT%rOopfnMkO?}>KQBOz&~gNI!HQHCC2mu2dJY^gsdyBl66$iYOO(W#sl{G_NN-pXZ#A#U z{XF%^8B#lXeuRZ1`pV2CF_k?dNvEU%OF`kuiPG6nQVg4v92PgTVKzmt zHXgO;HhG0O8N8BhP~%xD6IybAMLEJ$XUo8xQACE2vSyQsN7+`fMG7r^#4X<}zTgi3 z!SkdwG~fSK+1;{lvJ?{~f4@ulRBKH_hf0hCtU`w_GS2?yn@s<7wF*Fe`v?k?rONYn?!8JS$b4B8uBc8G9C_)he_z@5=a@33ph;^iLa&-%K8{ad_| zholmD7%(x%=~KPs*wObT5#_-&$lUw4$x1-UqKLj>B7DZx<<{PMvVny+vVdg|HSF5R zWQLsOo7z`I4Ne;8MlK$foVN(Haa5K$LwSQD)8qw=Pv1;Ylt5^!ZdE+Blj1N>w3cO_ zijSW#LSyo=f3qGDCzBDeAYm=1U(`ev!owQYiBjLZ(Tg`V(Jg#Z>{U;+Mr~jhP-JBqy0OE)YkZ3l*vg@@UISaluZYi7 z>m{wRp#oiI%i?bu*fQQ|II~{DXcUGQy>$GR(9-8tJ2XqRoR#c9dr@GqYH^J$6{8YpFGQWj_b4@Ac|tlj zxNfxCj~i(}gh3~4KeiBOT+S4OUT^fWF^HYXLHCm`c;bmpld*^N{bx}X_*9_#nSZm{Ox9s zM%K|@Amwi8U8UKqWefxQGHMxvwTAbU>Z+odL7HExeq@p=*#$afA3?uiJ@1?uVMp{M zp@U_MZZfrUs)clR-zTze3?f*DqS`LXnmdSnPd2|!TZg5TioY^#;KAydut&XH&f*9X zCC%hyxoMm|%fWJ8J##2XagR?XK2;x1xj)eVy-(*quJ-z+T=Ymma6&WZK_HT153$*+ zD%PZ?Vg)qEK+a{yss)_wQBlNa{7tm2k&yHr`qY1_VnT+A)d|+EoIi`Z^Sf=hS9tw- z71NC~RxL%7G(DW%pWsmstIC>YfOB-}#GHDoRu;!*QroZ>jQYJ2@iSwpiB8Ra>=O9b ze^Ew#W6i2j-&PJkb7e$oTTS^v|126B=fH+W=^M)sHLHPPd0XHI<-#T*e zt7&;kD}7!$4ymYeb}u6yIq$&FUG;av@5T<+9~BdPFR53TJ>!Xv$cYZum>-5CEPNKV zc@QUj1=sy%(GBO!k$g9>=F1<;)BTU>w@wTv01|ZhVdpNjUi< zI$}CU+&#r2P>rd`Y(2DVuU@iKzPB5!mu%I;FX^GT4(fEbhy;2H zdd(f3dX#xF&Nk=86J1s`%pK%kJ*%UHYG?QP;Eb!>`7=p2{KyV+L`3R{`0(Sqc#FON z4J_t}axt<;on!kyJ))ekXvK41&PN7kVc(rMsJJRVl5+7z6$5eSMg77#9~JO`G7cs9 zyz5;#MZN5q(6*}B+4Ep$M6uHo?!ze-AZ?FhtI1=EE2l^n82n7so@^rXL#h9f9uW}E zEhep6;Xq$cEtOOfBaUr7Vo#B`p1IuVH7?+Wmk!elk4ut|)j;i zdEGfpO*MzBH&VPT4w|+}mob}}Ynk%a?@_Cdq_}j^tw4s%NyRuDI-s(bw1?;aXspLr7HaNjnXK~VNd4yX2IJXUpF@J zclO*zy^MdzKiXfBn&Et6@f@6H(&@Ca{bre%q0ff`s^OP$TVk z^l_<2vAb3fp4Cl}9wmP3wYXH1=GD|zNn$9|+;@;7uN5A*^X@)A(#VRpD;k2rE;#{C z2~>O|(WTit1a^Eq5QVlZ)#E#sydjWB()(2fW(CH-5nUP`_YH_I`0*ur_ZjExP4`=#vn#Lni{Ww_TX!1Y}6+&v){dn~l6YCyU47fSQ zjgsYPjVGqzBFy_jy~&un4aQ_uT;l90fF`j#)RL}q7Jaj#-rsB-{JEZE2|j==tWgk; zUsGJBy_U0%q8&RpP=vGRCWGqHo3otjJy}t=I*zVLd91`A=drRXLcxpPPtGsP`O*ln zq?#qW^+Y>7<3-khH_1v?(U@{bXBcqF0 zbzN_4sINX}+m4ei-?e?82QO=l#z2|p%ABIErC#h8)w$dg`|XXQ-S>FH@5T1aLc{gE z>L1jH_9tv;tIHdXS=I+8Z6%{%(q1!A^>gt!(i1xB2z;$Cn|vdy@AYptgHv9d+_TjM z#>q`FWfHr;u|=Kd6%{S?IyiX2gmWbn` zrHDIlSN{XlqLjSDP(Pz<`=wv9J1_P)>v&M}=kR&b*1XWJqgyrzC8R6)k)IyTEpOJe z)c9^R_p2H2k9~{X?n1o}9n)@6Z_ocYYLI*RBSUG-J>GgyZ zTIexK540P z?*ZeOTh?Lr`WJg6*Y3S0I?kz=?_K;&S$^g07F~_Kd$V|Bi@GoLUqbmruF3O8Wo0IF z47cM!5ATp^hVqv>-w6!US^4A5&X+^FJ#qvLqfEnD{`p70E~`1c;cNGDcG^L43NOj) z&li2iyB?l+no7eU^-n*5R6j+G`1W)+K`Lcsoh#{aJm(GP^}mqhx2zIPrW3fQ|1U;A zz?;pAl^F+(Bq#nCQF7>7imevAG#Qh@gi`0xox9>z$?2<#VKan^zuaI@p+kiyba=1Y z*>{6fG;d_o*YKe`CqGR!cj}=x_adtP2~<$GBvxSc@1mXbci-`!=ZWML(YI5ktX{rp z@!#|kLLPK3zStYSz#E-cU?u$Ldq_B7CcLxJ$iAH6=AJF}BU&-k8=g;|Kd07i57on0 z_4lSSHdBxODyWKOPpp()la%c^J-Q^%T(0De29dUkGuyqEWNab6-d>|0bQ zDX=X@ImCZghqg=6m-Z-Wa~_|npy5wqZ(T~j4cd!4H*Wmjzuur{W5gYFEON%aPSUCH zoMQcS>}*9%P{MDK+Lsn7_4n6Hgg&EwzE17VbRRl%VErDF{w@_W_9&rJHL@JSQu;^o zc*8i{jXf6`(5S8DIrNspPbaBzwUPIfK8WOczVArs6HoXV&Lge$-w-}1>Hrr>g^7iK<2od>kDDFqbB@kcj&F#Jukc!+BCRk$d2mnSC_mUuNL;3<6x)YKlJg{ z&K0K<#vOXQlHwZJzJ}_x1E(_uNE>!x#}pT}#7XZ0R#kCT?@_Tm=M25fgLD00Z*;yx4m#}(9)eBJvEC?nD9B+TtcwaBqRoE;q+*P7 z#XEG&JdHWay>iT#lolO>aYe>js1GPDU6qzTWCtgzEqS|)6QNfh$H&Xchwl8E zp@XL{9-*x518?Y#SNbZY*N-kAUsL|>qRTMT;?xkBd6E=8;hr6YBhcb!#m-JMvQHXT z(Qvw=K@mWfv0+?goO~)0LOw>AIo%Q3MNW4im->)x-On$1$K%)+pK&Vm2}jIwKOtX` z+r{~zzg0SWToi#J?`f=ZuK24cQdv=}NX;>L75^jXN%LvYAo;N?Iip#eaT^o~2Y$Tvgq{Q%S=r%#+m{-B<&WmcnVLLYT-&vi+pID$II$C`|*s zB#qhI!c*sp4Kmr4EEKxnD3;RrPeD(f8Hr%|`vi%1p0fWHQ_TeXsu75_?ZPpn_#-$3$=_A<2VYN?xZDN$Y4--o0*h~LZ1mf?>2-*t89t*e8F;Mj7R z@PcEK0qD`u-L|bSo*~aE{qA-CDk3$LMwM7g%~(?k9?Luac~Eco zXXEiE1#1X{6p}&LiS&W!-B;QuC}DB+n3|F zim0V2;FsglPiF+TmNeyKOL`K!_QqRKmIgn!<(5PpTHdzY^NriN#_eq5cDivJX54a( zTVj<8@Ue0G(73&4+}<^AZyL8(joXXH?T^Oo8M!6xN%swpmBOB~USkehj&EfQQw=yC7`5qHD|dd_Fv?<)3GE{Eq9@YR3#^Af@KlxaY~M-q_-wIcSwyf)*1F3Wen{0AwExZb`Hb0AHzDt zZZUIDXEFxEHtN5?7?A4LpR*cFdZ;XiO|t^}umU(O`f;gUoY;x_Lvf$65qLX0Vz5Xv%cmq4w+C;@d##X8n z@ljPIBmQryGdg>6%0sCrS%N$8eG8{Kcl4~ zU4E5>+9>_tifm_3A#WVd6MVdr2kg_wC1=j=2L2o7Sd}WEB-=&Yr{4kVmp>qVtDQhIIXVmJpRX-Pou{ z!iVVBnBnaa=ErYS9SP_HmqK5u@)~~}*z$j+(oY;U6lO#>cGriJfVZfSbC!nd(HYEo zZHtZJ#2>GmMx@ID)m25sL@yON9!SPhk>l){M;F+=u@K(GU$oMte0Ik@IGFjM+cAEF z*sue#SSfNT<A1GKv>uP6dve|y$jV<43oYiq%kJWxxK}_3$=H! z%1R9>LnxN_S|W;n*P=%?(TX3(R07YCO;TUB^+@rGgjE$eJdQuf#uJE{v-@#)&sb&L z!+mw7bNw5Tz*@(7P@s}WtA|B*#xw{}Y_d{6U8Np`5YZ8$GlJuFXU|^A>u>vdWj4pj z$H27=Lca!vpVdHmWc6CPMc2t&Ur(iwC4lj*5gjg6&2xImOMTUO3NK-jARzu%DkpW0 zfgaV1@xmxIZVXecQyMEA4T_hjUYc}s;ikZFR3#qO$0yxN^=L;2quy(cK$S^{N*os8 zu+DJMlMb~wEWx4IaHusL`l_W0LA5bd9@S?-S6_9pfPK~DjhobZ-ews$kzxV_=JxeS zt`w*^XgrCQHJ(JB8c$CrpI%8m#mpy?-o+z1pR4CeV7+9A(9m-*=|L@qla6e3FIb=Y zh(aQU?LtfekK1~b5{iEnv(T!a8;9c%f1P$u+28JsdGh`T>pgfV&)IW6lEYW&nsK7L zbN!4g>SR{D98&}NkIz<~y9q~Xq1}a^@W-C;-u=sEo}kh{B>2P|;u09<2`w3q-uaN1 zvR-fWti3%9#9e6AX9h6+`R-sv7RkNL1dtvcE5jDb;#51*x`P`X!WSfQ#3oN<9Al5a z!XL1v;K1XIA~OZY2j|Z_@7`3|{YU>3die+b$EXm)vqB%|brtR3L}mXkqF`OuPg&Jd zxpKn(OyZat487O zSETj$umr>|rvcu7hmql-?%-oajt6@}OUG;e>rf1ZTY?h@kzdALgCeJF40N63vZ^T0 zy+3FK%yVxGs(@l>aJ+ow`1nq$RphGhgor=?Fa#cszpLt5<@jVEQrlppRuG(cUpFZ@ zd;UYBj%c2*+#yMMvV%Lg#TP;9>a_K=ckC`!^P64{b(5ixvK)#p{D~*L-xGfMaSIFKs=g>2QJ`R6_?tdtZ|LtGAuQ(A-;3Xx$Qd5T=I|bG_|^UqLZ>$} z!@=3fp)ErUc?%`3c7C@DpV4-Q-iRu$JM5C(k5_z!v}Mla2HZmL;r1RXk%^x>whFh> zw7(h8-)j|t#m5rpOcLnqkwvX~H23JYg~$SkJXoWMR20VBaW(l=6`j7r0KX2{ygGEm z;k@%%JV$Rm3OS*Fd|q$tgD7vLZmWw1Z?_B6W`uLaLwG^A?`Jsa*IvidNxn-~R;l3d zuTO;v9y;H-Vi1`L{bLY;&vmZoNCke_>)4eXi@MY(tz(9w7@^q=XXB4bL*RUJGV_MB zv$NwHd6o1=5}mLvIwMf7&zRZuZ+8)cYEN;?J3*rDcKzFQC|Nn?zbrSSL4f#yJ3lnJ`sD5 zK0#W4Kt=`sCK{xf{(`Z(I-EW_!T4xm$zRh|h#HhOhtL{d3LQm-EWbun$bMAFfpise zMM{Otz1>nFU&pVdLgLTSyg{%UCQ>V zd)^oywc&$j$Y$X~cn=wMuDFr_p|>iX-D7#s`>gRhJjGq+tudfM0-dJsFbG_K^ivh-&GbuNnKH;vt)KrEy(xR8ZJLs$K6@dR5aIF(pp5ohl_#= z*;r(R=`LEU-dCyPj+`ZT74@q3HAP|ru&7sFiSQTZ{Z|6>{^&05y7RZYu5y;#S0pda zk_U_Ss{6x5Vxe{o4Q?z-0BD>9%f1h@(CP1@7~K1NlIBZ~vP9J010D=x@f-qFQyO{q}bky^HrsKgo$%%8JB_-s1J6<-vcA%ry;_WK zZ}`Rj>1I28Fw5EV?~tS1rLO9c4Pu#_?W%MA+02RB=@nUo=F%NU%btn7!Jw8Ne##Sm zXf^5U?2whm0v@cL3vLiA$Y11I9r{)tTN23t6(7~m*mkS-G9?dsAc6SwWmLy15rc=+uO3FEuFT6tyHyCmRv%j`fL~am9 z+8hZN$%->gmk3Ur3Xb`%`nb*>x42=x7bXQaZ(FxfKg4{;IH)R`6@P=qE&c_(TJqqY z$>+9Tgdei7VV}%qGsE}-1B~}j0y4MH#s~~oV7RgoS9j3{PzEjTApmpl#;+2fm&cg4 zTP-2#>75KIp@|D-cDb{Mzc8@H{-?Fr+y!MOb&WD4-Lx%1{X=T46-bHDhb~Y&oD;zNTBJnBB$oCX~jJVS0qun>q*`f)N zku~O=)p8eEBlIC6u@cM^{%ikana@WU)EG1i4P4=W&H&Ic!dLYFEne#u$*xSB@3+=It1q4p&s5z=Sph9_p+~0j ziNa#4Mjc8i%J*%0IH#z8uEd~6a*Arg@7IK5&xc`gp zCuvngM@kl|!UqTP!D!RM#n!qewP-}l%?Xv1eB*wiKCpj9QbE_VapV_<;&y+$^5Mr>huEy%rvEK)ot$ znyL?^Cz6>;&#(~0WDo6fga;kt9)2{_|Jsc38wR|yc0v#Bq2c{oOwQk;2z6(*Z0{D@ zS}g0OA}iJn@E=(r!&^yuP?roRDUCV9!+K4sFVZ9{!aZzw#AbVY@$EGyhgccKYU@=+ zs;&tc@t{8FJ-+vOFiKqRza%#&}Zs<18?;4 zxze`^AJwCQ0{2XB7`x>m1S{dLljY=e{ANtAg>?(Eg^-zfu};id48I-;#y#3$-0uz= z_j^|x_xslx_lMSTcb4=>On85!*MJ}2q2TCT1Mb;nz|p-5zPkh1S#odCaJ^eI?)R$o zz$N#uHeT;{DPIq*HC`VoRj-e%GhQF5RIiWss$0(vb&Kv&x4ZYM+r1k3be7yN>z17* z54qIsky38`SDE$(Y6SbUdbOyKFL@%Ls|oMo_+O=Ulj?Jj-!1B69J*3gGK<}BKK{o$ zvMe<>@R%N%^NKg(+vSZcjCmu0z23;J2cMx#G&SzuziTzDQ3XM>BGh>LrW8=bNa){% z*TO^2LlNTnz{49p;4FE(mKO=tVRB9wEBYAhZecF>dxfj;el1@0k}U+9YX%A$9_|-* zJbOCDC>2_IS{AU8_>KyVpQho2W~@-n&EH?%$^Pw9uG{68Y(NmuM6RRd(*C=3?sO3D2})c_&)w_t5U zX^(iPn1yJ`5%;?#OWg03Jk^9T#Hd0$4D5b0gAYm0kP1(Dc2OaIAE`9lAFt(35s7y_ zsy-#bwP&tEqq_`G(GK-`_g>@m?x1?TSEGHFWLK-#`wNWM``4=1hg`<%L+jM*Bc;ac zBfaYN@k(B$R;pWcuDabVi>;j{bArwi-)d*c!nMwlz`EExwX}LL3$1NsSV{VUSOtxw z9(iN|F;G4dUo0#G4F7ETNSSz}kL-YIlvDH}RaizD?J*QnvVdf}Tj8BiK>K;LEg+N2 zMiCAu9#_;>kmP%l5>=3;r zRZGqoFGe}vqwI;Tr=>DKi7`uoPo!d+vb3ovb)TZ5#J-8V%jb}W{c5!rrm306`q1qK zTHp(@Us7r9B}7^cv*c81%7~{b(Ab4C`5Jv(3KubYtOAAiAs_s!TA*^oe5aMmvMZ9g z-0@dQYJjYrE2)24SxdL4u`%i19u+k|+)(VyK+s~MFoL3pjr@DUY|O(%hvY+vW39+# z-3&c??_8yg-Y|^}$#YC+rnJAvu%pNh8^yR2FVmnDyF-U_{l#Qi_Kx`}OfVnk|ApAX zTcH7?JlFrMdn1jmn2^%0SE;4q*2hY+UVj6siPfmn@)l>E(+i;k?P*1;duDa`Dd`;S zoH-*pdkB^ZW|fR7Rh4g9wlMx5L}1WY6Rpe1<8b?t-td2819+?f6HM-c#10v$E?`1@ zWKZvsnKhC8@ar-RtwvPBjEXUzKa$-tE=}y{>0Me)D=B^R@R`xs7jrhuAb0uex0QzD zdgGSpO%9ebF`GXwhe)c*-&?ja{CfYxLU~POXifMf`N+eNH!<_lEM_(Tlo8~Mjq zj{n_wQZqY^~d`s100b zE{0a}9g-E&Z(Mpb(H~duc+czqWAkc>|7qYfdOmyn-S+V>OpD)s{d_Gx=ocTD%mBsz zTR)F-IOX;8#^~}#sI=tJ0OGp4!1-vqt9jFV*_F=q@6lj9rL@+Yp6JXxnGM^KnOu*?yT*3cY)KYz};4YDJ=WmDM;>S*iIDY_$*a%|)CYp{X!I z^2LIeJog3}^>+502g+{fJe|!GX=TN8mB)elS{jz4M)QiYmXREqBl&`W5>umlX~mY0 z)g2MGvF?X2xZLq`X$s8xI~f~z2E9w^E2F*HAh_4>#ERBSw(&KW^V_dLlnf5f&d!Ns z%lf=G0rbYnIrw|my?=@;$3KRG&(TXZd&@WbIkMwrJv^c)b`j-{FgVvA3_lf%ifzX@ z2j* zNVB@^9p|Gr6gJDcNMoP8;V%m9e80$+8)bC8o~tfTlwE zdm>^7F+Tm_yP@9kd_m@~h!lhlIRdYHIa5cEoLS>|nsVrT(;KNRu3`SX{P`N^q`!0R z<)Ut>K!ImXxvP-_%f=XVj#=c^Be@^UbI4ZV{x35(4vYC@vQRdE#QYfbGERE)sktj$Bvl?EVpP>*2Fj2@pN2M^SXz<#LODCrC18zVyCHGUfr( zCy4+PWI7DnT&kBp7kHZ{Vk99b2fZ(Mc_X2ekJApYRIzVzv3j$As;~%uKcwBBocfh* znXI5?{Q#JpySKhNt0KYN*R9!CI=g>B#7KG#MYruAWkpRf9n^G!qn{<_q5f~ySeiT%oof+d7iDw^0~fuNb-n~+gw_r1ix zT5Pi84((zw>V;Ch8)Y_2FW=!@aU0Hh=pPOavv{BBv?2pvROTt)>kR#tOnRa@rTTL* z+&6g|D{$YVm%qHY&C6-=dSlh5JTt%_Ya0DUIUdJ9)OzITZ7RQ4)0;rgxlwfE=miOh zWSDP|!oBD&7Dx`ti(L!BHI2iIB+o97BhKO(y*#@s#~bdV?p#|Ke~mm-Qt@v>c#SM3 z^+Z~W3Tw)DI=g>?lN?GQb#O+#gCWVueDFt|j7M>a*Q099ewGeJG^H~2AX=$VXVtWn z$h~kI1t>g)y4X>fIin5N!*81Wd{E{FQR;jLtOB$e0gNL2qw+q3wiuB$_`gj2%fw}7 zt8N;&v~!eZN|ui z=kGEbx_&)+Qx0|PuwL?%Uj7IF9ZX!sbc&Sjm$AdsH{dND2NvD3{~JVL8DSJvyTY5B z?|AFr;O09H$q3bD6fO$|cQ%h2e31P~hU{o|hpS!jF(Ry%#t{i&9pJ$JjB>?SqTlFe z3O|$W|2~?2F#)8=Rr{aT^q&bWku!zee^pg^o0@4xvidJq`jv@h&Hs5%gepOUe!6_| z%{~UG5*_K7y`vzF8_w>ZN)*Rw`VXa={xJwgvg!YKwPKo6NyS=xnWldgX5kUD=~sQr zFrV^|7Bac-lhwki;W56BIBwhCLsQOXQg9@l!k%vkCkOIQNuo7hWUQZ{y>_mrVF`~b z+P@jw@HX-ldW%kG4ROYKq3Gtgnh$J|GHXC9dmce}Box!)< zo2aca*;M{==f$3EI=}Li&mv!kUjF{#mu0_>ccjUX@3+%!3gm55|8KB=t0MDV?oGRg zRfXS%?P9iYRMw-U9C|BDkF*y$yDyU35(zM~u|IHb{Cd^YRSJTvJcWANqSWBSxuQe& z#8!zm2^~zZ3sf}sOIMJCzG~M&CRGvg!gwK}je1oRS!x^+ej|)yRghS|l~O6k*I-Vf z>I-PJJTgy=LQ{hirhSxMg+Cgt6%UajUu8Jt9FEXg!hX?k*F|&2d7g`16KUkOpt~V-)EdOq|9BE8&1P zsr+IA777K+Wf-ys&3Oy=WcwWchq3>>WI-6gdSGd#4)CgJyLkOGO`H1;pG!8Mk#!~v zxAja>JkBW+ec)Wd0vxHVGhLp@!a|Xg9@)8v0&#X(o71aM_OJ0-Z^*m!lQeJX+cgQDkNqJ3+=A19z zHL#s(f^t($>olwI@}$2P3kx~n_bCi|ew$;r@G3!*g@N=xz3BVi@Rt7jWWG)K9v%IZ zOiPHe{N)IlDiom+i{bmiGCmi^QMpD&obB;5ESH2`9&R5i%G1ANd*GfGkh>ROT)(QITA{g@#Vj$O)X& z>^>lMgpJSsn-ve#n|CFP&T6u4B-mf$j$0nZ|BKX-Ed?k-Z(jlD zuZ6uI;=Ik#|ArYavZE_zD{+#YKlE@H1iAA(+3@R1`0niaEtaB}T|!i0#bybYDG*(; zDtXS^uZ4#kmRPAXH9`y+8M5Dnj`)Z{OG1V0^R>wvVYR%J$vCOB(bjC5+`q`&YEW2n zZKP^n0sWNSQ2CyNdgJGF^bc#Ii&Tmf#~~QD57o3s1|e_XVQ7Gw@Uq=PqW(!a{+ zf3y}Al)c*jhEK)vIthm-l3QnbbxAH3~8cqe~!Iu#3wDG z$ZD({=@KpQ4%Q?63zU9*4!+FtkP42ui$}c2PD4uLrAl2G`d7Z`F`_wx1vvfRq2kAX zBxa~N9!P7%KFrmwO>YewB^qwX4rOSZNdg|ccF6d*QhH>0D|@`iK1X_V!BKByg0Q_H z-cJqXqYIITcnQxL_M9=|z*waoxp>xiq`hwtF;GO;Q|{A{`P&NhzA383k*#O=RBB=g z;ugE>4B=4jOrG`dxzp5ba&aVg98Xkz>ePE;lx?L6n0RoeL%VTK{APlz7RVUvG4kJ+ zdlXknUhChk_)6^_78y(wxpA3I%CtXadyzAh`)Pcq_k6qpKN8Lwj}fQ9P#H`z;+OHL zCrgPw_JLCr*B%Ll2{R|2MM%z5!rb~0b4%s_isR=0kTfn<{vkMamy(d&nLJDW)lKD} zr(^P8XXKwCcKOFOm4B6~`6^REs8-`?2K{3*=%1wMSIbCclc^W*EZ_ZoB5HXXFBIsC z%c0Jsx{G9De<@I<_7k2(L9@r+Y2V{n9qwrGOLoK&h3LN=3lf8#uXQVn_3{@2eCNn9 zzlH={C~^IixJ)xBZGR2+(J9AO1|I5AYZpuGXUjT?E5GD#^k9z^%Z@q4;y%uUuA*I_ z)p7*h(91swykYc}!v+0!Tjhtnbt8MI?qahXCAk&}_sxKWFF``=qBGOi*IF^e&dg`+ z?X{+Kq}rGGv($mK@%l`85mAW!LRvME-Ed$0+0!VsRCufZBa876olo#TK9YMC)WG+i z<-&K_zHs7wvJG>XQ;3qi0Uiux&qE>pRKMIM4*;?{Rg3+K8bUNH+j$cmtlGMBMG^6J z4~9Jtq6-auMd9!Ynd51b(u`3{k6#uZC*`nT)!D#w*tH9mAQeC088!yaihq|xllpL> zs1Vajs2ZiIr51i^3W_cOV8_sV3x!rr`n)KT3u()pK;<_f`zVTH{;Co zu`p)nMeT`w?OkC7W6d`(EGUD`vB7H681qbW5HA-&w#u7Nh(7+RLyTHO9-;Sr-l5mo zeUFiNu|b?3&V3n|*xyyZEX~W=H6x! z0}`0<|1>V63reo&O~uzqu}3F*B-X7Id;ARL8YEfG(>OK96TMxTy@jkvkREva3n_PH z(K(C=@+5+gL?8*G^Nw$^EPy4!gzGodc^N^Uss^juZ=1l*EFhh>@k-%s#T}`z%s%y+8 zii9zvx(uo>>1b)vI<=vTTl@=LL3Ee+m-w|Y9i8osW4e5;&0`uDHnq02`3x@;#*FC zj&%^?GU1w*ww9{E!VXVci{Hq?4V^81p9K@$39S3v~>jhrKOi^GrL;a=DX&Z!gq(@0!Y1tJ6UdNi$PDPq2F2%6x45?0sW_FQ{v6mX|;xNP4iLcVK%M55rCt}@1 z=FQk^8vKn5Y8q}Lo28}YhMzRd2;Eg$IyTM8h_<1#aY1QmnSs|fboyI-DSy*^{>AN` zMy!`>GwQ0{H8ZD9uJ=raLsqwRAtFrDQ~0 zulH0{UpLUjBq)_ojjyY#VZJR5Guj((^7+a1F@X$8C>>|yBE_#6{s#P$yHSBltiK7P zTGA??5_D~h=I;#nwC0A^E}u&)9(7Bg#8u_%YJ|rVT%)>1yNXA3m5k#oyl+;x+6&D*{hE!E_@a8%cYu8Xfy_n}7JkbnwqX)|F} zTE}>v*|tzh7F?oV-0&5xBhcw{Eo$lX2O3&k3w{0t?M<#ml#j1zsD}J4Y-vLjO~lpd zYYcRDwJh?rE*+{}I8<}F7B6V<7ne+Mxv2C_6DG`~@)EecQ7dj}Yj0b+uszV_YD3<- zIuPU%JgGdkQZG9jS|yCu5b(EaO}@rf|I!X$vAmUNH#W>`nV?OWpna>!*W7>-bTxF& zKVKWwq}>X5jk~jxDs|%=6?THQ$loxp)#qw%?{sO`*VP#hqO&Q4Mt@6tn=9SN#Vu_Y zYm!z=TQjr35-9m5lx9H9Z3aMYF1Fa@Ul3@!$>nc%X~!as?TaYKhBiOU@&`KGTq)1r zY*^Ibn%vsZ)n!CEKhV(GF^heVUP&=1Sv0eOE&>4sGqe zC5^rgJo^`PwlCH$xImMN+Ui^4(pnZa%%^g|ZUprr%~fB&$kzx*;6kxhEZO2J(dwsq zubbrd)*JOh)eXhB)RS3px>_3Rjq0JzYe;)h&ZdKUZs7%!!t`amDW~=UPS%?d%6c;* zZ?Rq*rfC;U)EZ`J{A5SNbBg(zQRkFhFn~Hm3DJL zyISk4(MC8o8yT{;w!t{e7JNh56M=f4b9Wx z@09k2M#-qdv;)Sgv{5N2)fwTb?9$1Wkv^%R%a{J{q1t=AEoGNiR}Tm>;H5kn+*39- z`CK!`-)w-`7;{_s%O#i?HmnRG@}7TI5d|&1Xr!>|c|dC z#qIIVan)f{Dy`kn*oYD4QXHUhGp$-NbmsYJ-F$`%0qeyUcXb8u?`!fkT_PVcsb5no z?F|ia1L~S6JC};RLwkTsH_@pv%ruY0+}6-qkEJiheG`2Fm8Pc(`($!M;{pj~P@y3D zPfe~Hg?^y);czz3Ui|PC$|UCr4mmQ?V9u& zO|9-S2EvTkD95H2`jA-3OO;F>pWdZi^^!+Mr~=ZpGZnw7@m1$lRNZmIoBXjj<8l>G zk%~amA#GBMZ6$_&JJom$-&VS2JD1|EDjbv8D_wy$)h$)Usd$?#W4tIHz!kCrCsY*Z ztyUjaN8JKZcwQ;m8s9=#b&Ba>p|`%J^s(op46hlv^`R zywN76h8(4X=5L@%A(j%pvAt8I$V>(%E~Q^-#@gP>P_7a#=vAXc)2nN)tE-;DD3W_> zb^2(LHP&Q}M9l!G<2x2RKVyY`MttttT01Wc5*qF^CQq-fo;E|j&Ne2-u1tS5GiP}0 z#=d0mOh&hqyFt7SMfft~(d+7J?L3f>iU)eVo*8v^UL+3rDSvuhjd#XOV;t_75xM%( z(#uSLweIPR$+^8Vs_W`J)27<;!i+eL|FvHCfI&Qy`6?Uc2$_gI`oR=Lk>lo;HgmWu zIj*JXHB?O6z*Ivs5VAoBNra@KZGif^PzB=OG2vvStsTiDfQ*VP^0|^d1F7pWq{1M9 z&&2=*GAn7c&-$v^`Z3BFMoOM>MnH6=LR#r;9OO`PFx2UY_3Z7-OtX{@&=yEo3E)v|35pApI&;{%;#!}>zq@&nhFz%<6YxM8g6y0 z9Vp}ye}lOH*XQlSIL!ylLB zUNJF|_%d<4$%^{zlM{*AxNRprx%*3vD|Q8RaJ7z4B-TLFaPl#zJdxPPdpGx=j5e?x zR~mP5f6%<1s2~j2Mm>>udn)N(Mf{}ch5qe4i{CZmam_gB;2L>pB5~JPgT6uLU1(i4 zCy_XpD~GFpb|UdA_xo;6Bo4R#>#va=#LZPi{OQjR^ZpK3cPIJb8sSeQb_T$>nid^P zM^Tr7)#3g>LFcK)`Ho53PUx-U8o4BOd6&ZLj7!{igISm5-_BpU?WXLvH2ZPf@hspI z*pTV==j8*#r(4F!7_bAA{SirjhQI966HH$Wv`@<}W44C@C#D$!ZFGy8sx#clj10ds zos^mnsn%Nm9Xb47BC(bGPA++0vG2%$YaH(Tz~3?5-@OZ2|4}0GC$4L`zRC6G4-$!A za|O7nx&C}lBC!(u5w0aXySYYi{o#j+#JcaJXSja(ABjXgS0UG3cPA31TyseKf8r9F z_i=rj%XL4ret1M(g+Di*FM1%6_&nE8u0y||UUI$0C4S{9_}}p8KNE>7!HT(talK90 z-*BzrS>ltpUcqnXC3w19`m^PC<#ED_`%>JV=Kg>4T0;8&H?Ot#B@+KPuX@T}%J>Q7 z!zydJuQ%W2`Tt%2vpl$gde+8uHrIR9iGy5H4?o&-L|qekz7&IOGX<`p^ z@8kM2myX{Xxh~@R7P9rf2=lMFmLDgsAAxtIxn1?sL}C##Rg7Ml#kGUyS}u*Na3eaB ztAi`ZCGXRrFa6pDww7zP>7L$pVU1}gynb|+W7uyTeDu@n$a>e|koDK{OZ@0GN6xTZ zV;@s_H{u!F3AD?RyDCq6DvQUzWtDIeZ@s?%i49lJiC0xDRdP{vAi|w@(2bawC2h<$jXb!%}|J<2lQbJ5AQ;XK4=kqL1GzLy_Dco$DAr z_S{abA=i<8)7k0H+6LvG>(FjD{7d|KCApV6F8P9%!qVMOcMR8{>Jt21&B(iiLmmcP zKqHLkG{-H|HOG&`D-KFFOvJ~X&0e{OcO zV`%<(Aa{N-UhqngN%?t`hBf4muQ~=MO8H6p?{MAok0S~76{&#yB>lr&tI_%I3XqY0 zwWLq1%Li3U`cv~OvsRG2eoQ*m!)Du(Yn$HBpws`rYFk*`l30C59E6Jps7YVupz!( zDY%D`sXQV2zyB!FjOLn6yxIpx66FF+$uC%*Rh3^D%AT6Fd_jcuT@#FYk4+o3*}@@8Jb^Mr36AB z_O;}*XBbHuummz>cOUU=AfE3FpyI*Pa^V;8bP^WY5G?nw8P+3T9^rK~Uqrj;J3VCb z`OYj)eql7*lkZxUqvsbd7kLd0nvy>^>#IYRoIqJpOsfojk0c&H@vQs^TS(#|t>sx+ z*O1j3@gaP>3-`hUM-q3K>8ujIEf>CB2XRyL3q1LGUL!iA{PDl!;E}{N!f*0pmOrU4 z&zUUpB_zF^QU28_dUA50=g^S^`^jtCSB0Lh4c(TXH^ri-2=~}0M-u-g?#M?5J=-8? zyG4=EUx)u$M^3HWrG9qfzmR=zBzo&t#KRi!0&v-!Sojpmx+Z^@BkO)8Ii^SPw-tX6 zoW!4`vm1Yvd}FUZhD!@CR@5x*Xkxu!sdWA`Jsl~x(fHeozu$yp>niK>H&o8a@2#BA zy^8m&wxRhhL?@j&$KMcOs1|NX}zXu%W}!?JEozUafGi7<8LwZnujBZ+YbUj^ST@&{kC%L$t| zSt$)aZGi(aZFfhn*9SjGPu59@G$ zPux}fw7XV&v2o~@RDAldlvvP);a&1Ka)_qQ9l<$fJX=4($APZ|&#WI_@Su(VCh!Mr z{4WDv3m&v0=PvQz1)g`-(M0-0zVP)y@JjIc;-6a5M1J*QG<{Td^dP6_zb~X^9v-5l7Gy@yw92b>+)R-iP3HHQQV7gACLP435PqjNYY*Cs=|F1 z?yJR}<|$;fXH$o{@krg2UmaH~{(ABEU9-M_SIYZNv4f&HUa_}4VrNu|o#D-2=g8ii zmA}DJo!{%2VQX>V#o*Ug;yrlI!1^iri_%ND>%5#qEUrsUUV{XJd%nD&>H zOE=*bu;azqJX=4(*MN6`pDQ5sYZm1t_`~2!z&|gz`4jrKg9pJY1ylN>845kat|Oe3 z({9|?;;!_xa-W*NHZSY?p-Oug3QzJeVmR&bmyag8Bpyqjhr~Xbm8KzE$4J9r!Hp^Cg`5llWRCKJXt4MtWIuNpLoW!cW4j!hJ99 z^yrd5!S4e<2!3f2HTb;|+;stZR&dHQl=bx_!v%ZoB<_bzcO`c!{LBI2N9Lb6{)&^h z&pwI!vXi*4If?tmleoWj68FO=aUaQn!YATy#Yx;}pTvFHN!-_*#C_vQ++RD1`{9$g zk7PfI6XyRU?z2zgzU(CKYfj?6@g(lAoy7gH>3&@KKi$Z`YWGzA5RE@|7~LO&CvjhM68DWKaewV3?uSp}KJtv?=TGTB(>>D;sskSnUogj$Kk?rRUTTAP zgBRQ2YrsYRGUIs|ywC>U4qjk`zXP5Jj`@@P2|b6wH5>mUFla^JW~MtDT=a7$UIiYr z!DoZ-vcdh}uh`(Lz<1c-_kllcgKq@y1<%aaPVfyj_-^oZHh2!qc)$i93BJ|_9|wMq z4ekYBZG$&~ue8CJfd_5yyTF&&;17a#*x*~i7uevhfzP$U_kqu{!H1t^wC9=pECSbU z{8xZi+TeBI<8AO(@KPJR8@$*CUjyy}SG)OOPkGThkgh#!`p>L)+fDzO^7#&UA@QK@ zmECcT8P8$x0xN!T<0tWt_?+QClMkc8#s4|t5o@r{jHk-ve^~Y99n*hieK~CU z&ynfPe%N*lZid^~t&{hAG4YUAGzUTlM}0(aTq_kkDM;2XgUZ1A1nc{ccN zaLoqKp#yX9{A2Sw5`3=>J`OwvevJHs@3O(0z+VB+l*477gO3KEYlByT&$7X1gV%y*=GPCd+xTAv zUJ0IAzV{u+|Hk9^-+3JWyG{R@@{q#-%Xk}lMuL~x;N!rHZE!ER%LZ=(FSNmzffv}| zcY){G;17ap;Fa3*^9+TcatF&n%Bd>42oJ$1+N-)j2L zOt%~S6`T0ifbX!u9|nKg2Hy_eYlFW7z5$%!j^t0;!NcI|Z2XV-67}5%9}T|N2Co9Y z#|EDbzS;)&gRiu~SAhp@@cY1**x(z%J8bZs;0tW<-QaU=@EjNQ*9IR6UTcGo1J`YE zFL?=s`hY_A_Q{b#nzTg~`0@z=mhNjDSU2VQK04?mCkYl9bo z7uw(z-~~2#9eAD%-U_bS;N9Q{8JEqZa}D@j8~kDLm<_%ie3uRW4)`nJnf2~4_zoL< z#FwdGHuz}pUK_j$e1i=>8+@G&?gxLs244j(_8~h#cm<@gye3uPAVifho1|JQ+13c5tuL6JC#{X>aUK`vGzQG1x z1-=ga82JZ(zy{w4z7{;Q{oQ#S|GQ29nepdbK>f0be&$Yp~g3kiav@>4=ueI^N4_vpwhhIqjvcZeM$J^i);HBW1>DGZ4 zgJ+gYtLZe+N9zCZ5CKnvMSv#ni7a z9h>fG@V(%f=~jWqZ1CCOyKLg|gTG?qe--!+8~i@-r@=Gz)kf2QCVzIC{xj*^4c==L ze@+SY(FPw0zRm_82mXK!?gd|KgExWSV}maPUu}cm1-{Y-e-J!qgKq_2VuQa1-eH69 z1783>Btw3NUqt<~!Hd9W+29r6wKn+w$KLxu$5qye-_zu_6f)F+BUBwB;)n$X3^+iM ztOU8Vp$!mZ#Hu3%9H3y3C<7LVYp_G<>HtwktdiB}4p`l&aUG#zbh9#I)hN-O#iCKN zI*V1JM0UigY3KL(-uu1z^W+)(JMTH~Iq!Lo_nh?8`?=re?|trb|4cGBiSNLh`>RLe z&Ha_tcyoUZ;@io`^hNwyiyz0gT6_h+#^Pu3Efya+iQ~)S7bL3hqN?U^_FMR5zdmNa#qhTJwBc>_N#bqw z>A_2V%>K{fw_E%mzTe`D_$?Mcj_S%kK?;6-o-9o7C((|v3Tcnju(rM;+ri#fnRCyo%oQ&r}6tWEm+*TmeiA=n@m2h8iw~c{__z2NewW3!;YTb!iQj4QJ@_Gu&*FDj z{2+e7;*0q07C(;fxA+Qv3*MYhW;NcNPa0SJ0i%;RZEk1+aWbrwC zm&F(G9Tq=|Z@2g|eyzn%<6AA>S#V7EqExr@qZ1HLQN{jEqhw$ccoyYHc z$0SH|i%;PPEIxzZ zZt*#Mzr`2uTkwZTyL5@bQG73+)4A;5)*JNxu8f!ZnB!_1FZDTD<2P%(bGE%dqj+hr zIUW*tTmGGRTYJ-ZTYLNP(q1$FJYL#s`eD4Z*YqX)W{aQ1cUycFzscgmZOlJ-bDYHR z5^s(Z-VEWzo8uvg?;zgvJ@|IKxgWFmwU+one5=J5@oOx89N%K`75r+8pT#%h&GttA z!H+lF8^=q$*)A8q(&AJ2ki}>4^7krcJ~_Pny^84z_`McCil4IhGJcQ6Pva->W_z92 z`SE6Zqj-ro>yyCmw)jr`7~b5!Y5Xord>?+q;`8{OmVAcsLl$4c@38nu{D8$*@!Ksv zd=AH>#mDel@Mb@>;d?FdN&IGu@4i;mzZE5Z{G2=dmKb!xBG^Z@2ggeyzpN z;#(~~($4W^@o{{M#k=^`7N5d5TYLt;(&BUYki{49`&KXQpHcjb#h3AWEq)q5W%16r zjDNg&Ttx8_Z~6pY@;8r{PK`ImNm}zaeII^;d`zFm@3#11{Fud;@VhL25+vTP^V!{2GhT;al+Lcq`P2AJuqs{FL#l$;b55_-4F0 zKRYhRqs2$@Axl09{Jx`?=HH2*vG_E8uf_M_rz}2?-(&H^_z8>K8fF9@jdtfi_hY>Tl^rt-{On-EfznH@3r^}ezV2T;=3(A zvX0}$;^X)(i+AxIhL19IQuubfT&su9m%s0pzYzBqaecb|1?mLi^Z2!-dx_zPHU2om zm+(^GqYOWZm-d;*WmV&kHsZtU?fntM+xnvoZ|jdF-s}%)R}WtL!yHFhytLQ!gZNg9 zFXGo&{5ZbF;w$*o7C(z`w)jW~^MS?3@gcl9Ke-ye-007g#;-7Z2EUKL12=sRKV$I) z{9cP6#ZOs$8NbKkr|}aO?_9wAVewJC{Jpwa&jfy##dqRIEIy6jY4LsdA&bxBcUb%| ze!${O`0e;ZjeefQ_gj1wzs2Ii8<;-~FUN5VzZqZeICgcrd7P#2-J~;*vy8@@$9+!Y z&EvSB@n-u*HQw}P{3fFvQuk?mm&H39IbJP3if_001b(f>cj8+uK8;^v@qPFfym>t3 zHQw~Yn!kCRl{DTQr<0n$>8tqFlxzBMC-Z^D$M7pHz6~F;_#}QG-!oz6--DmA_$+>} z#Sh}AEWU`}WAWqo35&1bcU$}{UcL{*tY_pxj&F;P<45r3cysYPEk1=GviJ;shsEdc z0~TMv%lCPh<&NU}ExwH3f)5+>!8E?t;+=~)zAZkA@3!~^ev`#_;=3$9jqkAdK72dg zY*!w?*5ZfptrlOxud(<^d<)*}hbn%xB|dyH~slh7VbM62Fh{buru3gP*bZ zEPk)W58|gRzKGvr@#FXji?85!Tl_43%;F3P&hw-g=v)@YiHJ12Ee2c|b@vAL9{07Fq#mDd~ExrvOviKx^AK$xV?)M)2jKyd1 zdo6wtKV|Vn{2sj7599a=OMC^t8-KVlAI{>(Eb);`8UGd^$IJIgneB4%J1stiAF}ui zeuu^9@B#t(g*S;*~zsurt_z{aQ;CEX5D1HcU&I4uq4omzre!$|LH*tJfd=%f0f1%N?M4k9fjW_!< zjo(5(#~S(c)rrq*yxFc{e6OXRCH!WKpTu|L&3vl(O%@;C#PMj!Cx-99o5y3D#+yEg zm;B9nqetV-aht_U{-z(qw_AJ>zt-Z%@vRnL!LPCSS$vDdN8ZfwYw>Y>Gu}LpxcHTp z_!K^5@frL+z8}$SZw^0W@df-|iyy^LS$rA42XBtUY5au6J8$9mwfHE0%;FRHT^8So zAF=o}ey7Fv;fE|fkKbYO!}tMvKui)3{3yQ5;>-9Bi=W1~TfEcFd}8rYe5=JL z@N4kq`K1%zV)1GGYK!l~H{;FglRSQYnr7GJ^du=rX0fW=3yV*Fct z9N%y8E`E!}r|`WNpTTdo_#D35;tTjq7C(ybviLH-!{Vp$?H2F6o$+t+QG6@joPQD; zZ_YoR8h-@CG*}}1C#~`3{L`oLrqAQoP_F5R@hui#!mqaYNqn=#SMe(?KAfhq79Yd! zTeY-4ZTK0BPvZAld=Gxg;xx^ z@^;N<7BBTVQT^5GBUk(Fjf&Sr%8lcte@+l5-@mQ#E?)Zo6!q7sPvK?!#MNJ`K7*I> zd7OA%B>x<~+mcTKzs2H5@dFlL#_zQFY5bVQJJ8?_B54 zw`M+3e2XPMfnRO$o%m+FS^qSCr6s-(AF}v7Ugq0Fjd3!Jm-*K8CA`eHrk}*i{AT(p zUglfVhp+eN+l5}{z!+ZUPdUbIzsKY1cJsbAg&(t&lfmz@_#A%3;tTkl7C(v~!h7?? zg6{(?*NLCj_=WA0HaYL$cp@M3xR2r`|K-wdT_irC@#gW-i60=|^lAKdi|@ntTYMhB z#o~wYy%t}>Z?^bJe7D6{@tZ6@+{5vQH`^P-cUa=v@a-0##ILpZ9(*hQgrGB_^k-J{ zZ!-L#=D*VLMa|!gAJ_cN`d9F4DA)9}_!hil);;z2tI7YXEokC{&btwO+OOwQJscG!i%Z_sgeiI(;A3MXOp;!E0;0en=4~_vft= zr;GG!1~2#juMkJSZfm%7u3u$)p6&8H;Z<|nubA5|{ZeH6cD64W|M-4GktBpBJ~@_&)|8CKetF3CA@qdvb6u-FxTfBEd4)Ax-rrnF_)XaUn1{jKEZ#+@Nv9+ zzH7$2c=`O;Ywz1uHD5++;-pRC2k@_ve7yI_%)e(Z>AlE~*KlbWGB4$bA0qzr#qmpy zt0Hl`h%?W#OXSsMpP75}&ibY+`9Fslop*jyC!r;*xu?c-9K6U8vF~TJY6KeL5)9JoHwsrGB;kN z-J`nw%d*{@zvth3>AgRW_g>0tFV9=>s<;c}J(Y`$B<9|ppYY9$KkCsT!s~yp-JG$v zI`0j-T(+^K_-P}4jQID8@XGhjAKv@<^u3;~w;8Wp>}cXWW{X~4C$FenTA>J84wC;A z`J2}*Mf@Ja%keXgpTJ)&^`XDE$_oK`Q&$iF2Jflz&i}-%KL0@N4vAxWzLo9gHOX7O zcvlqv%Bvxz{3sVL{lvXgau-LuzmhGU{7t6 zSBr$wY+rftf!an*zpcSPe){lT_)+nkC-0H>*4{1e|IIlZzxqB^#-$uDY$9vQUDxzx zZ^z9G@2#`v$-7s#Ezdc3H{L`3Qtl-AkC6WblD{tEtN6WmbG`}R%KU{tT4Ig)M$*M~ z`@-{u*M9sy(w(B|cn=r-==Y%4ezvdd(%=7Bu)q57&3LmOdHia;tsV)=DC%|&W34CS zP0kbTZ}QI!TyJe`V#eZ~yZTy4?|sM9r0XW%)8%-hGxdFy-uw5w_bq$xJNDj(x1Kk~ zzTf)3$_;V|tfjY?yoOn7;~kI5+qfRNMEAEb9=v&xy?gk6Qbu ztAVNA^0q*oXj5d7BEKo>alKrq)Xh)dZ#}TA@!4gob}ZwSe9NwZ?kda?@}I?Tl4q!r{>?ktZ|d%R(H>l2aEJyq>u6ZmIJkmJmdc(K8|mFEAM|< z(Ba};{MyU84qkAg5}(3%y?yEZNCw}5H}_9Y<8Av#@*CFe+`i5&;!F6QYY{=7Vi zpTR#YeuLa+ZPfdco9B)b-hs;5L|>r#`X(-2{Wns#ktE8Ay*GWJ_GwAa^~~Lz!Gibi z2IIKlJ&T9!I+=_J_u5bTDbhbA>2-<141W97dY#0$$Tc*a$Aq!5iSwUq&$E3u+t*7P z?>x>+sW~eL?^i9deIMI-d>fMYi8CL|OF&(DWvBb7NdLS!NnHCiyzf&E950`S9C6+Y zT;+T{%l7_j57gc&3A}oE*XJCCya&5$@dc06EB=qnr^LTd##`gAWviN{UhBL!Cd&Dw zQ?_5H?~U}z;}D_VX|Hkwut>jTh+BER&JUjT>zl*3__RBh0g`_y0W#Q-I&1E`)A!WNs^@BMf!x?+<3=5=czlc#4!O;cm<6Vxyd*2x?`}WG->nB~ef&Rkwd^;#_-60ibN-`Uj3&m5%&$Gf zg>E}g`y!Bj(f#3#H>&Erp833B*qz(N$xKcM3?%PP=Zy>2{QW*ke%<8v3dv6w*?;AJ zt_ShAX}{=u;=Swm#($vc@=U_Jl4!VhZW!nlr_?iY2m7!8K<$GvpBeklJ5Mg!e~mYg zqvb^4?WRxNlQZ^?clzpX3eBK5AN?~Q+bQ^VG!mTButOD&fiE=823 zynC6q^tj*FKsU_^~_7uvB+ z+VL)m=K`F4xL$_I>hrw+OHvt+Wzuz!?h}&En}>Mz#5~oxU>=fwnk8Hy z@5QnH_$RLW69;OqlfwV@xZ*0Hq4D!Fu$G?vy&WX|GfIA4ztH<4Z~u7LfwF(D_v`3g z2woKIDw2fq{8UM|i*)VsL5NY0`R4`Rb&k8Lop)ZY<$}$)+`FKuabJVAdc4s57<;q$9JjeF{qySswSQ>z>!rsD*QYs$^X<0f-h9@01-UPpnAiG!+keka$+{I^Zow^iDAf!{U;A_0uI&ZdU!;wubE?4PGbrGDfWl5ywd=RJS$ z?zZuI5CQ{H*r z`{Jj`w`EqJ<8s|J|7`^R`6PT7{lWI&nIu*5t}kNvH5T87m;4r9$3{q#)cA!yito|< zUnvaV{63?c7$L)WDgSRhFOuISH`7^6(wNJ?o=kREJ5fh*e z6zPZX2bfQqYqfQU1odNn((6{^{JnnFl+$>Meg5>WgeAX}X~yhVK|NsDELXApJDy2ckMI=FgX0 zPsn76;B?9JVdh@{|0MCoN9=%^xteqs3%pTTdo zZOPG?_hpZWB+Z zxp3sagh{=^cQgMVTU#_A%D9W+*WmZbb%2*2=Py^zUzgs!{DRzyOST?z$IAXg8!z_e z2)C)RXW3)n2ESI4K27?)FR#^h%+)7I&-6n2^;-|=^fGv)Gt!{A#xApy|U)N z=a+R^;_`#F{&8{h$CG!RHb4Ho zORN@!igRK6)M7)$otDd}5?^?&bcKd^Sry7p`jN zj)G3Sh<`W8vjN74e_s(NZicu`632N(&L!)c{EsU*lh4H^iCgokgU9s|*G=5tI-kk- zDG;}PO>ORTCcPeF{?Y5=x%q7-M(I)ioQ|`T6(>?Q9fF;cvcK(tJmdn zb$`?1tME?JwUMst)wSA2>DRwqZ=Uls9&bNg%5OvX=b4=3*IKKcv^c-{{^mAp+X_8j zb~mnAW*g9w8H?0!lKfgvt<}!8oX?H(uRLvT;F*|Z|M}+`>8JRI$!~3~#`LPkKc`Cn zxdNA1tGS|LE?p;kK~JC?n!I@+O}g!*J3`XwBJ)EZegOaCK*A_LdHfFiDdPEDi0kCX zyh)l>sYj9RJO3ZlW14iKvucZe?@`KgKEm}T`FZCZZ(f_fE|=pkrrW*ahyAS2lf36v z4gcu3mV2v>O$|wZg+~MWILU86>Gx8u-2McM*M9sI-n-sUu4=x3M#_H6OL_SD=lb*4 z2gdcr`c=IR{np*4&m*Orij;qLZOPvq&Pw?fAIWn(T6`QoVeu}0H~v^@9M_d^p&c@> zrto9y_Sc4_;j5Z?c)sBUP48LRA3EnHXP@|**l`jbmw#B6-S&pivMtTaUds7nU>O(I z{fFMM(!97PF2YZOpIhI!e@TBz-(6#PsfR2}@;CC9e5Ee`lU$nb()6pEC0@qPwo8`A zOZ~Q3ywvY@Oa3y~ZnpS7{9P8G$7d{l82=87FX1;?{3QN-i?8BOGrY7_mc{)j&$@gs z{ULqbOS`?}*4N(tlK&d%S|ncb+}LIBziTgD?CX@@vb9k@P4>rylz*X-PMj=8`HMa) zmhDm>+5dM^pOZxBQYU@;!q1u|G)Uh<`*#J2VV(573!k4XOn?0w7Pq@j`hjIe20{7n zGt!H9`ROryoqWd{w=Uo2}>kFot+==_T9=~XSknMKlP`I!l}e}D0INn7A2blLvev%vWh+l_0C`D@(s z{d-041BA65OKugYpLL>o5CjzvchV*NFG}>w;BdPQ(3& zc^6L`nj~(JxYd8vxCetc9&R{tAuAVI9bR0CxTzrSBlY8^iQ74=`SsV2i|`z{cfZEn zV2_ji*GAmh1N&?Hr5$e}j-9k{|80=Y`pm-p=Qc^Yj67$qX}UB2@6t(s43lm*>3%BZ zMS^GL3)w3fe7DaS7t4g18HUxW(gFj?)xzI}g$P&LGaaX7-L#Z$Er;Za;K*`z=TO=9L=% z(!U)q<7kxlHHY#!vdjaaMe!R}O|5J^fg3Hkt!3iyK7$~MH(ymrH*%P!t6aXYUUkz+ zd*VE=T^rW=?Ov45YtN_W+T;H|Zi@JQ#Q#w0_XR)x9{JmXyBm$qvNo)mS=sn%s%w6V zG50x*jEiCN+rH|6KmKneKW|)kXG0+a^MKf|I8r&rQI(vxo#;14OdsEkE z=l17$UcPajXvz1zdgXs3$aiu7cam39o_rn4@9i+ooPYK6{TrV_@(g`h!`H1R<_+F{ zDv)1u%>UfR+3M$4@3XLOik2n9&m`&AknV;2-+2$|I2NQIt5UxNeDvZ_+K8VJtq0RH7HjiuS+1W- z#0?R5y5zftxbFGuGpVO6%Wj*yzWd!(bJurEKe&;8ley))|KNYV=6rfnt;V|LI`M(| zeEj=Bsq=ZRe|_p4o$}I&lVw5qH!MiE=)OhjIzfM50G(IWO@H#@^s+bRudja^q(8(@ zZ)^XRbM0T%Zd@jN<3`#)(zf4w9^<^}O7pKjZIkmPgM>-X1&q4Tr1)a#G8 zEy!og@o(ei?iY?_KfFTHf6}jq>>rn(69a$$yiegz5B!dKpTV~W{-g6ghu;|Z56}Ao zelYM~oA;ym*St>abC^Faq&{VSUKjX7=KVDOg1|fT-r;yj27cMRm*eP)z#o_|H-UfP z94+^r`S?!!NZ>y>@6-5Y?HXU0_j0`*4!n+Qsec}Sbl^WZA3uygF7Wry`x5@-!0+(A z93PYTw+8<6zDIkD`0kAK~YN3N3qS zc|tt=Gn|D{_8XTxwR4~| zTdQ3Kqwr3cfIDF){60*>-@`cZN6=muCSXskR=W*mq4a+W|0`Hw{RH~Ak93_d4{wBn z_^-lY*7v{?{41P<$1t9&@LU+CercE?zq??J_0PlTbglL!nEG93qb z-WwT5G5YydI89su#^H})bziNPWd4i)p3iuhcXGd|)!Ja1_-(KNABUafTNV$WgwyQ5 zU%(#H{R(EtZ!au8TdO?>vv3wVaM`)EkL@d<%X$;+BcEoNhc6P6-%H>yJXVO0!4ftZB80lOSU>WA1dkf{kJRFBrSP}mo>c=ChER4a(7RE0u+)jVNA{>Ru_t9P+Mdo1~ zmbcQsunPNNB+Iyj2{;Tha9sSI^cO6^PR3mkreO*8!Nhj@6=vY9@So_1H08hqEWu8g z{s7|yW?@!59E24(3Y`y<4~)TC=)&mLq=RiR3sbNF`(OzU!U`OPPLA|224|rQqt}og zw!th+!2;}qB{&Exa1=UslOD$4EOcS?TGGQdn1v}=fPJt82Vn({LgyaR!x)@}X&Bv1 zI@ksaFa=An4_4qHbnYcRjKN9h!daMx(d+0>n1ET>3G*-w3$PCsVIG#?Ff79otiVZF zg;nSbu>Y@Tf5I4y!8VwHN$A2Jn1orFhJ!Ezi!ckvVIEds0nWlAjJ$*LVH}pB3o9@M zt1ttd4>68m1QuWnj=}^iLl;iNBy@TxA4XvYCSVqJ!aPjF0_=k&n1^LJ3@fk%t8fxV zK1};z42Ex@92kR1*ap)u2{W(c<;2jE!#IpV7bai| zx-bKiFb6ZR0JCru=3yBY;4~~k=iQVGqp$)KunIe&b06)85!eS~Fb@-O7`m_olW-EI zVHIXz_$JDQF_?#KumF>=2zy`|W?=;mLg!<&A4cFfjKK;_z**?RNQUxY9HyZQGcX0S zFaz^22aB)(OK=pHVHsB7G^|4BX3E_``(X?wU;=hR7p7qn_Q5pF!wejTSy+O3I0*}| z3X3p&3+2KXEWJTN1-!F`(X@D!vu70r(76?Ntl3X*aNCgC(pL+1|4hf$b?30Q!gun5zz1p8na=3xa6!zwI6=hL(wMqm}j zVEFx%3uDlQZ7>OwFb#WP24-Ov4#EN~!Xg}pC0K!FI14K+u60rtQm%)$~Jgk@NS6*vy7umYU|?S~N<*+%&=4inIYE=<8B%)m6v z!3-?GEF6V-ScU~S4U5pZi}GOVFdQU7|g>29EL6|!8DwN8CZo`7`}&c zVGI^v8!W;kEWsXFhFMsFgRlyV(D^*=hY?tTF*pkoFmf;D!Z=Jq7p7qfW?%+pVGibD z0T$pWEWt7?!)aK7&H&}YD0Kdn_QMG5gfW&74|@9nD)a69E3?&glRYq^RNO7a26I}YW22KK-#%)&ezgaufHMK}&iumUS^7CK+1{W~ZR#$gP)FacB0g&CNHIhck8 zn1Q1(3(GJMr(pp)AE!JRg(aAPW!MQTFb%7)4?6!w`(XqQ!x${V1e}B}timJ=e}eL1 z3}#>(%)%th!yZ_KSy+ODundc^0>@z$R-p40+7BZzazEw5I7~nnx-bQkFay&t2Q#n$ zvv3sVVHp?By=94o-hK#pQ1b%gDz}?NtlFb*aI^#3$t(#=3x;Q;5aP83M|1{ScZ`y%7t-Q zg)Vd+ru{GiGcX2oFaZnDg`+SD%P0M|L+5MM6Gou(FO&zPFaZ9voH%I|4O+q4hztQMVNvmn1L0TgH>37&cD-s7=dLNgVQhpongv@QJ91Yn1-D& z1Jf`I`(PgCVF3=qA}qlYoP=dqg%ue70_DRPbjD~ujKCy}!5)}^S?Iz+n1n@`hT|{; zD=-UZVF5ei(rT7=xoQ0n5;Z(=Z90FHsJR!aPjC0_=oE zn1&_T2g@)ID{vTAVF^0lqWv%et1t$`Ba{bY(1mR<36n4ldte4;VHOU;JS@Tj9EU|% zfh9N#%P{g~%7<}Sg)Vd+rTs7hGcX2oFaZnDg`+SD%PDsm=)w$4!7R+cJj}rYEWjchg(X;qWjGBh(Ah=#FbbXBv>!%bCyc=~ zOu#!(XF(7=uOF21_sr%diJlU=~*4AauS<`(Xr*!x*f< z1e}FQ7;`SFwDaeEWt@wg;f~&KJ6bRJ&eH&Y=Z@ugk{(RopIU^6L1iwVG-uxI4r>m ztioC7{DAge3?A56eJbm1^e!V*lwNtl6En1$hgr(76=1=t3QFbPYr2bN(LR^TA4!Xk7Yr~NPj zD=-FUVFE_RC?CdQ61p%AQ!oQFFbi`q4-2pWM`00`VF^ydGIaifa$yWQ6Vww%U=qe) z4@|%;bm1UO!y?SUahQb_Sb(#z2qWL392kdX=)ww2!79u^=SS2NMqmNP;3!PMGIZfI zOhV^T%7sywfeDy}ov;Ygumt;H8RlUH4#O%eLFdQpCm4ZM7=z&w<-r(qVH+&Lw5!F#2uEg9+&T7xjV>n1%`12a_-lvv3#|UJbFcyn(0P(}!5A#V zB%FpB=oxgG%!72>@k^Y>ezhQYl;|P`x5D&Bb zJ6%NA+e^*k51^x~s{3qJO-*+TnmcQ3X!AukJFv;Il z3`6&D;)VQOgtL$O@pljL?-LI@VIHPo5%$3{92C#rJxs$2jE>Vk{5?Y-%)mU%!eLm2 zWm)I%7-nGsMt(rMU>qj+dxR9s^2>Kw7~}5=3a|*PvYo#di2V>AO+59>M=1wZU=cbm zqke3Uz&?qCdFaAnn1m&mfs?QZoySRc4Ee$&bYT&uVCGo9M;Vr28CKvlbXw@o3F-%9 zFbQ3lfhm}W8CZmQn0Pt+OST_J|Nn^mU>26)AgsV5bY4LmjKOIke{azCWAZzZI9TSF z4996_^I1!kc8bIOBx zSR$?rCnX+Mq4Nv+|0&YJ7>vO-=)x4tzzod794tRYf57zAfm#I?_fnrJ>iHY$153|R zf0+IQ^@qiMXd^5iLOe_#O1aQIj67r=hX05E!!{Uc zq8wO(gD@ZF|Ij&{xTlGS30Q(D7#E5||Ou-Dyz~b?g1xs)e zmSGiEVE7sS4`Wb%zxwaX-+SQiJ@EG)_v3PYz|lI|7Jhzr&v{I zz3C(VcK#IGTlgDgZ+VgW;5*;U^;hZku*Tn{OsMz16c0D?89#sd8$Vghbl3Sh)NbUr z`d|EXr|R|#b^GDEexvfOM!GH|-DV?QTGQQP`0bkRG~J%n?Loh(UV@Kk+Wps%Dq^^(YehK0~t-i1zUeY{h@sj3q>W7u`)c_Lz)jEEc`mZbHZ#*TR$8`O> z%Ah{uy8gH_sQ-_2y}ZE7wkLG`NoA1Fq^>`ulyN5UztQz+rHms<^JiV3RZ6V<90=C) z{PAUuDlu7{ODxi8T5SKjT%OCGZANcDT~S?ph=ezlR$OLhGiWspycuD`;FKVH{g zsQXt+k)IRl__)POJzrhNpQiqFWzgQWx_*kz3*x1&XRCjmGN^yMu3xwyUecVWew|Y4 zE`CE0uMEoV()CM~a@}|Q4Dz{M*B6c- zsp~(gze6cb(qwi0E+u~|?Cr0+b$wt#yrlVvt}iTC^0`m_$Ccvb=YC!Pq%s%}pV9RP zltF*))b)Qg{1x(tJhNA2Q;}RtEj^eO+IekCgpG z^*>q=FKNoU{u8CdN*Pb;`lJ#6l&=3$8Pw-#UH^@eukiKS`xjl`uMGO3rt1wKl^pz7 zsn2pzwPS>S=@^iVd{q4s3W?h$h$g*%<&M7zQ_MrTQ>$3fJ-5#`W;ks% z$J(rJm$4{|IUb$+{5>nNvMlU>sq-q`ex$c`@w$xJh38obG1p&imW=4S)JY=y_5MfJWm%(fQyMp*+XLV7F@L}AGJLmsGk!?@UZb2KZbZMR z*1YfXYcv1vT=@mR{9dhR5YeUWoq4xEJ2cDPZCtL;<< z`+KjJ7nC1*z^{*4e!rIAuKm+(^i#d^XY?Cr`|r|z)O104YqUJGzh;8{^$9;+i*n}U z{(6vp?2CSVgY)5{04MA=)YDiCn&ew zC^v}fRv)bQ>v~Y$n3mVB$JI-x{7De8V57ey{Rwqq z@A@ILeK~)Aknh)#C78bg?%V8d59Y1li;{zRJb1?lA1!*f>bLsgq2M~BTMG`ZD}wmo zJT>LL3@H=}=G{BK<=5kn2mJgtuk|z3Y(qcyH_u&5ecf39Ck+hZgLhB{=e=_^KDdtP z`lufnocDV_;P>21G_G6csi>|8{SbT?{$}0&Qr#Z3Pp)GZF5Y+6BZ7ARRr@Qb=Zvmz zy4*Ljy1wsPe|^6Ze?ZqGx_@fAzIawzScCs>{lE7SdrfcA{jp7XpYlQFL(0dLPbi;O zKCfJHpWkmsDUVm4py(!% zuTkEj+@`!w`JnP4Jg$_JGXDIZflp?q5TymG}Sw0!09$}^Pf zl$R;5QQo55ro2!2pz9i zw*3!(JAL;-(zhQZefvSuw;UvW=pfstX8v~l#}2Z+`2^-yfuZ4zhjd zAlv&7vc3CY{d17??FZT3QfGT_zd!#3*VnBB{yNta-V$1W-g&QWS#xRT>ML(}L$c+p zQ(kjQ>&d5{dD2ZH-kmx1l*CClffKwfr?#BddfKV2uW3DNak5u;UiZ!`-+g0C;*>K^ zdCkeE-h9=~@66nM`g|UzNgk)2;BV(U9GUUt`ck<5qBn-Fy|E|Va_T9k%J&ENTz8dM z^c7e3UU$k(H=dHd>e|pTA1t?A)_IwCO*rtU<)PshZ%lXXKkM;ZKk$?OzRTLLI{L4d zfB18g4_)$uD@Wh<^y^;L@ZGOG{rket?|8}i$Cfg^9e??+wMRX4bM@Iz-SDD2R=@Jg z?SD9@^LLN*KYaW%yHZDu9J~I>S6}su4+#a=k&FGxP;mXf*dID~->}#p77DJX z7yG7Aw_cYm_FRSEzx23k3>_W{uEQ6{uL||+`E#*r`Be#UJ+#KFhQCSK^N3Zps{QCwYG9_>s?-Tlg`T3lS}*Wc^xxZ1uba zZ>!HCb?O~g{?lx?wQFY`{{_#7 zLt~%w^AGNezh5W*M|J$3AfKQ3`Lwex%X9qL>T?0}rp?PQs=l02v{|3SZIVn_UP-p% zcj)t`z+a5F?cbEfuQuZE#M{c1X)Smr>zC;NJ6I?FGj+Ulqb>hkb^Leg_@CABWzUC0 zQyb|(7I`<1EdO06{?~Q9Tt?XH`Nul`FLivAw;#iyiAM=w37$6{r+&)t^3I3!8(KYbzZ;*cf6WrK{j{wI7d=LuQXs-IB* zJ70$`Qs1oeZM*uj>eT0Yjqft%=k4md)%R;Y@)+ILt}kf(l*V^yd`F#ff2i@TPx|eW z^PDWts~=L|;p@<`TpoDGo%&1Fcc}0Bsh>{}e}(!X^{>_V+dZ!<3$Rt=r!<~d%zDc| z*NJ~n<9lVIW4V`l%krH%@&8%J|5Wn{J>};U92bA86aU>h{n@~AW7{uR*NH#Wi)S3l z1j`aUhd)|VKwwO8slp|GG{+KUc@^s^h;}r=NG$@%?rD zyX*LS>-bG|{Gkj0*}r4@JdWYvEw5HTp`LE>mN%)NGW;FtXAJ*Y^`Zau`#;F%>N@rL zuEsYT@xN2wV)&yONK*e+^}*+C=XhQ#y#VVpzTL>@ggW)PLF2oO_z$b^Hhf2&e7>Ra zy+-_N>%>2+@%={pu{2QHJ7D+@^+Se#kNOeA->-g5J*(dGZS@m|e@^|B;a|oFbW)!g z!>?B#s`~pq$o~fQ&4&ND`WC}~SADDDf2Y3P@JI8(snn;-@UKSqi;p*|!x8Z5!*tb5hBs1J_! z!w&cB*{VJ`PS>b!SN{SpH=Z-9?=td#ulio~!G15Q?^hpu9{e-)L+XQam+=9#*KdYD zLH(HFyVOq@{ucF9>VtdFkEjnlzj$7HSbekM|3`g`;aAZiQqNYypQXOt@RzIaQXlls z9qPLc|DgI_!#}RR-|#c)2h_9bEl2VJyVP^Yh;LUvV)*OTkEv(ac*}tL2_t@&`YFRt zsh=_Ya(0B&C-kSq^TWyNn+^X)^(}_KOMR>1N7T0){wM0Y41X9GrBa`6^}+G-O7;Ef zgX3kR`T_O9xP6!UA;a%bKVtZAsvlDy?60TQPZ;qp1oyVVEd{{i*AhTo&UUwtqR4||bc?tl@0 zlKLUTU#xyaJ>BaqH>;m8;&-T@GW;XzXAJ*q^`Sp69?vi4L9O&pv*9mL-=aRK&+Y2l zjrdQi?@}L(lgHI}8}WZp-)s1jxPg-T^c(&P^#g|gkoqCRKcs%d@V`?(X80F#KuNh1 zhF_(f&^EvgcM*Pb-0eR!rh~J>T+laqeeXrpM z)%P3z+v*1l|D5_E!@r6f8L7{R;V)M|X83#6PZ<7T^;3rbmHHX=LH``ajgOSu{MW_( zdAj-*!*{E1HT-t{tWd4hVNECWca((j~M7Oa}T&{Ram--nazE6E?@d^Pe#MYt>Jw=WzCx zOVx+=`~4jFThzCx56;&G^{wiI^Yu^Ew;O)h%l-Ov8GeoWZo_X--)s0D_5FswNBw}| zA5lMK_&w@J3?DwuujiQI6Y3`nf2H~VtZoslLmI z?@`~aJ~-|^sJ>tQa&NSRLSI)upniq=r_~Rs=QhDxj(C+{pAo~KqkhcD=N;-N4F90| zDZ^LP&lvu=HGa9lUq=P?yjp!TziT2(;6J0j#qhsW-)i_ZCul#b53Xk}Ro`XA-=@CX z@SjrOYxr-g?^l1QS6e<8RzG0)mJ|K@45<&!zn7>VGvaSmKVkS!tDiFb57o~Ye!u$A zGQU5AdagOiuTQh#FH_%Q_^kR?!;h$MH~i1lcNzZBxLVxt8!c+Wm zTMXZ&zSYR*F7@q3d`W$m5r05^w-JBNtNn6&4d1W6-^k}-^#ewHRsE3RPi^({A5kC7 zS8q~3VdVc2^;1SZ-%~$h_~obi`G*=8&kwIx-)#8zs&6s;BkEfX|BU)}!yj{+Uv8J- zJJoj^{&w}fhX1_!e#8Gv{ea<{UgMWLq&_%)*Qy^e{ATrIhQCk!gyFxZeoB3?zxJvR zIg7{hQ3=03&FX{kyk32a;crskYWN-M+YSFs^<9R4PJOrGUvj!%pI*bSQ{Qj+o7E2( z{sHwvhA*ohG5ld?_~nin{!H}~hJUmADfK}=XViz5FYf10sc%*v^z(Prw;29e^{s|) zKGUyfyW!WV?=t+Y>bnj9&+2;(|9$oShOeq0F#IuV{rU_U{yg;~>W|a&X|MVz^+CUV zNPTF<;(ptuzFB?HZ@*CAV)(<(^6S%T_|w(58-BC;F2fI~?>77{^}U9FN`1fK4}Yy+ zp8>&yhCl3eez`M-?@%9Fxw!w|tG?OrUsK;=_-EC(8vdknwEqo%jruOb z-><&g@IO}HYxqOj{rvk4zgGQ#;oqfx$nX!S|39Uj33y~jb?+O4O$^2s25bxj1TkP1 z4?W|R<$>0j5ol)Gl4b@BxYg>ulA39Cw|kixK>~t=nDrqCNHBy%ge9@VB115Q0MX+! zBmo{#whsc|dnkk?JRm`jB*fu`@cvcxufBE9t-EM4-)Fb_*QZXMI<;^2ZVLWQ@-4w1 zF>2!77W`)N9l_s9zDs^l!TkF&dEvsS|1TUf`iBHRL_RF|t>i_)?<5}){JZ2O!Jj;C z;++@#X7aY+Zy{e3{13_31>Ys#5d3iyCf-eQ+de;se4E^khwJ1!ue(+(Ot*9EVV zZwUSl@=d|-CEpT!mwcPtw$G0`Wa8Zse1d#e@Fsad9f-w0tLL5MLxTSW`7pU{Z+}c) z6!K3yZ0s2ke2%;%_!{}V;2$M#3;u2LHNh{RqW=Yd0r`gDw~=oO{#o)Z!T*VTTkyv` zpZ*v882PT?Zz3;T6!rhT@_E63K;9Po=cbK4 zYl7cGzApG3JU+heX?%D*2G$ zx04SGeh+z3@UN4P2>zg>#-5Vk&nBN2{Ke#L!CynZCir{E*9HF^`G(;COui}jqmCIn zx5yttKaY}clV3=_Oui%do5^pvGM<<)bnTL!{k=~zmpdQzp`ZP91;8=c}eiI z%{Tlg@ z;Ds4u&#>UvkQW6H$VUYKHS&_+_ma<(KZyDGL-IEH0rJPq8hbX$ZMm8y-y*-7^1npB zOK!`<2gwUV(RT9dR^wmwa3B zi(hE$-x2(3@?F8F$qSbnKil-4A|EEV>HSUeBDu}qzak$I{DK$JpX4@wuO*)s^0$z; z1%Ctin$Yt}@^!(#MZO{UMK>FJHU+4SbV&a__{F&r!!CywcCivUQ*9HF^ z`G(-%Bi|JK(JwXjYzaO|zAbo{d`Iy2lJ5%sr{o27fe!zyKhL{`{ulfi#Imt-L-3o)HwAws`Ig`xBi|PMYvenEpEqyf z-4(n@UU)>beJ+y^3I0a%VZlE^UKITM z%T;zwc9iL&!gN!PvPe_)+pL@{6ePP2}4`ev5oZ@P8uTCAa1CfmIXl&}HXo z&DIZ(Cm$xavzZwdZ&@@>H{2$VhXe?yw%oS2Y*F8QwDZSun9#{bsOzd=4kZu8}f0untYAi*3<8iuajR+`6twk zJ)7hJA!|ed{^-Mzf8p&|MyVMVf7W!cHlYWLxQ)+hXsEpc~S5` zCLa;}U&u>>?>lAuGcWi{$=l?%9eV}&I{Eob;ycMV$S)xO4Ed(uJLFq}U)nJCYzzK8 z@*TlXk?#tA2YKO9(fa;H@*%;0LOv|`wJXM+qToy9BZ9w!yd?M^k%pCE4w{$}zu!S5ws7yMtzHw1s;ttQ@0!Cy+gCHR}k zw*|kKe23iDqi>KG9%KAw`G1fPliT|Bl#a2dNN(%bi^xX=e^JT1;3zg;vEtE2J({N74mt(f04W`_+8{{f`6HOo!r*PUGfdVuQ+Y&+!TD0 zd`s|`k#7tB7V;gzKTEzV__xUmk2U_c{=E2%v1dr|tI3B2KS^E`{MF-q;IAa#75q-}!f@38|C4-3@E?&6lUu)C@$<&cqL6=% z=ImUZk1@)R2>E65lHhM8pBMc1$lHQ{m3&R`!Y>#**9CtP`G(*J$u|XWkZ%e8R`P9f z>z|L2?+E$7A>So`2z_+^D~+9n$4B#JANi2rC&-5de?56o@Vm%I1pf+oN$?+%&kKIV ztBjp(!JkLICiqLq*9Cty`G(+kk#CYelK%NT`8K&7hwPH?klS&{BiD?byMjN9yzm6$ zXDh!zJ|y^UR*_z%gq1;6|?#?BqVpF_SYc%8iPbLVN_S^vL@d`R%S$%n~pfBnD6i-Mo` zi^iT2!LK4Okq_zE3xyYu&kOkmd0X(ekgo~;S@Lzkzem17Zrg`Tf63UnDdcY?-x9n^ zzAgCeoy|HIU@axET$!+mVajojALI{CWb zZzkUm{Nv=CFCf_Ev@eciWW9N?0bC7&j@E&>Ls%U%i0rDZizeYYR_{DEA@fOLg zolhq(ky|@Ykk1P}zd+t5zl8aCC;6I?{~Gza;15|h_G}0~PQEGlE6BIVt^IeA?+E_4 zaj{w(rs!57JQ1b+wlF8M>4zkfnrcxtp9{s;Mx;Mcs(*fT8n%gBp@ zzngqS@NM#v;1|A~{uKNM^0wd|@-@LfLcT8ex5zihZF}{&Up4V=3jPxEEy3SLzD<4+ z{rLs*9U))%HKTu5@T@)5zWdxx>7B>1i5^Mc<+ z-WL2jG1pg*^N$>~#x`}sQ@I&Nn!Cy*1* zJnY>j-YxQnQhthjhupTSuO#0U{G;TBtE277zmg9Le&9Duyu*U8kQW7iANh#je@$Kz z{G#8aKLvj-d0X&0`I_KwC0`f(Uh)mWe@wnf{s{W>$_-=Bwvazgz9aaX$#==GqMk31 z5AQR6ek%D-$xGyqA%Et3OuTLKr;xYFH^}#qe~5gW{ORQ1B`-X~#QOyDtA5MqFOpk5 ztK{>7e}#Nq@W;N_=-CqdCFHw;zmI&lc%HVz#``_;61iIE52l{~OJ3M-(reRo?FWqhA#z*a zXUL1>w*Hed|U7h@*Tmy zM7}HdkI4(yM9cZpe%IJDB=`yPVZnciyh#3V=Hp%DCGsKizb2m-dM^B+iMK8Ib>wS; zuaK_`emD6BxosbQK)xm9pZp;c@3!DKk?)Y(`raciTx)hY_upgeDLlvMvG%-}e3;zoe=YflkpC$8ypaDU`5O5XY0rZ`!F(aN_FPB4O>WD> z0(s%T89SdwJ#Qp0l3z>yDe^Y?jpW}Z-z0x3`K6yU_Uw?qi2QlvBhNMQT7TY3UU;72 z*8caB50n2E_53CIwva#n_f5RJPpKb`u2NIp;g4Du`gz}T}r zW%U08`2q5+=NtaB_cK3?lJ8C%Zr>+WA}<^>{KwRDi{&N5zf0b*dIUd9zH?Jl{l-ZvFEF^0k*l`8DM2mqz(0`TQ+WeiM1A9Obu=kIYB;De__^%2&yU7YzR} z%fsu)cdAkTHu8-is^>lA>x+heg?c_r-d;A``v249rFvA)7s*FnX1KNIYvhGSRQ~VC zcLo1v@|~5aeBm>!7fr*hJr5z@Y(?cCO}=q!RQ@XP#}_Ux48QOGx%BsG5g&!})ZCGBE$9SGb-ZA=Gk0XX(P{>Bm4ya^so(s+l&fbeNN zt1er?ZlTubuC5eTf|YL23q@yWsFg$9$`qvXtV+|(b0?1OFDiDnvQR(0fBy~myHV+O z%e~chfP^o$CgV~E_Ls}2SL$&|>&sTNid0JF2M|%$6AH?@&332W>@Ak7%clz69vs{$ zEVlc-ZUJ5jnx|oQM5GKqR~qd~Z&`=Huf0ySjSNKaa`{ZB(k?Hy+Cft}u2L&3RvWEu z0L7hNt5G;rZ#0y`9vrMflou=YM(DCbLGQ56l2RSlwFa3pz{k5m$3{|Xte9xdbm}NL zwFV4tbyP&Pep{!Y3TR?i7OT9(j}2IcoT{$0)wd=rH}Q-&>OnJ-d%6scw%b#SM}un6 z?N&Ogg+=(JYc0^xcUMsuS9F@7t?WR%3#8I&YfW(;NIRJ&4B89q_a#imwwL{Rjs$atsHT|QiC zsv>6!5wc1bsBX2=TnyF1RvpDu6J@Ees*u?eWOB*~+4>Ix2*U zAoAmAv(;Q(Y4y9hRw%tzVycPu5e;5>9Es`$`s-pvwF_lbl4h%nx=`uWTg`&1qDbcT z*BaG>fCuZ%+OduauGEJrm>?TL3sIfzY0GU3w9*GsQJgA7QNO97)^G@gW;W=Y4mxFc zNmhE_uR4{{*d4LZcLHh`E|uFlq^abggh_Syy&SEf&JXd*=KP2hOqIq}nH+E7#qlY= z3U-I^#&@jHfgFfhLWk8O4WwjwmX)HLF5NJXw_>@^BD2oKqMF5( z%Be_0?bqjsnnIqb{7}#gI`wK+sR%7brJU_m&>3krC3_%rZ7y*%f0-(e2E8+_P98tZ zpv&q7v%OZQvgGs;ey7c12pBZG%dNbLs&uN$Danu3$e5E*s!moqs^>kT`uC;aaI59? zbrCbiY|}8cIEtnmSi{Ygf49zQgmC4wxR#t1n} z>`1>`ujUdmxtJw-c<#vbY`>%XnT$kCHBX}(jtpiZz8;kF6ZKlq%8IyDQDdY^1G(8l zR%RC8sU}9ZIh6M5L8dO_no^_UjLUT5RjXG%T|97&j7a~01ez|-*Enj4vFX;*QcyeQ zjuk`22(Q0jq#|6c1FO!kj*ef|eYX7Q?8_6ArJ2d`(YZ-~sJWoCf@#v8gLN>LN0~dW zD$u0LSg%bmS=lyG=~WK48kl8xrO-xodM8@-YA_R2TOHlf`%;J|rJX|zk&d3m3`5Nk zwYWD@NG1O`0XtUO9kdV0GFHK{cCU_E<7{Q6-3Vsj$)xB;`@Q8^s6?*J)vKr6S)-{4 zIvbVs5Hu18Tb+}^!f3nh)j5Vyf78+G6`E|;vP8x!m_9bbSiO3bafd72Y2;F~8^AE+ zzE^N;syh}eqr*NrI_DJ_tziJzt9Nxt^-9bHxArkuo@`b-tLh-nRaYBSR+8mtrRwMc<-FrF)85p;A*E$3TAV;%zgUm=9Xi_~GYfN6k^RG73vb!M3q6z=yitp;ef(MIglD#ood#CvndHMK`@Zk?&~hsV|6H|l&Q&Q zXTGGHI8;^81wB^Ml59|ZKa5d^rxT9_Nv=GOsHta1(Hb^WvUK3z?x9Yr-_Dm8Ms{i< zZ=M+Ww2~#vz{<=5isN3aVG!Ry%{m25tobvg5REq~&8%pZOm@OYU@TuZg!Nh$OOz#yUInQW!l zGuP7nR#Ggg6;*v;?PMLxR|(Gcn1Vq**5fAXl}0PA$jqu`c4HleY8d_{MS=W064dJb zm7K!UmCjO-N0yzp^7IrI-8(C0X)Gb7lvd^T*y19FK%KNY45PK=8O-ie66R>jQ=1ua zMsY^mDot1>?O|m>E%znXM&$8Ky}De&FgH;92MK8($D(v6kKmy0owM)@0@Hvbnc|HB zfiir;+%v@@k8!3HD?eiGEWKkIZ?#cRjOxr>bYwG=XcW)Y-DUP@(=qUd5lu7n`K@|W z6}R!_N^hpsT1kq1I;flu@(3bsD`q;i%(NqBBbkv|vsk(dRqF+sBx@r^q)JX<*~X-P zw1(+!lEP5zB<3%zGsm!_g`Po81CoL$5v$t-$_Sv>kM+bTBu8w-`UdVs;TufEdKcAkb4w7h^4-M2TO+) zEDdA{%k5*4nD~aC%qpZQd-uR(*Ho9OPg>8>P8zc=3W8eKjK$(>sakS;IjEjGh*~;Z zU&4wl((F&|>~iajss`BCp3~d3o|W%Gftpmi#zD=IpAMQ!@iBi&0PoWJKwjta@u}%b zbE)r{qF7gHF`kH+YMz)$NSe1qVW-`*n5uooqZU)mvHDU8<6$`MNUN6QIK2)%Gl7l% zg;u4LQ1Yg_CuR<#yFi?1%rG=SE1R0bC^CBtL955Hidvbj^qW<5D1L{iMSm0tD$e}_j7R!*zI0C!Uc`}<*YkRqR^dbCz^Ri}xnLB;7H zzqDR6pKdKItolY%uDvjV{r^DR^vkBGq#5N2ijkDs#jZ5YEi`7ACn}A8a8993pxFkt zj|Y1`do&-#sHZkS&MAJ=pXfgMoWiSd1nLHs`?)rltp$Zs=UT!%%FSnUlt)XaIYrG5 zGllfjFjLB%IcEwZB`7;ujHyqv(xy9g7U`zm22i_?X^PR{XdANRTz%ddP*UVMa%uuc z|9Zim%rJh)GTJ6CuXMAfjZjmgBi+~%o-c(;6r+gvkkl1+o145Qt6sni<&|`yNASv-6@`#;#T_{&hn66SkOgQFSO>|vsmF5zxH%|p7r&H`S z&R3lYnz=Ss5(5nB%``=Qt;NUYjy;Ki)p$Lq&9#iq*dQxi#Y}($#@%JrY^n;AVtfe$ zj&m^E6D+^MW;?4ph!<L36E)E?I`;_j8YWpfIn$X8MgFwA}fYp3RORiiiG|-)K+Z z#0-{$I8(m5YAkmkr5IQ-HbDBQT~p2cRXNVc6cKiM^ys++;}X?51?3>LdoF>wQF|^y zvA-cImd#O>oBCLm$PobxQ(o~Ylz&}a zJnLp2TL$K54F*giyJc!OUvrqR+M@iz`p8OtPa4I#2Ieru_}zpN?$j02^uFqBrmB$p zrAFsxw29$2oZ_p*7aYUi(cdVYSol^`oN&^HlUd=n6y509mE7T0x0l0Ty}g*gx@vnHm8xf_)Dblj2_yNKTtensIW_K6f5+M|D?r=qDMk~yIt%aJ&~-u$Cc!y_m>HrG z4t;36Y}FoXA{WqK>d5WLD>iPk(l*ZrA(GT$-q2hMkOgWBP0!$yBq!8nc55}=O1LbV z=IAs-7YnmZqYge`B@%^W|MfD;s01eCfxcjrEr#Xim`f6rjdoX?u`}3lS*3BWqBeK3 z?M0Ki6kjxrTVW=q+4^ReaUeTg4DHw~QjUtqL@Gz3)e%0m-J4OIP~KIFeLFqT1X56KD`BC_-WChz zyD{lKWD<3I2S72s%3O8WY_wU+W%W({R(#Cm0sHo=DR3U(Q4kj|Hx195XD zGTmIgci&w;iTgvi>C;vPAw7WUmKZFksQ|~@H5?d7m|Tb?YtcyP>?n=?NFeOSovsIG z&ME9%y_c0pPmJj4s+~G66vTHjGn06t-c2~Qof#D8J2MI#Iu2f0B(qkDMCs8N1BH=Ppg^{N6_d*Z}w1>zB3baQuVmsdKo+z zBO^wtVyz|wS+#FqU!>f0$5xfLb5pQ~^f|o+))nogZau(F>hX{p`X;SPpeZ`}T6qyD zJpVWHILgLTjUFWWv9SXD>{U9$kT@uhAYBU>0yBD>)qQ=BOh`sNn;NR24GXMPcA>SB8HJ5H9cwe=cVu(syIM8q z;o5XY*1J!gz$P;;iTV%U49b%VkV_D$bmY?WBPy%+Grdiub#PZb!NSo#+v?zab8op7 zo3NJ=cd2-_&JKN#kQcY`;K@_1Wa>v7~~agR5p zDx$Vls>_Z%`n1#%h&qVNR>v0E#&f|kIS8wldGLi(bTPTc{nZ|pf6|?B1Ph%ER&#=@ zt0KvfqC}|2-kfV#CmtI$%}tInlFp2Z2J?(RlpLB8`}~)1&=6*5M0TQuaeqo~MufTT zv={vhk|P?&MYieIoATZ4mN7A?Xev!_iU(q-#p4^L=W*J#TgF}8aNwULG2J?IqTa%T zr{Xtjtdj9nlY&!clMYx~(ZR~FD;%o?Hs&yW zAW7y;H1Oz>u6oE(vW1hC4xX`voeSLBTgC&8a1i>eZ@3VTSl!?|h=+rm41!a*6XG=j zE|{41uUHs`MnEkECoJ7-F{T2lys^M$lguOm)KNN5YKM zT4++D)<5vc%2`9FJ=EA>aOChb11mLXQ>6KhxLSN1 z1W1uHo3W7OBQo^891K=cg6OsSa|)s6z|EkMBMuajFb36e#`51uD+ar*GlRscTmG@? z?@04>rdsRA?c)rub4Bz3?tkcqdSQ(sXP7+%;6_`fV#=xGGdWCG1xvNSI4ho&%Bijr z92Bb@A?(Jx3TN(oXE;(sky5-|)hS{qAtqhE!J(7gs#SCxmMv|xhS}$vDMa-jshl;{ zH@d%~n*`@UTdKvlMyAO9-R!$n?UT9EL~)!W45^N}Ep*f)Gj){Vz%mr+meJnfAxE7M zr3tHNbA^(zcHH?%-8M>E40FY>)~$xQX^}!CvapvXi=p7aPhDkgyw7w5%5c|-nGkp! zrah-0)1|U0c9O>Yg1#f+%E{n&HAV7tJ?3}Ss^NF_#9P&-^<$1TVoF9Pp1)N3@yl#s zAiZ;fhg2p6hFUdI^8bo_Jke_4PJ#btm@a<~U1ru7lO4z3?ZrzO&>JJeObnhuyq8eO zD2~y!bBwOEXvXw`6n)fUwvY2k@tr_Nkt*|CM(~Xxg=FdIA+@kpzW&;?@u{CHYl@c2 z{M3A;V?yb~JdH@=k$Q`Zo*6^jg6S6SjK)TLaXAb#`~K1riC85F+gI^DcvmC0TOgWI zIjGvm=Sh>1%ubV$lV#it*IY|kuJp>xNjzcMtm~(hEjI*)`Sx^CS`cH58_;*p%rz?> zVzN#m|9->*|Y5C8iLw55cRj4 zEHn_L(fQ=HRIa4iGQNk@*&z86=t}2IW9}eU(34W#=BkHkim{94;cUkvVJeRP!Hl8R zpPVepvZ$qyIgSX1UwE)_Pv20>AT|jAN`?SLc;+^xpHFBCVI!<`Gp*n%1bEF0Nk`;Eg+KX->sX+SJf} zbP4NWzUhw;uvNKfE_pjhTN%G3M@ z%@J8&<7qrCC68fM0ITeNH$oWAEZSIACS6;6vz~$eQM?SHwUo_FlQjN9y1D_1sXxx%vQ7G-WGTG-i=+Sl^bfl%u zr}Sgb&Kg*A1doNmO&L!QYecwIbV4mE`A%i|r6$i}Rnl{7F(N%%S2suxS8&#{s#_3$ zwy3%>IWdlVM6nT;QD^*Z7qyJOQoj*j9Px?cFM0t02STZWg=@dDs3tlV zwNQw1kx@~@_878i2#x2(cpqPDEQfMe2w_46iRKIn&>GGE zKPQbo9Fe5qgd>8C!Z-CklGLF(LNyr;*1X zS3cVwS2~wDw(W5nmh|E@XyAjs3Pp&S{kw^N& zZ;5@MZqIOX5B-b#)P9Y_HMzKeX^<5+OA%P!?C7KOKnW{zLKqwEp{(!8*J<)-`@8Ys zoCtxhJQ$KA#A*~PR#-;IN_r&X2p6H5hfNO!xqGW z16z6}F^B&h(KNjW>z%H;;FnVzlB(um-efQLv^?i^kt1B23k%k*YW2xy3EZH{a2=0% zOeix^3Gb70m1`k3dA8c4MX(=92Y>XYj2| z8z@aHodcn?vN;fnm7a7X=WtoIp_1*(f`L*sGw5McKo$EWeMu)dnNEGk$Xvg)OU?;V z>MN^i9Z*f#@#)znpicdClhNjCa<-c1YWOz)totkKky2x;bExw;?z5VdI<=N+ntgRB z2ro>+LjV%&SE6P+Fy@lM_z;sl*uT)P*&l3gld;r+ze+c||Ztfq?f4 zD7~eA&o0toG#O<=31*ahC6ata)G;fm=|dS-I@mg;q?@gCf`l%z7o;NZG=95w}seU8LS?YJ5?)L4DDX%ko z?7m`>*x>hs{druNPG-iLoPzFj5_CRfP*BesB{3IOb+YG_M?j_;wr| zP56__CcKv#tZ|TwT_Dhjb6#Sq5_nw!uXo4ejAN_puAFbp)crka(bSbO1C4~2J-C9V zz~m5|#w|S049?YsZrp$TVelf?vRa)|nq*~m)nNLOA#tcRhH9Lq<4_AV@{H#}>uyA- zQ06>Hnx@IKK^6P-&##=WboB!?(u9xTU@FFynIfve$|0og#^Dv?=}Fab9>JIEfL;*dod$t?n78CK=TvO9pKs z?hKt8?IpeS!Zi;*%<__KtFND;B*DM^3XTz)4g{Inwr=p=2F5$h2&6@8~`a<1EY=vofGNJ4&69rWV9qSIm=mrXv9LCM? zX#DAb9&t5A?)skU70T+@hFELyx(eTpq)iv5r0Pj~MmqG8Csj!UsW4t7NhcZ?FDH^! zVc3Z4Z#W~E6(>x~D|#rvfi{ejvwV(yVD-A8l~#rzx}bqra!OpOl$^#?tBcDGD#~PC zSY}&R3nLkZ>1BPK#8hH8^^qFSD|^EXQLEKTastH*U23PdfWrsLHgF@!hDZ~{(bWt~ zRRz_Wg2lRePr2t|o1vjBIXGlWSz|XPOe`l^MupXApWqD`Ym* zQY;8X$Iu7m67)WtAhgnnT|68>HiTwLB!BubPf?H7R+$hQs{0_l+M_zB1K|%QHuDQ| zTL0l0-5UYCb-ja1R;^$&CH~sCfs$jTgGiqDcu7R2$K!UGG=&CfOL3nYU8y>NV$^u& zgfSspgt82`^iCz*Ub7+t-6EI@8ov?b3#BG|jlD?Y6;VoFO%}rhn;h`zKZv#yFO%9s z91P)&M>|$kj-Gxax7Vf4&nOH$Jmh^wjaM?>346t~B{^j=ibhHB&Cb25jX0)!EB>*i z6KKpyp^gNj19cMHv-{(QQaxPwJ^CufFjI9B!;7=_b&r_#VsxYqN=Iv<#^De#e?U`s zeDW}pvHIWj>DM8y~ve9_*F(sZz4@2cb+_Fa|DReAw|^ z=M3|D5C%6W@Gc-HCOlFgcT~A#O_9J*RGr83R4R{AjaJurx_u6FQ~);-*+J0ihbF0| zf8W`zxM?Uk=MpRuo39beh$&wX)g%q-dT3@IXAAxLn3-VKmlt`I>qNM7%m2C#&6K<@#OxOH4i z*h7d&mE1$PSP|ZPSd1HRG(&l24okW5i$C$?sii*=2xA-nHds6~Yv^1{y;38<#*=wA zC2mQpr}pH^MsLWHXOA=1h`!Fs@2czpe%Fh*+y>I?11pSa|gbUEP*mMwY zug+7Brn;u4p3ba4FdeU}zSfARKND-<7B$SY!z#1|CTv#9kr4B|RFpA{=JdH>?{h{y z9*70>E+pm&n6FU!Y%TRt#u3If2o|=B=a5Wq;Jiuo2HtCO*=Lzm0QfUhYT7)t5U~_r zGEDZoROlNl|JRhdt@(e^NK=sgTW8rxwDU!-@njA~24#k&+qz2VG4KD(QM;T1w>N1N zlD>Q2QBE}n^?naApjmOG)2FgVKUVS8E~DOU#3kYyuE?vSAYo&X+4G6Pgjpjh=k34z zp>?m8vlgjgOIS=8Jsgq0ZIw1~>Uz#;u;IO_(2<2Ju~?_wf1NBi&d+Gvu!&Ujl$5BD z`j&DQJ1UGb6rNLNQJIX%oFhElS~?b+rpVt%o2};Risxy1LU3_0#TjFW3hiKOY-4yt z7xpw#w4iM0Oo&es9a~OT)O&>0F?qHq@#fc&MlPwxR${CgV2s{x#s(x&*|8Si44FgL z%%y$nbWxp0D&8Z8QK3?$+dYF>CW=%~Ut&&i6Vfzzg>H2EU|OoE=7DG~d(F!_8Vwwv z(j$^sBNJ6_#nP=+VSMNQqq@{L-;!)pN6JDAWX;hJZ>Vo=)EOn4RsvLo)hkN4;@gL8dd@&vKlrp7pUOMYn;Y6w&1!1}vmpQT zq8)K4RS$;5bYKauJ8`dG;DTn= z7?Cg5F>}~J#S^WHqI1**!@G|hCc(%!#V?~Y8F}QVid}v2&W5CZN8UR#NRW5l*N|K* zjYVi~_hgYHpUa}qcfDTUWjs<*7f5X9 zr#5XMh}w>nN5+HjHhQ9_y3(9aLM_LI2@t_i)b)MbXrC{HU~IM!G_h?$Gm@n)rzCW` z?Rwm-lwTwEaG76DGK50wjC|5l-Dxaq&SAEm=v{OS^(E)$CG>$N&N|Lh`SUj>1FNC~ z!k7f|Cn_Q5IC>`^rz3ELGPKn%G}XmY)%95My*cQ#oa*V9M$Ae)OQ{ia*2(wY|VM3p<6xF zL^Zx=Fx1a!`d)(|meTCeoXT@bYOhpk^-4~)7>48&hGtVnvkQ@~nX2*37S&o$Uz<-j zv&P#pm$PzM@%+^T=H_S}LcMzshH9?I$=)fYOz$y77{}OZ&pp$T9YxLnIodDlo$S`r zO6F-lxsr>%6pPdWUe}da_AutKdm@ll1u*(TRpAStcuE@*6Bpz3EbWo4k1SztNl}xl zCA_%GoKfHy#@p%pB~f`wt#+*a@C(CzxJboFjLX%Im41A0xqPNmX_pr}0WJkK3-+t} z0tfPI^qYCQto^NviJJYp;eJtnhbr_>;a6OM#p_Z#HXy%-zpEIX-^=B*uIh5RA-?Oc zVQ?!D5*7UPVs{Xs`5OH_`txEp{I$Bgf;Vr{8&zC-9(bw|`PN-s!SRk$R?_`@iu0wWF3=6^tuE`IZl0Sx zy1!U1SI?fUEYwf$-+u%CZeR_v+{4p7%XPena4#p_ZFk5|Mw>r$rI56?$sq^{JLt5GUkbD$FAI7#;6KS7OQ+v zQ;#z%mlsgG&X@M@K;%-bzcu@gK0>oiUJDup@`wNgIZy^(&^wuPdXy| zwJSw`8f7s6}MFbBJ-}mABgYlmYKhHnx=UUeUxIaGbhyQ-Y zd@VeZpV#A4{bs}O^vw_U^VOfDAU~82_uq5ziZ{MnD2`0eNCMTFvO z;bS8F$frzr`}vqVqY>Ktv-C3}{7b4PhQa66R?>XR_`d|Kbl#soY}{%7*to-d+I+HF zWc+`IA5{8{)rEo#zjcufX+DReUkl&H_e=1f4L^U6iFf`3=2OOJzuV7UgjHqKhF@m* zWrnx)1S0yM4QD_91Jt#Djp5fAe%C4%;rX?2Ap)zqW5wE^GIqD`Vmer`?C&;S>(@&W z-i?3z(Z2hHb*jfkH%yv3J`@I#+5;f6kBK6h-vP6$6N!WXtoxWXnsZA1y-iy7fR zDb(0>Tea6Cyh@Yx&(^&rz$bste6C$)CC%q>^lRbSjK3(j4bSdt@GHdiKl?d>KUDuI z|2_TVCVt!g+uucU3NL2-_ILYvnBislli^>M5PqKF*R4W+TDvV)-$fSdpLQ93?X;16 x0zMv(|7`iOcB%H!`rb&I|F@rR8txBnxzv /home/lucaber/code/steamdeck/deckjoy/../ebiten + +//replace github.com/ebitenui/ebitenui => /home/lucaber/code/steamdeck/deckjoy/../ebitenui diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6d8e122 --- /dev/null +++ b/go.sum @@ -0,0 +1,716 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +fyne.io/fyne/v2 v2.3.4 h1:CL8LBUoct2K3EF7Q7NdcDrDMcb3OrNJTghLYTFF400Q= +fyne.io/fyne/v2 v2.3.4/go.mod h1:X2+NrR+62mvAiAt2fwKT7035zQsE77KVV1NlvWo4vW8= +fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6 h1:lHt8dm97Uy9ggtnt9N6XOlsp76wXmRAh3SjReWm1e2Q= +fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.3.2 h1:+pV+tskAkn/bxEcUzGtDfw2VAe3bRQ26kdzFjPPrCww= +github.com/ebitengine/purego v0.3.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fredbi/uri v0.1.0 h1:8XBBD74STBLcWJ5smjEkKCZivSxSKMhFB0FbQUKeNyM= +github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4= +github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= +github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU= +github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E= +github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk= +github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= +github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 h1:DvHeDNqK8cxdZ7C6y88pt3uE7euZH7/LluzyfnUfH/Q= +github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16/go.mod h1:zvWM81wAVW6QfVDI6yxfbCuoLnobSYTuMsrXU/u11y8= +github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6 h1:zAAA1U4ykFwqPbcj6YDxvq3F2g0wc/ngPfLJjkR/8zs= +github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6/go.mod h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8= +github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY= +github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= +github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk= +github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA= +github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 h1:oDMiXaTMyBEuZMU53atpxqYsSB3U1CHkeAu2zr6wTeY= +github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= +github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= +github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= +github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw= +github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/veandco/go-sdl2 v0.4.35 h1:NohzsfageDWGtCd9nf7Pc3sokMK/MOK+UA2QMJARWzQ= +github.com/veandco/go-sdl2 v0.4.35/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= +golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= +golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4= +golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700= +honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..d22f741 --- /dev/null +++ b/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "github.com/lucaber/deckjoy/pkg/cmd" + "github.com/lucaber/deckjoy/pkg/setup" + "github.com/urfave/cli/v2" + "log" + "os" +) + +func main() { + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "daemon", + Usage: "daemon to configure the steam deck; start with root permissions", + Action: cmd.RunDaemon, + }, + { + Name: "cleanup", + Usage: "remove usb gadget", + Action: func(*cli.Context) error { + deckSetup, err := setup.NewDeck() + if err != nil { + return err + } + err = deckSetup.Destroy() + if err != nil { + return err + } + return nil + }, + }, + { + Name: "gui", + Usage: "show gui", + Action: cmd.RunGui, + }, + }, + Action: cmd.RunGui, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } + +} diff --git a/pkg/cmd/daemon.go b/pkg/cmd/daemon.go new file mode 100644 index 0000000..af9ad08 --- /dev/null +++ b/pkg/cmd/daemon.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "github.com/lucaber/deckjoy/pkg/daemon" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + "os" + "os/signal" + "sync" + "syscall" +) + +func RunDaemon(*cli.Context) error { + d := daemon.NewServer("/run/deckjoy.sock") + + stopOnce := &sync.Once{} + stopFunc := func() { + log.Infof("closing server") + err := d.Close() + if err != nil { + log.WithError(err).Error("failed to close server") + } + log.Infof("closed server") + } + defer stopOnce.Do(stopFunc) + + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + stopOnce.Do(stopFunc) + os.Exit(0) + }() + + err := d.Run() + if err != nil { + return err + } + return nil +} diff --git a/pkg/cmd/gui.go b/pkg/cmd/gui.go new file mode 100644 index 0000000..a2f4834 --- /dev/null +++ b/pkg/cmd/gui.go @@ -0,0 +1,51 @@ +package cmd + +import ( + "github.com/lucaber/deckjoy/pkg/gui" + "github.com/lucaber/deckjoy/pkg/service" + "github.com/lucaber/deckjoy/pkg/setup" + "github.com/lucaber/deckjoy/pkg/steamworks" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + "os" + "os/signal" + "sync" + "syscall" +) + +func RunGui(c *cli.Context) error { + + err := setup.Install() + if err != nil { + log.WithError(err).Error("installation failed") + } + + // requires a "steam_appid.txt" file in the root directory + // but then steam drm blocks the steam account from starting a different game on a different device + // with error: "another computer already playing" + err = steamworks.Init() + if err != nil { + log.WithError(err).Error("failed to init Steamworks") + } + + deck := service.NewDeck() + go deck.Run(c.Context) + + stopOnce := &sync.Once{} + stopFunc := func() { + deck.Stop() + } + defer stopOnce.Do(stopFunc) + + signals := make(chan os.Signal, 2) + signal.Notify(signals, os.Interrupt, syscall.SIGTERM) + go func() { + <-signals + stopOnce.Do(stopFunc) + os.Exit(0) + }() + + gui := gui.NewGUI(deck) + gui.Run() + return nil +} diff --git a/pkg/config/version.go b/pkg/config/version.go new file mode 100644 index 0000000..c00394e --- /dev/null +++ b/pkg/config/version.go @@ -0,0 +1,3 @@ +package config + +var Version = "" diff --git a/pkg/daemon/client.go b/pkg/daemon/client.go new file mode 100644 index 0000000..77475cb --- /dev/null +++ b/pkg/daemon/client.go @@ -0,0 +1,66 @@ +package daemon + +import ( + "context" + "github.com/lucaber/deckjoy/pkg/ipc" + "github.com/lucaber/deckjoy/pkg/util" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "net" + "os" + "time" +) + +func RunSelfAsRoot(ctx context.Context, args ...string) error { + exePath, err := os.Readlink("/proc/self/exe") + if err != nil { + return err + } + + execArgs := []string{exePath} + execArgs = append(execArgs, args...) + return util.ExecAsRoot(ctx, execArgs...) +} + +func RunDaemonProcess(ctx context.Context) chan error { + errChan := make(chan error) + go func() { + err := RunSelfAsRoot(ctx, "daemon") + // todo: stdout/stderr to errChan + if err != nil { + errChan <- err + } + close(errChan) + }() + return errChan +} + +func StopDaemonProcess(ctx context.Context, path string) error { + client, err := NewClient(ctx, path) + if err != nil { + return err + } + _, err = client.Stop(ctx, &ipc.Empty{}) + if err != nil { + return err + } + return nil +} + +func NewClient(ctx context.Context, path string) (ipc.DeckJoyDaemonClient, error) { + dialer := func(ctx context.Context, addr string) (net.Conn, error) { + return net.DialTimeout("unix", addr, time.Second) + } + c, err := grpc.DialContext( + ctx, + path, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(dialer), + grpc.WithBlock(), + ) + if err != nil { + return nil, err + } + client := ipc.NewDeckJoyDaemonClient(c) + return client, nil +} diff --git a/pkg/daemon/server.go b/pkg/daemon/server.go new file mode 100644 index 0000000..c46b0ef --- /dev/null +++ b/pkg/daemon/server.go @@ -0,0 +1,136 @@ +package daemon + +import ( + context "context" + "github.com/lucaber/deckjoy/pkg/ipc" + "github.com/lucaber/deckjoy/pkg/setup" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "net" + "os" + "syscall" + "time" +) + +type Server struct { + path string + server *grpc.Server + deck *setup.Deck +} + +func (s *Server) Stop(ctx context.Context, empty *ipc.Empty) (*ipc.Empty, error) { + go func() { + time.Sleep(100 * time.Millisecond) + _ = syscall.Kill(syscall.Getpid(), syscall.SIGINT) + time.Sleep(100 * time.Millisecond) + os.Exit(0) + }() + return &ipc.Empty{}, nil +} + +func (s *Server) Init(ctx context.Context, request *ipc.Empty) (*ipc.Empty, error) { + deck, err := setup.NewDeck() + if err != nil { + return nil, status.Errorf(codes.Internal, "could not setup deck: %s", err.Error()) + } + s.deck = deck + + _ = deck.Destroy() + + err = s.deck.SetupModules() + if err != nil { + return nil, status.Errorf(codes.Internal, "could not setup kernel modules: %s", err.Error()) + } + + err = s.deck.SetupGadget() + if err != nil { + return nil, status.Errorf(codes.Internal, "could not setup gadget: %s", err.Error()) + } + + return &ipc.Empty{}, nil +} + +func (s *Server) SetupJoystick(ctx context.Context, request *ipc.SetupJoystickRequest) (*ipc.SetupJoystickResponse, error) { + if s.deck == nil { + return nil, status.Error(codes.Unavailable, "gadget not setup") + } + path, err := s.deck.SetupJoystick(request.UserPermissions) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &ipc.SetupJoystickResponse{ + Path: path, + }, nil +} + +func (s *Server) SetupKeyboard(ctx context.Context, request *ipc.SetupKeyboardRequest) (*ipc.SetupKeyboardResponse, error) { + if s.deck == nil { + return nil, status.Error(codes.Unavailable, "gadget not setup") + } + path, err := s.deck.SetupKeyboard(request.UserPermissions) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &ipc.SetupKeyboardResponse{ + Path: path, + }, nil +} + +func (s *Server) SetupMouse(ctx context.Context, request *ipc.SetupMouseRequest) (*ipc.SetupMouseResponse, error) { + if s.deck == nil { + return nil, status.Error(codes.Unavailable, "gadget not setup") + } + path, err := s.deck.SetupMouse(request.UserPermissions) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &ipc.SetupMouseResponse{ + Path: path, + }, nil +} + +var _ ipc.DeckJoyDaemonServer = &Server{} + +func NewServer(path string) *Server { + s := &Server{ + path: path, + server: grpc.NewServer(), + } + + ipc.RegisterDeckJoyDaemonServer(s.server, s) + + return s +} + +func (s *Server) Run() error { + l, err := net.Listen("unix", s.path) + defer func() { + _ = os.Remove(s.path) + }() + if err != nil { + return err + } + + err = os.Chmod(s.path, os.ModePerm) + if err != nil { + return err + } + + return s.server.Serve(l) +} + +func (s *Server) Close() error { + s.server.Stop() + if s.deck != nil { + err := s.deck.Destroy() + if err != nil { + return err + } + s.deck = nil + } + return nil +} diff --git a/pkg/deck/info.go b/pkg/deck/info.go new file mode 100644 index 0000000..1991096 --- /dev/null +++ b/pkg/deck/info.go @@ -0,0 +1,9 @@ +package deck + +import "os" + +// SerialNumber requires root +func SerialNumber() (string, error) { + serial, err := os.ReadFile("/sys/devices/virtual/nvme-subsystem/nvme-subsys0/serial") + return string(serial), err +} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go new file mode 100644 index 0000000..2f41eb2 --- /dev/null +++ b/pkg/gui/gui.go @@ -0,0 +1,94 @@ +package gui + +import ( + "context" + "fmt" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" + "github.com/lucaber/deckjoy/pkg/config" + "github.com/lucaber/deckjoy/pkg/service" + "github.com/lucaber/deckjoy/pkg/usbgadget" + log "github.com/sirupsen/logrus" + "golang.org/x/image/colornames" + "time" +) + +type GUI struct { + deck *service.Deck + app fyne.App + window fyne.Window + inputWindow *InputWindow +} + +func NewGUI(deck *service.Deck) *GUI { + return &GUI{ + deck: deck, + } +} + +func (g *GUI) Run() { + g.app = app.New() + g.window = g.app.NewWindow("DeckJoy") + g.window.SetFullScreen(true) + + errLabel := widget.NewLabel("") + startUSBButton := widget.NewButton("Start USB", func() { + g.deck.StartDaemon(context.Background()) + }) + startInputWindowButton := widget.NewButton("Show Mouse/Keyboard", func() { + go func() { + if g.inputWindow == nil { + g.inputWindow = NewInputWindow(g.deck) + err := g.inputWindow.Run() + if err != nil { + log.WithError(err).Errorf("input window crashed") + } + } else { + g.inputWindow.Show() + } + }() + }) + startInputWindowButton.Disable() + + // todo: sometimes not working, maybe before libcomposite is loaded? + if hasDevice, err := usbgadget.HasDevice(); err != nil || !hasDevice { + startUSBButton.Disable() + startUSBButton.SetText("No USB port in DRD mode found") + } + + go func() { + // wait for daemon to start + for { + time.Sleep(100 * time.Millisecond) + if g.deck.Mouse != nil { + break + } + if g.deck.SetupErr != nil { + errLabel.SetText(g.deck.SetupErr.Error()) + } + } + startUSBButton.Disable() + startInputWindowButton.Enable() + }() + + versionText := canvas.NewText(fmt.Sprintf("%s", config.Version), colornames.Gray) + versionText.TextSize = 8 + + g.window.SetContent(container.NewVBox( + widget.NewLabel(fmt.Sprintf("DeckJoy")), + startUSBButton, + startInputWindowButton, + errLabel, + layout.NewSpacer(), + container.NewHBox( + layout.NewSpacer(), + versionText, + ), + )) + + g.window.ShowAndRun() +} diff --git a/pkg/gui/input_window.go b/pkg/gui/input_window.go new file mode 100644 index 0000000..aaa12e8 --- /dev/null +++ b/pkg/gui/input_window.go @@ -0,0 +1,363 @@ +package gui + +import ( + "errors" + "fmt" + "github.com/lucaber/deckjoy/pkg/hid" + "github.com/lucaber/deckjoy/pkg/service" + "github.com/lucaber/deckjoy/pkg/steamworks" + log "github.com/sirupsen/logrus" + "sync" +) +import "github.com/veandco/go-sdl2/sdl" + +type InputWindow struct { + deck *service.Deck + window *sdl.Window + renderer *sdl.Renderer + keyboardGui *KeyboardGUI + pendingEventsBatch []sdl.Event + pendingEventsBatchMutex *sync.Mutex + // Unlock to trigger event handling + pendingEventsLoopLock *sync.Mutex + touches map[sdl.FingerID]*Key +} + +var QuitErr = fmt.Errorf("quit") + +func NewInputWindow(deck *service.Deck) *InputWindow { + return &InputWindow{ + deck: deck, + pendingEventsBatch: []sdl.Event{}, + touches: map[sdl.FingerID]*Key{}, + pendingEventsBatchMutex: &sync.Mutex{}, + pendingEventsLoopLock: &sync.Mutex{}, + } +} + +func (iw *InputWindow) Run() error { + go iw.runPendingEventsHandler() + + var err error + sdl.Main(func() { + err = iw.runGui() + }) + return err +} + +// todo: not working in game mode +func (iw *InputWindow) Show() { + iw.window.SetFullscreen(0) + iw.window.Hide() + + iw.window.Show() + iw.window.Flash(sdl.FLASH_UNTIL_FOCUSED) + iw.window.Raise() + iw.window.SetFullscreen(sdl.WINDOW_FULLSCREEN_DESKTOP) +} + +func (iw *InputWindow) runPendingEventsHandler() { + for { + iw.pendingEventsLoopLock.Lock() + err := iw.handlePendingEventsBatch() + if err != nil { + log.WithError(err).Error("failed to handle events") + continue + } + } +} + +func (iw *InputWindow) runGui() error { + var err error + sdl.Do(func() { + iw.window, err = sdl.CreateWindow( + "DeckJoy Input", + sdl.WINDOWPOS_UNDEFINED, + sdl.WINDOWPOS_UNDEFINED, + 100, + 100, + sdl.WINDOW_FULLSCREEN_DESKTOP|sdl.WINDOW_OPENGL, + ) + }) + if err != nil { + return err + } + defer func() { + sdl.Do(func() { + iw.window.Destroy() + }) + }() + + sdl.Do(func() { + iw.renderer, err = sdl.CreateRenderer(iw.window, -1, sdl.RENDERER_ACCELERATED) + }) + if err != nil { + return err + } + defer func() { + sdl.Do(func() { + iw.renderer.Destroy() + }) + }() + + iw.keyboardGui = &KeyboardGUI{ + renderer: iw.renderer, + pixelPerUnit: 68, + keyboard: KeyboardAnsiTKL, + } + + for { + err = iw.loop() + if errors.Is(err, QuitErr) { + break + } + } + + return nil +} + +func (iw *InputWindow) loop() error { + var err error + sdl.Do(func() { + for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { + err = iw.handleEvent(event) + if err != nil { + break + } + } + }) + if err != nil { + return err + } + if !iw.pendingEventsLoopLock.TryLock() { + iw.pendingEventsLoopLock.Unlock() + } + + sdl.Do(func() { + err = iw.renderer.Clear() + }) + if err != nil { + return err + } + + sdl.Do(func() { + err = iw.renderer.SetDrawColor(0, 0, 0, 0x20) + }) + if err != nil { + return err + } + sdl.Do(func() { + w, h := iw.window.GetSize() + err = iw.renderer.FillRect(&sdl.Rect{W: w, H: h}) + }) + if err != nil { + return err + } + + err = iw.keyboardGui.Render(10, 390) + if err != nil { + return err + } + + sdl.Do(func() { + iw.renderer.Present() + sdl.Delay(1) + }) + return nil +} + +func (iw *InputWindow) getTouchCords(event *sdl.TouchFingerEvent) (int32, int32) { + windowW, windowH := iw.window.GetSize() + x := int32(event.X * float32(windowW)) + y := int32(event.Y * float32(windowH)) + return x, y +} + +func (iw *InputWindow) handlePendingEventsBatch() error { + // filtering events + // only keep the last TouchFingerEvent per finger, sdl detects double touches + eventsToApply := []sdl.Event{} + fingerEvents := map[sdl.FingerID]sdl.Event{} + iw.pendingEventsBatchMutex.Lock() + for _, event := range iw.pendingEventsBatch { + switch t := event.(type) { + case *sdl.TouchFingerEvent: + fingerEvents[t.FingerID] = event + default: + eventsToApply = append(eventsToApply, event) + } + } + iw.pendingEventsBatch = []sdl.Event{} + iw.pendingEventsBatchMutex.Unlock() + + for _, event := range fingerEvents { + eventsToApply = append(eventsToApply, event) + } + + for _, event := range eventsToApply { + switch t := event.(type) { + case *sdl.MouseMotionEvent: + err := iw.handleMouseMotionEvent(t) + if err != nil { + return err + } + case *sdl.MouseButtonEvent: + err := iw.handleMouseButtonEvent(t) + if err != nil { + return err + } + case *sdl.TouchFingerEvent: + err := iw.handleTouchFingerEvent(t) + if err != nil { + return err + } + } + } + return nil +} + +func (iw *InputWindow) storeEvent(event sdl.Event) { + iw.pendingEventsBatchMutex.Lock() + defer iw.pendingEventsBatchMutex.Unlock() + iw.pendingEventsBatch = append(iw.pendingEventsBatch, event) +} + +func (iw *InputWindow) handleEvent(event sdl.Event) error { + switch t := event.(type) { + case *sdl.QuitEvent: + return QuitErr + case *sdl.WindowEvent: + if t.Event == sdl.WINDOWEVENT_FOCUS_LOST { + log.Infof("input window lost focus") + sdl.SetRelativeMouseMode(false) + if err := steamworks.ActivateActionSetForDefaultController(steamworks.DefaultActionSet); err != nil { + log.WithError(err).Error("failed to set DefaultActionSet") + } + } else if t.Event == sdl.WINDOWEVENT_FOCUS_GAINED { + log.Infof("input window gained focus") + sdl.SetRelativeMouseMode(true) + if err := steamworks.ActivateActionSetForDefaultController(steamworks.TouchActionSet); err != nil { + log.WithError(err).Error("failed to set TouchActionSet") + } + } + case *sdl.MouseMotionEvent: + copyEvent := *t + iw.storeEvent(©Event) + case *sdl.MouseButtonEvent: + copyEvent := *t + iw.storeEvent(©Event) + case *sdl.TouchFingerEvent: + copyEvent := *t + iw.storeEvent(©Event) + } + return nil +} + +func (iw *InputWindow) handleMouseButtonEvent(t *sdl.MouseButtonEvent) error { + if iw.deck.Mouse == nil { + return nil + } + if t.Which > 100 { + // ignore touchscreen "mouse" presses + return nil + } + if t.Button == sdl.BUTTON_LEFT { + if t.Type == sdl.MOUSEBUTTONDOWN { + err := iw.deck.Mouse.PressButton(hid.MouseButtonLeft) + if err != nil { + return err + } + } else { + err := iw.deck.Mouse.ReleaseButton(hid.MouseButtonLeft) + if err != nil { + return err + } + } + } + if t.Button == sdl.BUTTON_MIDDLE { + if t.Type == sdl.MOUSEBUTTONDOWN { + err := iw.deck.Mouse.PressButton(hid.MouseButtonMiddle) + if err != nil { + return err + } + } else { + err := iw.deck.Mouse.ReleaseButton(hid.MouseButtonMiddle) + if err != nil { + return err + } + } + } + if t.Button == sdl.BUTTON_RIGHT { + if t.Type == sdl.MOUSEBUTTONDOWN { + err := iw.deck.Mouse.PressButton(hid.MouseButtonRight) + if err != nil { + return err + } + } else { + err := iw.deck.Mouse.ReleaseButton(hid.MouseButtonRight) + if err != nil { + return err + } + } + } + return nil +} +func (iw *InputWindow) handleTouchFingerEvent(t *sdl.TouchFingerEvent) error { + if iw.deck.Keyboard == nil { + return nil + } + if t.Type == sdl.FINGERDOWN { + x, y := iw.getTouchCords(t) + key, err := iw.keyboardGui.GetKeyAt(x, y) + if err != nil { + // not a key, ignore press + return nil + } + key.pressed = true + iw.touches[t.FingerID] = key + if key.Key != 0 { + err = iw.deck.Keyboard.Press(key.Key) + if err != nil { + return err + } + } + if key.ModKey != 0 { + err = iw.deck.Keyboard.PressMod(key.ModKey) + if err != nil { + return err + } + } + } else if t.Type == sdl.FINGERUP { + key, found := iw.touches[t.FingerID] + if found { + key.pressed = false + delete(iw.touches, t.FingerID) + if key.Key != 0 { + err := iw.deck.Keyboard.Release(key.Key) + if err != nil { + return err + } + } + if key.ModKey != 0 { + err := iw.deck.Keyboard.ReleaseMod(key.ModKey) + if err != nil { + return err + } + } + } + } + return nil +} + +func (iw *InputWindow) handleMouseMotionEvent(t *sdl.MouseMotionEvent) error { + if iw.deck.Mouse == nil { + return nil + } + + err := iw.deck.Mouse.Move(int16(t.XRel), int16(t.YRel)) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/gui/keyboard.go b/pkg/gui/keyboard.go new file mode 100644 index 0000000..5e6dfc3 --- /dev/null +++ b/pkg/gui/keyboard.go @@ -0,0 +1,151 @@ +package gui + +import ( + "bytes" + "fmt" + "github.com/veandco/go-sdl2/sdl" + "golang.org/x/image/bmp" + "golang.org/x/image/font" + "golang.org/x/image/font/basicfont" + "golang.org/x/image/math/fixed" + "image" + "image/color" +) + +type KeyboardGUI struct { + renderer *sdl.Renderer + pixelPerUnit float64 + keyboard Keyboard +} + +func (kr *KeyboardGUI) RenderKey(key *Key) error { + if key.Hidden { + return nil + } + + if key.pressed { + if err := kr.renderer.SetDrawColor(0x00, 0xff, 0xff, 0xff); err != nil { + return err + } + } else { + if err := kr.renderer.SetDrawColor(0xff, 0x00, 0x00, 0xff); err != nil { + return err + } + } + + keyRect := &sdl.Rect{ + X: key.renderX, + Y: key.renderY, + W: key.renderW, + H: key.renderH, + } + + if err := kr.renderer.DrawRect(keyRect); err != nil { + return err + } + + if key.renderSurface != nil { + texture, err := kr.renderer.CreateTextureFromSurface(key.renderSurface) + if err != nil { + return err + } + + if err := kr.renderer.Copy(texture, &sdl.Rect{W: keyRect.W, H: keyRect.H}, keyRect); err != nil { + return err + } + + if err := texture.Destroy(); err != nil { + return err + } + } + return nil +} + +func (kr *KeyboardGUI) PreRender(paddingX, paddingY int) error { + y := float64(paddingY) / kr.pixelPerUnit + for rowNum := range kr.keyboard.Rows { + x := float64(paddingX) / kr.pixelPerUnit + for keyNum := range kr.keyboard.Rows[rowNum].Keys { + key := &kr.keyboard.Rows[rowNum].Keys[keyNum] + + key.renderX = int32(kr.pixelPerUnit * x) + key.renderY = int32(kr.pixelPerUnit * y) + key.renderW = int32(kr.pixelPerUnit * key.WidthUnits) + key.renderH = int32(kr.pixelPerUnit) + + if key.Text != "" { + // rendering text without sdl_ttf or sdl_image + // not installed on steam deck by default + img := image.NewRGBA(image.Rect(0, 0, int(key.renderW), int(key.renderH))) + location := fixed.Point26_6{fixed.I(20), fixed.I(30)} + d := &font.Drawer{ + Dst: img, + Src: image.NewUniform(color.RGBA{200, 100, 0, 255}), + Face: basicfont.Face7x13, + Dot: location, + } + + d.DrawString(key.Text) + + bmpBytes := bytes.Buffer{} + err := bmp.Encode(&bmpBytes, img) + if err != nil { + return err + } + + rw, err := sdl.RWFromMem(bmpBytes.Bytes()) + if err != nil { + return err + } + surface, err := sdl.LoadBMPRW(rw, true) + if err != nil { + return err + } + key.renderSurface = surface + } + + x += key.WidthUnits + } + y += 1 + } + return nil +} + +func (kr *KeyboardGUI) Render(paddingX, paddingY int) error { + if !kr.keyboard.PreRendered { + if err := kr.PreRender(paddingX, paddingY); err != nil { + return err + } + kr.keyboard.PreRendered = true + } + + for rowNum := range kr.keyboard.Rows { + for keyNum := range kr.keyboard.Rows[rowNum].Keys { + key := &kr.keyboard.Rows[rowNum].Keys[keyNum] + var err error + sdl.Do(func() { + err = kr.RenderKey(key) + }) + if err != nil { + return err + } + } + } + + return nil +} + +var KeyNotFoundErr = fmt.Errorf("key not found") + +func (kr *KeyboardGUI) GetKeyAt(x int32, y int32) (*Key, error) { + for rowNum := range kr.keyboard.Rows { + for keyNum := range kr.keyboard.Rows[rowNum].Keys { + key := &kr.keyboard.Rows[rowNum].Keys[keyNum] + if x > key.renderX && x < (key.renderX+key.renderW) && + y > key.renderY && y < (key.renderY+key.renderH) { + return key, nil + } + } + } + return nil, KeyNotFoundErr +} diff --git a/pkg/gui/keyboard_layout.go b/pkg/gui/keyboard_layout.go new file mode 100644 index 0000000..fb4d40c --- /dev/null +++ b/pkg/gui/keyboard_layout.go @@ -0,0 +1,528 @@ +package gui + +import ( + "github.com/lucaber/deckjoy/pkg/hid" + "github.com/veandco/go-sdl2/sdl" +) + +type Key struct { + WidthUnits float64 + ModKey hid.KeyboardModKey + Key hid.KeyboardKey + Hidden bool + Text string + + renderX int32 + renderY int32 + renderW int32 + renderH int32 + renderSurface *sdl.Surface + pressed bool +} + +type KeyRow struct { + // Left to Right + Keys []Key +} + +type Keyboard struct { + // Top to Bottom + Rows []KeyRow + PreRendered bool +} + +var KeyboardAnsiTKL = Keyboard{ + Rows: []KeyRow{ + { + Keys: []Key{ + { + WidthUnits: 1, + Key: hid.KeyboardKeyESC, + Text: "esc", + }, + { + WidthUnits: 1, + Hidden: true, + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF1, + Text: "F1", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF2, + Text: "F2", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF3, + Text: "F3", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF4, + Text: "F4", + }, + { + WidthUnits: 0.5, + Hidden: true, + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF5, + Text: "F5", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF6, + Text: "F6", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF7, + Text: "F7", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF8, + Text: "F8", + }, + { + WidthUnits: 0.5, + Hidden: true, + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF9, + Text: "F9", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF10, + Text: "F10", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF11, + Text: "F11", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF12, + Text: "F12", + }, + { + WidthUnits: 0.5, + Hidden: true, + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeySYSRQ, + Text: "sysrq", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeySCROLLLOCK, + Text: "scroll", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyPAUSE, + Text: "pause", + }, + }, + }, + { + Keys: []Key{ + { + WidthUnits: 1, + Key: hid.KeyboardKeyGRAVE, + Text: "` ~", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey1, + Text: "1 !", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey2, + Text: "2 @", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey3, + Text: "3 #", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey4, + Text: "4 $", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey5, + Text: "5 %", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey6, + Text: "6 ^", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey7, + Text: "7 &", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey8, + Text: "8 *", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey9, + Text: "9 (", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKey0, + Text: "0 )", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyMINUS, + Text: "- _", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyEQUAL, + Text: "= +", + }, + { + WidthUnits: 2, + Key: hid.KeyboardKeyBACKSPACE, + Text: "backspace", + }, + { + WidthUnits: 0.5, + Hidden: true, + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyINSERT, + Text: "ins", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyHOME, + Text: "home", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyPAGEUP, + Text: "pgup", + }, + }, + }, + { + Keys: []Key{ + { + WidthUnits: 1.5, + Key: hid.KeyboardKeyTAB, + Text: "tab", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyQ, + Text: "Q", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyW, + Text: "W", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyE, + Text: "E", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyR, + Text: "R", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyT, + Text: "T", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyY, + Text: "Y", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyU, + Text: "U", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyI, + Text: "I", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyO, + Text: "O", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyP, + Text: "P", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyLEFTBRACE, + Text: "[ {", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyRIGHTBRACE, + Text: "] }", + }, + { + WidthUnits: 1.5, + Key: hid.KeyboardKeyBACKSLASH, + Text: "\\ |", + }, + { + WidthUnits: 0.5, + Hidden: true, + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyDELETE, + Text: "del", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyEND, + Text: "end", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyPAGEDOWN, + Text: "pgdn", + }, + }, + }, + { + Keys: []Key{ + { + WidthUnits: 1.75, + Key: hid.KeyboardKeyCAPSLOCK, + Text: "caps", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyA, + Text: "A", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyS, + Text: "S", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyD, + Text: "D", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyF, + Text: "F", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyG, + Text: "G", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyH, + Text: "H", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyJ, + Text: "J", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyK, + Text: "K", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyL, + Text: "L", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeySEMICOLON, + Text: "; :", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyAPOSTROPHE, + Text: "' \"", + }, + { + WidthUnits: 2.25, + Key: hid.KeyboardKeyENTER, + Text: "return", + }, + }, + }, + { + Keys: []Key{ + { + WidthUnits: 2.25, + ModKey: hid.KeyboardModKeyLShift, + Text: "shift", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyZ, + Text: "Z", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyX, + Text: "X", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyC, + Text: "C", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyV, + Text: "V", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyB, + Text: "B", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyN, + Text: "N", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyM, + Text: "M", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyCOMMA, + Text: ", <", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyDOT, + Text: ". >", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeySLASH, + Text: "/ ?", + }, + { + WidthUnits: 2.75, + ModKey: hid.KeyboardModKeyRShift, + Text: "shift", + }, + { + WidthUnits: 1.5, + Hidden: true, + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyUP, + Text: "^", + }, + }, + }, + { + Keys: []Key{ + { + WidthUnits: 1.25, + ModKey: hid.KeyboardModKeyLCtrl, + Text: "ctrl", + }, + { + WidthUnits: 1.25, + ModKey: hid.KeyboardModKeyLMeta, + Text: "meta", + }, + { + WidthUnits: 1.25, + ModKey: hid.KeyboardModKeyLAlt, + Text: "alt", + }, + { + WidthUnits: 6.25, + Key: hid.KeyboardKeySPACE, + Text: "space", + }, + { + WidthUnits: 1.25, + ModKey: hid.KeyboardModKeyRAlt, + Text: "alt", + }, + { + WidthUnits: 1.25, + ModKey: hid.KeyboardModKeyRMeta, + Text: "meta", + }, + { + WidthUnits: 1.25, + Key: hid.KeyboardKeyCOMPOSE, + Text: "menu", + }, + { + WidthUnits: 1.25, + ModKey: hid.KeyboardModKeyRCtrl, + Text: "ctrl", + }, + { + WidthUnits: 0.5, + Hidden: true, + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyLEFT, + Text: "<", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyDOWN, + Text: "v", + }, + { + WidthUnits: 1, + Key: hid.KeyboardKeyRIGHT, + Text: ">", + }, + }, + }, + }, +} diff --git a/pkg/hid/device.go b/pkg/hid/device.go new file mode 100644 index 0000000..1565a5f --- /dev/null +++ b/pkg/hid/device.go @@ -0,0 +1,35 @@ +package hid + +import ( + "errors" + "os" +) + +type Device struct { + path string + file *os.File +} + +func (d *Device) Open() error { + file, err := os.OpenFile(d.path, os.O_RDWR, os.ModeCharDevice) + if err != nil { + return err + } + d.file = file + return nil +} + +func (d *Device) Write(b []byte) error { + if d.file == nil { + if err := d.Open(); err != nil { + return err + } + } + + if _, err := d.file.Write(b); err != nil && !errors.Is(err, os.ErrClosed) { + return err + } else if err != nil { + return d.Write(b) + } + return nil +} diff --git a/pkg/hid/joystick.go b/pkg/hid/joystick.go new file mode 100644 index 0000000..818688b --- /dev/null +++ b/pkg/hid/joystick.go @@ -0,0 +1,122 @@ +package hid + +import ( + "fmt" + "sync" + "time" +) + +var JoystickReportDesc = []byte{ + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x04, // USAGE (Joystick) + 0xa1, 0x01, // COLLECTION (Application) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x01, // USAGE (Pointer) + + 0xa1, 0x00, // COLLECTION (Physical) + + // USAGE AXIS 0-7 + 0x09, 0x30, + 0x09, 0x31, + 0x09, 0x32, + 0x09, 0x33, + 0x09, 0x34, + 0x09, 0x35, + 0x09, 0x36, + 0x09, 0x37, + + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x08, // REPORT_COUNT (8) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xc0, // END_COLLECTION + + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x0b, // USAGE_MAXIMUM (Button 11) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x95, 0x0b, // REPORT_COUNT (11) + 0x55, 0x00, // UNIT_EXPONENT (0) + 0x65, 0x00, // UNIT (None) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + // padding + 0x75, 0x01, // REPORT_SIZE (1) + 0x95, 0x05, // REPORT_COUNT (5) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + 0xc0, // END_COLLECTION +} + +type Joystick struct { + *Device + state []byte + sendMutex *sync.Mutex +} + +func (j *Joystick) PressButton(num uint8) error { + if num < 8 { + j.state[8] |= byte(0x01) << num + } else if num < 16 { + j.state[9] |= byte(0x01) << (num - 8) + } + return j.SendState() +} + +func (j *Joystick) ReleaseButton(num uint8) error { + if num < 8 { + j.state[8] &= ^(byte(0x01) << num) + } else if num < 16 { + j.state[9] &= ^(byte(0x01) << (num - 8)) + } + return j.SendState() +} + +func (j *Joystick) SetAxis(num uint8, value int16) error { + if num >= 8 { + return nil + } + prev := j.state[num] + new := byte(value >> 8) + if prev == new { + return nil + } + j.state[num] = byte(value >> 8) + return j.SendState() +} + +func (j *Joystick) SendState() error { + j.sendMutex.Lock() + defer j.sendMutex.Unlock() + err := j.Write(j.state) + if err != nil { + return fmt.Errorf("failed to set joystick state: %w", err) + } + return nil +} + +func NewJoystick(path string) *Joystick { + j := &Joystick{ + Device: &Device{ + path: path, + }, + state: make([]byte, 10), + sendMutex: &sync.Mutex{}, + } + + // set initial trigger states + j.state[2] = 0x80 + j.state[5] = 0x80 + + // send initial state + go func() { + // wait for device to fully setup? somehow not working otherwise + time.Sleep(time.Second) + _ = j.SendState() + }() + + return j +} diff --git a/pkg/hid/keyboard.go b/pkg/hid/keyboard.go new file mode 100644 index 0000000..8e0705f --- /dev/null +++ b/pkg/hid/keyboard.go @@ -0,0 +1,94 @@ +package hid + +var KeyboardReportDesc = []byte{ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x06, /* USAGE (Keyboard) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ + 0x19, 0xe0, /* USAGE_MINIMUM (Keyboard LeftControl) */ + 0x29, 0xe7, /* USAGE_MAXIMUM (Keyboard Right GUI) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x95, 0x08, /* REPORT_COUNT (8) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + //0x95, 0x01, /* REPORT_COUNT (1) */ + //0x75, 0x08, /* REPORT_SIZE (8) */ + //0x81, 0x03, /* INPUT (Cnst,Var,Abs) */ + //0x95, 0x05, /* REPORT_COUNT (5) */ + //0x75, 0x01, /* REPORT_SIZE (1) */ + //0x05, 0x08, /* USAGE_PAGE (LEDs) */ + //0x19, 0x01, /* USAGE_MINIMUM (Num Lock) */ + //0x29, 0x05, /* USAGE_MAXIMUM (Kana) */ + //0x91, 0x02, /* OUTPUT (Data,Var,Abs) */ + //0x95, 0x01, /* REPORT_COUNT (1) */ + //0x75, 0x03, /* REPORT_SIZE (3) */ + //0x91, 0x03, /* OUTPUT (Cnst,Var,Abs) */ + 0x95, 0x06, /* REPORT_COUNT (6) */ + 0x75, 0x08, /* REPORT_SIZE (8) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x65, /* LOGICAL_MAXIMUM (101) */ + 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ + 0x19, 0x00, /* USAGE_MINIMUM (Reserved) */ + 0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */ + 0x81, 0x00, /* INPUT (Data,Ary,Abs) */ + 0xc0, /* END_COLLECTION */ +} + +type Keyboard struct { + *Device + modKeysPressed map[KeyboardModKey]any + keysPressed map[KeyboardKey]any +} + +func (k *Keyboard) PressMod(key KeyboardModKey) error { + k.modKeysPressed[key] = struct{}{} + return k.SendState() +} +func (k *Keyboard) ReleaseMod(key KeyboardModKey) error { + delete(k.modKeysPressed, key) + return k.SendState() +} +func (k *Keyboard) Press(key KeyboardKey) error { + k.keysPressed[key] = struct{}{} + return k.SendState() +} +func (k *Keyboard) Release(key KeyboardKey) error { + delete(k.keysPressed, key) + return k.SendState() +} + +func (k *Keyboard) SendState() error { + state := []byte{0x00} + + for modKey := range k.modKeysPressed { + state[0] |= byte(modKey) + } + + if len(k.keysPressed) <= 6 { + for key := range k.keysPressed { + state = append(state, byte(key)) + } + for len(state) < 7 { + state = append(state, 0x00) + } + } else { + for len(state) < 7 { + state = append(state, 0x01) + } + } + + return k.Write(state) +} + +func NewKeyboard(path string) *Keyboard { + k := &Keyboard{ + Device: &Device{ + path: path, + }, + keysPressed: map[KeyboardKey]any{}, + modKeysPressed: map[KeyboardModKey]any{}, + } + + return k +} diff --git a/pkg/hid/keyboard_keys.go b/pkg/hid/keyboard_keys.go new file mode 100644 index 0000000..3dffa89 --- /dev/null +++ b/pkg/hid/keyboard_keys.go @@ -0,0 +1,129 @@ +package hid + +// https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2 + +type KeyboardModKey byte + +const ( + KeyboardModKeyLCtrl = KeyboardModKey(0x01) + KeyboardModKeyLShift = KeyboardModKey(0x02) + KeyboardModKeyLAlt = KeyboardModKey(0x04) + KeyboardModKeyLMeta = KeyboardModKey(0x08) + KeyboardModKeyRCtrl = KeyboardModKey(0x10) + KeyboardModKeyRShift = KeyboardModKey(0x20) + KeyboardModKeyRAlt = KeyboardModKey(0x40) + KeyboardModKeyRMeta = KeyboardModKey(0x80) +) + +type KeyboardKey byte + +const ( + KeyErr = KeyboardKey(0x01) + + KeyboardKeyA = KeyboardKey(0x04) + KeyboardKeyB = KeyboardKey(0x05) + KeyboardKeyC = KeyboardKey(0x06) + KeyboardKeyD = KeyboardKey(0x07) + KeyboardKeyE = KeyboardKey(0x08) + KeyboardKeyF = KeyboardKey(0x09) + KeyboardKeyG = KeyboardKey(0x0a) + KeyboardKeyH = KeyboardKey(0x0b) + KeyboardKeyI = KeyboardKey(0x0c) + KeyboardKeyJ = KeyboardKey(0x0d) + KeyboardKeyK = KeyboardKey(0x0e) + KeyboardKeyL = KeyboardKey(0x0f) + KeyboardKeyM = KeyboardKey(0x10) + KeyboardKeyN = KeyboardKey(0x11) + KeyboardKeyO = KeyboardKey(0x12) + KeyboardKeyP = KeyboardKey(0x13) + KeyboardKeyQ = KeyboardKey(0x14) + KeyboardKeyR = KeyboardKey(0x15) + KeyboardKeyS = KeyboardKey(0x16) + KeyboardKeyT = KeyboardKey(0x17) + KeyboardKeyU = KeyboardKey(0x18) + KeyboardKeyV = KeyboardKey(0x19) + KeyboardKeyW = KeyboardKey(0x1a) + KeyboardKeyX = KeyboardKey(0x1b) + KeyboardKeyY = KeyboardKey(0x1c) + KeyboardKeyZ = KeyboardKey(0x1d) + + KeyboardKey1 = KeyboardKey(0x1e) + KeyboardKey2 = KeyboardKey(0x1f) + KeyboardKey3 = KeyboardKey(0x20) + KeyboardKey4 = KeyboardKey(0x21) + KeyboardKey5 = KeyboardKey(0x22) + KeyboardKey6 = KeyboardKey(0x23) + KeyboardKey7 = KeyboardKey(0x24) + KeyboardKey8 = KeyboardKey(0x25) + KeyboardKey9 = KeyboardKey(0x26) + KeyboardKey0 = KeyboardKey(0x27) + + KeyboardKeyENTER = KeyboardKey(0x28) + KeyboardKeyESC = KeyboardKey(0x29) + KeyboardKeyBACKSPACE = KeyboardKey(0x2a) + KeyboardKeyTAB = KeyboardKey(0x2b) + KeyboardKeySPACE = KeyboardKey(0x2c) + KeyboardKeyMINUS = KeyboardKey(0x2d) + KeyboardKeyEQUAL = KeyboardKey(0x2e) + KeyboardKeyLEFTBRACE = KeyboardKey(0x2f) + KeyboardKeyRIGHTBRACE = KeyboardKey(0x30) + KeyboardKeyBACKSLASH = KeyboardKey(0x31) + KeyboardKeyHASHTILDE = KeyboardKey(0x32) + KeyboardKeySEMICOLON = KeyboardKey(0x33) + KeyboardKeyAPOSTROPHE = KeyboardKey(0x34) + KeyboardKeyGRAVE = KeyboardKey(0x35) + KeyboardKeyCOMMA = KeyboardKey(0x36) + KeyboardKeyDOT = KeyboardKey(0x37) + KeyboardKeySLASH = KeyboardKey(0x38) + KeyboardKeyCAPSLOCK = KeyboardKey(0x39) + + KeyboardKeyF1 = KeyboardKey(0x3a) + KeyboardKeyF2 = KeyboardKey(0x3b) + KeyboardKeyF3 = KeyboardKey(0x3c) + KeyboardKeyF4 = KeyboardKey(0x3d) + KeyboardKeyF5 = KeyboardKey(0x3e) + KeyboardKeyF6 = KeyboardKey(0x3f) + KeyboardKeyF7 = KeyboardKey(0x40) + KeyboardKeyF8 = KeyboardKey(0x41) + KeyboardKeyF9 = KeyboardKey(0x42) + KeyboardKeyF10 = KeyboardKey(0x43) + KeyboardKeyF11 = KeyboardKey(0x44) + KeyboardKeyF12 = KeyboardKey(0x45) + + KeyboardKeySYSRQ = KeyboardKey(0x46) + KeyboardKeySCROLLLOCK = KeyboardKey(0x47) + KeyboardKeyPAUSE = KeyboardKey(0x48) + KeyboardKeyINSERT = KeyboardKey(0x49) + KeyboardKeyHOME = KeyboardKey(0x4a) + KeyboardKeyPAGEUP = KeyboardKey(0x4b) + KeyboardKeyDELETE = KeyboardKey(0x4c) + KeyboardKeyEND = KeyboardKey(0x4d) + KeyboardKeyPAGEDOWN = KeyboardKey(0x4e) + KeyboardKeyRIGHT = KeyboardKey(0x4f) + KeyboardKeyLEFT = KeyboardKey(0x50) + KeyboardKeyDOWN = KeyboardKey(0x51) + KeyboardKeyUP = KeyboardKey(0x52) + + KeyboardKeyNUMLOCK = KeyboardKey(0x53) + KeyboardKeyKPSLASH = KeyboardKey(0x54) + KeyboardKeyKPASTERISK = KeyboardKey(0x55) + KeyboardKeyKPMINUS = KeyboardKey(0x56) + KeyboardKeyKPPLUS = KeyboardKey(0x57) + KeyboardKeyKPENTER = KeyboardKey(0x58) + KeyboardKeyKP1 = KeyboardKey(0x59) + KeyboardKeyKP2 = KeyboardKey(0x5a) + KeyboardKeyKP3 = KeyboardKey(0x5b) + KeyboardKeyKP4 = KeyboardKey(0x5c) + KeyboardKeyKP5 = KeyboardKey(0x5d) + KeyboardKeyKP6 = KeyboardKey(0x5e) + KeyboardKeyKP7 = KeyboardKey(0x5f) + KeyboardKeyKP8 = KeyboardKey(0x60) + KeyboardKeyKP9 = KeyboardKey(0x61) + KeyboardKeyKP0 = KeyboardKey(0x62) + KeyboardKeyKPDOT = KeyboardKey(0x63) + + KeyboardKey102ND = KeyboardKey(0x64) + KeyboardKeyCOMPOSE = KeyboardKey(0x65) + KeyboardKeyPOWER = KeyboardKey(0x66) + KeyboardKeyKPEQUAL = KeyboardKey(0x67) +) diff --git a/pkg/hid/mouse.go b/pkg/hid/mouse.go new file mode 100644 index 0000000..c47226e --- /dev/null +++ b/pkg/hid/mouse.go @@ -0,0 +1,75 @@ +package hid + +var MouseReportDesc = []byte{ + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x02, // USAGE (Mouse) + 0xa1, 0x01, // COLLECTION (Application) + 0x09, 0x01, // USAGE (Pointer) + 0xa1, 0x00, // COLLECTION (Physical) + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x03, // USAGE_MAXIMUM (Button 3) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x03, // REPORT_COUNT (3) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x05, // REPORT_SIZE (5) + 0x81, 0x03, // INPUT (Cnst,Var,Abs) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x16, 0x01, 0x80, // Logical Minimum (-32767) + 0x26, 0xFF, 0x7F, // Logical Maximum (32767) + //0x15, 0x81, // LOGICAL_MINIMUM (-127) + //0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x10, // REPORT_SIZE (16) + 0x95, 0x02, // REPORT_COUNT (2) + 0x81, 0x06, // INPUT (Data,Var,Rel) + 0xc0, // END_COLLECTION + 0xc0, // END_COLLECTION +} + +type Mouse struct { + *Device + buttons byte +} + +func (k *Mouse) Move(x, y int16) error { + err := k.Write([]byte{k.buttons, + byte(x & 0xff), + byte(x >> 8), + byte(y & 0xff), + byte(y >> 8), + }) + if err != nil { + return err + } + return k.Write([]byte{k.buttons, 0x00, 0x00, 0x00, 0x00}) +} + +type MouseButton uint8 + +const MouseButtonLeft = MouseButton(0) +const MouseButtonRight = MouseButton(1) +const MouseButtonMiddle = MouseButton(2) + +func (k *Mouse) PressButton(button MouseButton) error { + k.buttons |= 0x1 << button + return k.Write([]byte{k.buttons, 0x00, 0x00, 0x00, 0x00}) +} +func (k *Mouse) ReleaseButton(button MouseButton) error { + k.buttons &= ^(0x1 << button) + return k.Write([]byte{k.buttons, 0x00, 0x00, 0x00, 0x00}) +} + +func NewMouse(path string) *Mouse { + k := &Mouse{ + Device: &Device{ + path: path, + }, + buttons: 0x00, + } + return k +} diff --git a/pkg/ipc/daemon.pb.go b/pkg/ipc/daemon.pb.go new file mode 100644 index 0000000..1820e29 --- /dev/null +++ b/pkg/ipc/daemon.pb.go @@ -0,0 +1,772 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.21.12 +// source: daemon.proto + +package ipc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{0} +} + +type SetupJoystickRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserPermissions bool `protobuf:"varint,1,opt,name=userPermissions,proto3" json:"userPermissions,omitempty"` +} + +func (x *SetupJoystickRequest) Reset() { + *x = SetupJoystickRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetupJoystickRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetupJoystickRequest) ProtoMessage() {} + +func (x *SetupJoystickRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetupJoystickRequest.ProtoReflect.Descriptor instead. +func (*SetupJoystickRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{1} +} + +func (x *SetupJoystickRequest) GetUserPermissions() bool { + if x != nil { + return x.UserPermissions + } + return false +} + +type SetupJoystickResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` +} + +func (x *SetupJoystickResponse) Reset() { + *x = SetupJoystickResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetupJoystickResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetupJoystickResponse) ProtoMessage() {} + +func (x *SetupJoystickResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetupJoystickResponse.ProtoReflect.Descriptor instead. +func (*SetupJoystickResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{2} +} + +func (x *SetupJoystickResponse) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +type SetupKeyboardRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserPermissions bool `protobuf:"varint,1,opt,name=userPermissions,proto3" json:"userPermissions,omitempty"` +} + +func (x *SetupKeyboardRequest) Reset() { + *x = SetupKeyboardRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetupKeyboardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetupKeyboardRequest) ProtoMessage() {} + +func (x *SetupKeyboardRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetupKeyboardRequest.ProtoReflect.Descriptor instead. +func (*SetupKeyboardRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{3} +} + +func (x *SetupKeyboardRequest) GetUserPermissions() bool { + if x != nil { + return x.UserPermissions + } + return false +} + +type SetupKeyboardResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` +} + +func (x *SetupKeyboardResponse) Reset() { + *x = SetupKeyboardResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetupKeyboardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetupKeyboardResponse) ProtoMessage() {} + +func (x *SetupKeyboardResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetupKeyboardResponse.ProtoReflect.Descriptor instead. +func (*SetupKeyboardResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{4} +} + +func (x *SetupKeyboardResponse) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +type SetupMouseRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserPermissions bool `protobuf:"varint,1,opt,name=userPermissions,proto3" json:"userPermissions,omitempty"` +} + +func (x *SetupMouseRequest) Reset() { + *x = SetupMouseRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetupMouseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetupMouseRequest) ProtoMessage() {} + +func (x *SetupMouseRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetupMouseRequest.ProtoReflect.Descriptor instead. +func (*SetupMouseRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{5} +} + +func (x *SetupMouseRequest) GetUserPermissions() bool { + if x != nil { + return x.UserPermissions + } + return false +} + +type SetupMouseResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` +} + +func (x *SetupMouseResponse) Reset() { + *x = SetupMouseResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetupMouseResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetupMouseResponse) ProtoMessage() {} + +func (x *SetupMouseResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetupMouseResponse.ProtoReflect.Descriptor instead. +func (*SetupMouseResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{6} +} + +func (x *SetupMouseResponse) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +var File_daemon_proto protoreflect.FileDescriptor + +var file_daemon_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, + 0x64, 0x65, 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x40, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4a, 0x6f, 0x79, 0x73, 0x74, 0x69, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x72, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x22, 0x2b, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4a, 0x6f, 0x79, 0x73, 0x74, + 0x69, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, + 0x40, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0x2b, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x3d, + 0x0a, 0x11, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x75, 0x73, + 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x28, 0x0a, + 0x12, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x32, 0xd0, 0x02, 0x0a, 0x0d, 0x44, 0x65, 0x63, 0x6b, + 0x4a, 0x6f, 0x79, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x04, 0x53, 0x74, 0x6f, + 0x70, 0x12, 0x0e, 0x2e, 0x64, 0x65, 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x0e, 0x2e, 0x64, 0x65, 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x00, 0x12, 0x28, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x0e, 0x2e, 0x64, 0x65, + 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0e, 0x2e, 0x64, 0x65, + 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x50, 0x0a, + 0x0d, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4a, 0x6f, 0x79, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x12, 0x1d, + 0x2e, 0x64, 0x65, 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4a, 0x6f, + 0x79, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, + 0x64, 0x65, 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4a, 0x6f, 0x79, + 0x73, 0x74, 0x69, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x50, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x12, 0x1d, 0x2e, 0x64, 0x65, 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, + 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1e, 0x2e, 0x64, 0x65, 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4b, + 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x47, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x12, + 0x1a, 0x2e, 0x64, 0x65, 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4d, + 0x6f, 0x75, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x65, + 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x4d, 0x6f, 0x75, 0x73, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x24, 0x5a, 0x22, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x75, 0x63, 0x61, 0x62, 0x65, 0x72, + 0x2f, 0x64, 0x65, 0x63, 0x6b, 0x6a, 0x6f, 0x79, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x69, 0x70, 0x63, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_daemon_proto_rawDescOnce sync.Once + file_daemon_proto_rawDescData = file_daemon_proto_rawDesc +) + +func file_daemon_proto_rawDescGZIP() []byte { + file_daemon_proto_rawDescOnce.Do(func() { + file_daemon_proto_rawDescData = protoimpl.X.CompressGZIP(file_daemon_proto_rawDescData) + }) + return file_daemon_proto_rawDescData +} + +var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_daemon_proto_goTypes = []interface{}{ + (*Empty)(nil), // 0: deckjoy.Empty + (*SetupJoystickRequest)(nil), // 1: deckjoy.SetupJoystickRequest + (*SetupJoystickResponse)(nil), // 2: deckjoy.SetupJoystickResponse + (*SetupKeyboardRequest)(nil), // 3: deckjoy.SetupKeyboardRequest + (*SetupKeyboardResponse)(nil), // 4: deckjoy.SetupKeyboardResponse + (*SetupMouseRequest)(nil), // 5: deckjoy.SetupMouseRequest + (*SetupMouseResponse)(nil), // 6: deckjoy.SetupMouseResponse +} +var file_daemon_proto_depIdxs = []int32{ + 0, // 0: deckjoy.DeckJoyDaemon.Stop:input_type -> deckjoy.Empty + 0, // 1: deckjoy.DeckJoyDaemon.Init:input_type -> deckjoy.Empty + 1, // 2: deckjoy.DeckJoyDaemon.SetupJoystick:input_type -> deckjoy.SetupJoystickRequest + 3, // 3: deckjoy.DeckJoyDaemon.SetupKeyboard:input_type -> deckjoy.SetupKeyboardRequest + 5, // 4: deckjoy.DeckJoyDaemon.SetupMouse:input_type -> deckjoy.SetupMouseRequest + 0, // 5: deckjoy.DeckJoyDaemon.Stop:output_type -> deckjoy.Empty + 0, // 6: deckjoy.DeckJoyDaemon.Init:output_type -> deckjoy.Empty + 2, // 7: deckjoy.DeckJoyDaemon.SetupJoystick:output_type -> deckjoy.SetupJoystickResponse + 4, // 8: deckjoy.DeckJoyDaemon.SetupKeyboard:output_type -> deckjoy.SetupKeyboardResponse + 6, // 9: deckjoy.DeckJoyDaemon.SetupMouse:output_type -> deckjoy.SetupMouseResponse + 5, // [5:10] is the sub-list for method output_type + 0, // [0:5] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_daemon_proto_init() } +func file_daemon_proto_init() { + if File_daemon_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_daemon_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetupJoystickRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetupJoystickResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetupKeyboardRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetupKeyboardResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetupMouseRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetupMouseResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_daemon_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_daemon_proto_goTypes, + DependencyIndexes: file_daemon_proto_depIdxs, + MessageInfos: file_daemon_proto_msgTypes, + }.Build() + File_daemon_proto = out.File + file_daemon_proto_rawDesc = nil + file_daemon_proto_goTypes = nil + file_daemon_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// DeckJoyDaemonClient is the client API for DeckJoyDaemon service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type DeckJoyDaemonClient interface { + Stop(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) + Init(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) + SetupJoystick(ctx context.Context, in *SetupJoystickRequest, opts ...grpc.CallOption) (*SetupJoystickResponse, error) + SetupKeyboard(ctx context.Context, in *SetupKeyboardRequest, opts ...grpc.CallOption) (*SetupKeyboardResponse, error) + SetupMouse(ctx context.Context, in *SetupMouseRequest, opts ...grpc.CallOption) (*SetupMouseResponse, error) +} + +type deckJoyDaemonClient struct { + cc grpc.ClientConnInterface +} + +func NewDeckJoyDaemonClient(cc grpc.ClientConnInterface) DeckJoyDaemonClient { + return &deckJoyDaemonClient{cc} +} + +func (c *deckJoyDaemonClient) Stop(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/deckjoy.DeckJoyDaemon/Stop", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *deckJoyDaemonClient) Init(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/deckjoy.DeckJoyDaemon/Init", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *deckJoyDaemonClient) SetupJoystick(ctx context.Context, in *SetupJoystickRequest, opts ...grpc.CallOption) (*SetupJoystickResponse, error) { + out := new(SetupJoystickResponse) + err := c.cc.Invoke(ctx, "/deckjoy.DeckJoyDaemon/SetupJoystick", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *deckJoyDaemonClient) SetupKeyboard(ctx context.Context, in *SetupKeyboardRequest, opts ...grpc.CallOption) (*SetupKeyboardResponse, error) { + out := new(SetupKeyboardResponse) + err := c.cc.Invoke(ctx, "/deckjoy.DeckJoyDaemon/SetupKeyboard", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *deckJoyDaemonClient) SetupMouse(ctx context.Context, in *SetupMouseRequest, opts ...grpc.CallOption) (*SetupMouseResponse, error) { + out := new(SetupMouseResponse) + err := c.cc.Invoke(ctx, "/deckjoy.DeckJoyDaemon/SetupMouse", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DeckJoyDaemonServer is the server API for DeckJoyDaemon service. +type DeckJoyDaemonServer interface { + Stop(context.Context, *Empty) (*Empty, error) + Init(context.Context, *Empty) (*Empty, error) + SetupJoystick(context.Context, *SetupJoystickRequest) (*SetupJoystickResponse, error) + SetupKeyboard(context.Context, *SetupKeyboardRequest) (*SetupKeyboardResponse, error) + SetupMouse(context.Context, *SetupMouseRequest) (*SetupMouseResponse, error) +} + +// UnimplementedDeckJoyDaemonServer can be embedded to have forward compatible implementations. +type UnimplementedDeckJoyDaemonServer struct { +} + +func (*UnimplementedDeckJoyDaemonServer) Stop(context.Context, *Empty) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Stop not implemented") +} +func (*UnimplementedDeckJoyDaemonServer) Init(context.Context, *Empty) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Init not implemented") +} +func (*UnimplementedDeckJoyDaemonServer) SetupJoystick(context.Context, *SetupJoystickRequest) (*SetupJoystickResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetupJoystick not implemented") +} +func (*UnimplementedDeckJoyDaemonServer) SetupKeyboard(context.Context, *SetupKeyboardRequest) (*SetupKeyboardResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetupKeyboard not implemented") +} +func (*UnimplementedDeckJoyDaemonServer) SetupMouse(context.Context, *SetupMouseRequest) (*SetupMouseResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetupMouse not implemented") +} + +func RegisterDeckJoyDaemonServer(s *grpc.Server, srv DeckJoyDaemonServer) { + s.RegisterService(&_DeckJoyDaemon_serviceDesc, srv) +} + +func _DeckJoyDaemon_Stop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeckJoyDaemonServer).Stop(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/deckjoy.DeckJoyDaemon/Stop", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeckJoyDaemonServer).Stop(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _DeckJoyDaemon_Init_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeckJoyDaemonServer).Init(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/deckjoy.DeckJoyDaemon/Init", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeckJoyDaemonServer).Init(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _DeckJoyDaemon_SetupJoystick_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetupJoystickRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeckJoyDaemonServer).SetupJoystick(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/deckjoy.DeckJoyDaemon/SetupJoystick", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeckJoyDaemonServer).SetupJoystick(ctx, req.(*SetupJoystickRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DeckJoyDaemon_SetupKeyboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetupKeyboardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeckJoyDaemonServer).SetupKeyboard(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/deckjoy.DeckJoyDaemon/SetupKeyboard", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeckJoyDaemonServer).SetupKeyboard(ctx, req.(*SetupKeyboardRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DeckJoyDaemon_SetupMouse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetupMouseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeckJoyDaemonServer).SetupMouse(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/deckjoy.DeckJoyDaemon/SetupMouse", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeckJoyDaemonServer).SetupMouse(ctx, req.(*SetupMouseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _DeckJoyDaemon_serviceDesc = grpc.ServiceDesc{ + ServiceName: "deckjoy.DeckJoyDaemon", + HandlerType: (*DeckJoyDaemonServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Stop", + Handler: _DeckJoyDaemon_Stop_Handler, + }, + { + MethodName: "Init", + Handler: _DeckJoyDaemon_Init_Handler, + }, + { + MethodName: "SetupJoystick", + Handler: _DeckJoyDaemon_SetupJoystick_Handler, + }, + { + MethodName: "SetupKeyboard", + Handler: _DeckJoyDaemon_SetupKeyboard_Handler, + }, + { + MethodName: "SetupMouse", + Handler: _DeckJoyDaemon_SetupMouse_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "daemon.proto", +} diff --git a/pkg/ipc/proto/daemon.proto b/pkg/ipc/proto/daemon.proto new file mode 100644 index 0000000..97f8f2d --- /dev/null +++ b/pkg/ipc/proto/daemon.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; +option go_package = "github.com/lucaber/deckjoy/pkg/ipc"; + +package deckjoy; + +service DeckJoyDaemon { + rpc Stop (Empty) returns (Empty) {} + rpc Init (Empty) returns (Empty) {} + rpc SetupJoystick (SetupJoystickRequest) returns (SetupJoystickResponse) {} + rpc SetupKeyboard (SetupKeyboardRequest) returns (SetupKeyboardResponse) {} + rpc SetupMouse (SetupMouseRequest) returns (SetupMouseResponse) {} +} + +message Empty { +} + +message SetupJoystickRequest { + bool userPermissions = 1; +} + +message SetupJoystickResponse { + string path = 1; +} + +message SetupKeyboardRequest { + bool userPermissions = 1; +} + +message SetupKeyboardResponse { + string path = 1; +} + +message SetupMouseRequest { + bool userPermissions = 1; +} + +message SetupMouseResponse { + string path = 1; +} diff --git a/pkg/ipc/proto/generate.go b/pkg/ipc/proto/generate.go new file mode 100644 index 0000000..bb76ddc --- /dev/null +++ b/pkg/ipc/proto/generate.go @@ -0,0 +1,3 @@ +package ipc + +//go:generate protoc --go_out=plugins=grpc:.. --go_opt=paths=source_relative daemon.proto diff --git a/pkg/joystick/joystick.go b/pkg/joystick/joystick.go new file mode 100644 index 0000000..4904753 --- /dev/null +++ b/pkg/joystick/joystick.go @@ -0,0 +1,77 @@ +package joystick + +import ( + "bytes" + "encoding/binary" + "fmt" + "golang.org/x/sys/unix" + "log" + "os" +) + +type Joystick struct { + path string + file *os.File + axisCount int + buttonCount int +} + +func NewJoystick(path string) *Joystick { + return &Joystick{ + path: path, + } +} + +func (j *Joystick) Open() error { + if j.file != nil { + _ = j.file.Close() + } + + f, err := os.OpenFile(j.path, os.O_RDONLY, 0666) + if err != nil { + return fmt.Errorf("failed to open joystick %s: %w", j.path, err) + } + j.file = f + + j.buttonCount, err = unix.IoctlGetInt(int(j.file.Fd()), JSIOCGBUTTONS) + if err != nil { + return fmt.Errorf("failed to get button count of %s: %w", j.path, err) + } + j.axisCount, err = unix.IoctlGetInt(int(j.file.Fd()), JSIOCGAXES) + if err != nil { + return fmt.Errorf("failed to get axis count of %s: %w", j.path, err) + } + + log.Printf("opened joystick with %d axis and %d buttons\n", j.axisCount, j.buttonCount) + + return nil +} + +func (j *Joystick) Run() <-chan js_event { + if j.file == nil { + return nil + } + + ch := make(chan js_event) + + go func() { + for { + b := make([]byte, 8) + i, err := j.file.Read(b) + if err != nil { + break + } + if i != 8 { + break + } + data := bytes.NewReader(b) + var ev js_event + err = binary.Read(data, binary.LittleEndian, &ev) + ch <- ev + } + _ = j.file.Close() + close(ch) + }() + + return ch +} diff --git a/pkg/joystick/linux.go b/pkg/joystick/linux.go new file mode 100644 index 0000000..025b0a4 --- /dev/null +++ b/pkg/joystick/linux.go @@ -0,0 +1,45 @@ +package joystick + +// linux/joystick.h + +const JS_EVENT_BUTTON = 0x01 /* button pressed/released */ +const JS_EVENT_AXIS = 0x02 /* joystick moved */ +const JS_EVENT_INIT = 0x80 /* initial state of device */ + +type js_event struct { + Time uint32 /* event timestamp in milliseconds */ + Value int16 /* value */ + Type uint8 /* event type */ + Number uint8 /* axis/button number */ +} + +var JSIOCGAXES = _IOR('j', 0x11, 1) /* get number of axes */ +var JSIOCGBUTTONS = _IOR('j', 0x12, 1) /* get number of buttons */ + +// asm/ioctl.h +const ( + _IOC_NRBITS = 8 + _IOC_TYPEBITS = 8 + _IOC_SIZEBITS = 14 + _IOC_DIRBITS = 2 + + _IOC_NRSHIFT = 0 + _IOC_TYPESHIFT = (_IOC_NRSHIFT + _IOC_NRBITS) + _IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS) + _IOC_DIRSHIFT = (_IOC_SIZESHIFT + _IOC_SIZEBITS) + + _IOC_WRITE = 1 + _IOC_READ = 2 +) + +func _IOC(dir int, t int, nr int, size int) uint { + return uint((dir << _IOC_DIRSHIFT) | (t << _IOC_TYPESHIFT) | (nr << _IOC_NRSHIFT) | (size << _IOC_SIZESHIFT)) +} + +func _IOR(t int, nr int, size int) uint { + return _IOC(_IOC_READ, t, nr, size) +} + +func _IOW(t int, nr int, size int) uint { + return _IOC(_IOC_WRITE, t, nr, size) +} diff --git a/pkg/service/deck.go b/pkg/service/deck.go new file mode 100644 index 0000000..1cead5b --- /dev/null +++ b/pkg/service/deck.go @@ -0,0 +1,126 @@ +package service + +import ( + "context" + "fmt" + "github.com/lucaber/deckjoy/pkg/daemon" + "github.com/lucaber/deckjoy/pkg/hid" + "github.com/lucaber/deckjoy/pkg/ipc" + "github.com/lucaber/deckjoy/pkg/joystick" + log "github.com/sirupsen/logrus" + "time" +) + +const SocketPath = "/run/deckjoy.sock" + +type Deck struct { + Keyboard *hid.Keyboard + Joystick *hid.Joystick + Mouse *hid.Mouse + LocalJoystick *joystick.Joystick + Daemon ipc.DeckJoyDaemonClient + daemonStop context.CancelFunc + SetupErr error +} + +func NewDeck() *Deck { + deck := &Deck{ + LocalJoystick: joystick.NewJoystick("/dev/input/js0"), + } + return deck +} + +func (d *Deck) Run(ctx context.Context) { + for { + var err error + connectCtx, connectCtxCancel := context.WithTimeout(ctx, time.Second) + d.Daemon, err = daemon.NewClient(connectCtx, SocketPath) + connectCtxCancel() + if err != nil { + log.WithError(err).Infof("failed to connect to daemon") + time.Sleep(1 * time.Second) + continue + } + break + } + + _, err := d.Daemon.Init(ctx, &ipc.Empty{}) + if err != nil { + d.SetupErr = fmt.Errorf("usb init failed: %w", err) + log.WithError(err).Infof("usb init failed") + return + } + + joystickRes, err := d.Daemon.SetupJoystick(ctx, &ipc.SetupJoystickRequest{ + UserPermissions: true, + }) + if err != nil { + d.SetupErr = fmt.Errorf("joystick setup failed: %w", err) + log.WithError(err).Infof("joystick setup failed") + return + } + log.Infof("created joystick at %s", joystickRes.Path) + d.Joystick = hid.NewJoystick(joystickRes.Path) + + keyboardRes, err := d.Daemon.SetupKeyboard(ctx, &ipc.SetupKeyboardRequest{ + UserPermissions: true, + }) + if err != nil { + d.SetupErr = fmt.Errorf("keyboard setup failed: %w", err) + log.WithError(err).Infof("keyboard setup failed") + return + } + log.Infof("created keyboard at %s", keyboardRes.Path) + d.Keyboard = hid.NewKeyboard(keyboardRes.Path) + + mouseRes, err := d.Daemon.SetupMouse(ctx, &ipc.SetupMouseRequest{ + UserPermissions: true, + }) + if err != nil { + d.SetupErr = fmt.Errorf("mouse setup failed: %w", err) + log.WithError(err).Infof("mouse setup failed") + return + } + log.Infof("created mouse at %s", mouseRes.Path) + d.Mouse = hid.NewMouse(mouseRes.Path) + + d.RunJoystick() +} + +func (d *Deck) StartDaemon(ctx context.Context) { + daemonCtx, cancel := context.WithCancel(ctx) + d.daemonStop = cancel + errors := daemon.RunDaemonProcess(daemonCtx) + go func() { + for e := range errors { + log.WithError(e).Error("daemon run error") + } + d.daemonStop = nil + log.Infof("daemon exited") + }() +} + +func (d *Deck) Stop() { + d.StopDaemon() +} + +func (d *Deck) StopDaemon() { + if d.daemonStop == nil { + // only stop daemon if we started it + return + } + + stopCtx, stopCtxCancel := context.WithTimeout(context.Background(), time.Second) + log.Infof("stopping daemon") + err := daemon.StopDaemonProcess(stopCtx, SocketPath) + if err != nil { + log.WithError(err).Errorf("failed to stop daemon") + } + // wait for process to exit normally + time.Sleep(time.Second) + + stopCtxCancel() + if d.daemonStop != nil { + d.daemonStop() + } +} diff --git a/pkg/service/joystick.go b/pkg/service/joystick.go new file mode 100644 index 0000000..42b65a0 --- /dev/null +++ b/pkg/service/joystick.go @@ -0,0 +1,43 @@ +package service + +import ( + "github.com/lucaber/deckjoy/pkg/joystick" + log "github.com/sirupsen/logrus" +) + +func (d *Deck) RunJoystick() { + go func() { + for { + err := d.LocalJoystick.Open() + if err != nil { + log.WithError(err).Errorf("failed to open joystick") + continue + } + + events := d.LocalJoystick.Run() + for ev := range events { + switch ev.Type { + case joystick.JS_EVENT_BUTTON: + log.WithField("button", ev.Number).Debugf("received button event") + if ev.Value != 0 { + err = d.Joystick.PressButton(ev.Number) + if err != nil { + log.WithError(err).WithField("button", ev.Number).Errorf("failed to press button") + } + } else { + err = d.Joystick.ReleaseButton(ev.Number) + if err != nil { + log.WithError(err).WithField("button", ev.Number).Errorf("failed to release button") + } + } + case joystick.JS_EVENT_AXIS: + log.WithField("axis", ev.Number).WithField("value", ev.Value).Debugf("received update axis event") + err = d.Joystick.SetAxis(ev.Number, ev.Value) + if err != nil { + log.WithError(err).WithField("axis", ev.Number).WithField("value", ev.Value).Errorf("failed to update axis") + } + } + } + } + }() +} diff --git a/pkg/setup/install.go b/pkg/setup/install.go new file mode 100644 index 0000000..7f8f757 --- /dev/null +++ b/pkg/setup/install.go @@ -0,0 +1,54 @@ +package setup + +import ( + "fmt" + "github.com/lucaber/deckjoy/pkg/util" + log "github.com/sirupsen/logrus" + "os" + "path" +) + +func Install() error { + if err := installControllerConfig(); err != nil { + log.WithError(err).Error("failed to install controller config") + } + return nil +} + +const controllerConfigFileName = "controller_neptune.vdf" +const steamControllerConfigsPath = ".local/share/Steam/steamapps/common/Steam Controller Configs/" +const steamControllerConfigSubPath = "config/deckjoy/" + +func installControllerConfig() error { + cwd, err := os.Getwd() + if err != nil { + return err + } + sourcePath := path.Join(cwd, controllerConfigFileName) + if _, err := os.Stat(sourcePath); err != nil { + return fmt.Errorf("file not found %s %w", sourcePath, err) + } + + home, err := os.UserHomeDir() + if err != nil { + return err + } + userids, err := os.ReadDir(path.Join(home, steamControllerConfigsPath)) + if err != nil { + return err + } + for _, userid := range userids { + path := path.Join(home, steamControllerConfigsPath, userid.Name(), steamControllerConfigSubPath, controllerConfigFileName) + if _, err := os.Stat(path); err == nil { + // already exists + + //continue + } + err = util.CopyFile(sourcePath, path) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/setup/modprobe.go b/pkg/setup/modprobe.go new file mode 100644 index 0000000..8f0c933 --- /dev/null +++ b/pkg/setup/modprobe.go @@ -0,0 +1,10 @@ +package setup + +import ( + "context" + "github.com/lucaber/deckjoy/pkg/util" +) + +func Modprobe(module string) error { + return util.Exec(context.Background(), "modprobe", module) +} diff --git a/pkg/setup/usb.go b/pkg/setup/usb.go new file mode 100644 index 0000000..1e71940 --- /dev/null +++ b/pkg/setup/usb.go @@ -0,0 +1,174 @@ +package setup + +import ( + "fmt" + "github.com/lucaber/deckjoy/pkg/deck" + "github.com/lucaber/deckjoy/pkg/hid" + "github.com/lucaber/deckjoy/pkg/usbgadget" +) + +type AfterEnableHookFunc func() error + +type Deck struct { + gadget *usbgadget.Gadget + conf *usbgadget.Config + afterEnableHooks map[string]AfterEnableHookFunc +} + +func NewDeck() (*Deck, error) { + return &Deck{ + afterEnableHooks: map[string]AfterEnableHookFunc{}, + }, nil +} + +func (d *Deck) SetupModules() error { + return Modprobe("libcomposite") +} + +func (d *Deck) setupGadget() error { + gadget, err := usbgadget.CreateGadget("/sys/kernel/config/", "g.1") + if err != nil { + return err + } + d.gadget = gadget + return nil +} + +func (d *Deck) SetupGadget() error { + err := d.setupGadget() + if err != nil { + return err + } + + err = d.gadget.SetAttributes(usbgadget.GadgetAttributes{ + IDVendor: 0x1d6b, + IDProduct: 0x0104, + BCDDevice: 0x0100, + BCDUSB: 0x0200, + }) + + serial, err := deck.SerialNumber() + if err != nil || serial == "" { + serial = "1" + } + + err = d.gadget.SetStrings(usbgadget.GadgetStrings{ + Manufacturer: "Valve", + Product: "Steam Deck (DeckJoy)", + SerialNumber: serial, + }) + if err != nil { + return err + } + + d.conf, err = d.gadget.CreateConfig("c.1") + if err != nil { + return err + } + + err = d.conf.SetAttributes(usbgadget.ConfigAttributes{ + MaxPower: 1000, + }) + if err != nil { + return err + } + err = d.conf.SetStrings(usbgadget.ConfigStrings{ + Configuration: "Steam Deck (DeckJoy)", + }) + if err != nil { + return err + } + + return nil +} + +func (d *Deck) enable() error { + if d.gadget == nil { + return fmt.Errorf("gadget not setup") + } + + // disable first + _ = d.gadget.Disable() + + if err := d.gadget.Enable(); err != nil { + return err + } + + for _, hook := range d.afterEnableHooks { + if err := hook(); err != nil { + return err + } + } + + return nil +} + +func (d *Deck) SetupJoystick(userPermissions bool) (string, error) { + return d.SetupHidDevice("hid.joystick", hid.JoystickReportDesc, userPermissions) +} + +func (d *Deck) SetupKeyboard(userPermissions bool) (string, error) { + return d.SetupHidDevice("hid.keyboard", hid.KeyboardReportDesc, userPermissions) +} + +func (d *Deck) SetupMouse(userPermissions bool) (string, error) { + return d.SetupHidDevice("hid.mouse", hid.MouseReportDesc, userPermissions) +} + +func (d *Deck) SetupHidDevice(name string, reportDesc []byte, userPermissions bool) (string, error) { + if d.conf == nil { + return "", fmt.Errorf("gadget not setup") + } + + hid, err := d.gadget.CreateFunctionHID(name) + if err != nil { + return "", err + } + + err = hid.SetAttributes(usbgadget.FunctionHIDAttributes{ + Protocol: 1, + Subclass: 1, + ReportLength: 8, + ReportDesc: reportDesc, + }) + if err != nil { + return "", err + } + + err = d.conf.AddFunction(hid.FunctionGeneric) + if err != nil { + return "", err + } + + if userPermissions { + d.afterEnableHooks[name] = func() error { + err = hid.SetDevicePermissions() + if err != nil { + return err + } + return nil + } + } + + err = d.enable() + if err != nil { + return "", fmt.Errorf("enable error %w", err) + } + + path, err := hid.GetDevicePath() + if err != nil { + return "", err + } + + return path, nil +} + +func (d *Deck) Destroy() error { + if d.gadget == nil { + err := d.setupGadget() + if err != nil { + return err + } + } + return d.gadget.Destroy(true) +} diff --git a/pkg/steamworks/steamworks.go b/pkg/steamworks/steamworks.go new file mode 100644 index 0000000..007854c --- /dev/null +++ b/pkg/steamworks/steamworks.go @@ -0,0 +1,130 @@ +package steamworks + +import ( + "fmt" + "github.com/ebitengine/purego" + "os" + "time" +) + +type SteamInput uintptr +type InputHandle uintptr +type ActionSetHandle uintptr + +var SteamAPIInit func() bool +var GetSteamInput func() SteamInput +var SteamInputInit func(SteamInput, bool) bool +var SetInputActionManifestFilePath func(SteamInput, string) bool +var GetActionSetHandle func(SteamInput, string) ActionSetHandle + +var RunCallbacks func() +var RunFrame func(SteamInput, bool) + +var GetConnectedControllers func(SteamInput, *uint64) int +var GetControllerForGamepadIndex func(SteamInput, int) InputHandle +var GetCurrentActionSet func(SteamInput, InputHandle) ActionSetHandle +var ActivateActionSet func(SteamInput, InputHandle, ActionSetHandle) + +var SteamInputInstance SteamInput + +var DefaultActionSet ActionSetHandle +var TouchActionSet ActionSetHandle + +func Init() error { + path := "./libsteam_api.so" + lib, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + return fmt.Errorf("failed to load %s: %w", path, err) + } + + // var RestartAppIfNecessary func(uint322 uint32) + // purego.RegisterLibFunc(&RestartAppIfNecessary, lib, "SteamAPI_RestartAppIfNecessary") + + purego.RegisterLibFunc(&SteamAPIInit, lib, "SteamAPI_Init") + purego.RegisterLibFunc(&GetSteamInput, lib, "SteamAPI_SteamInput_v006") + purego.RegisterLibFunc(&SteamInputInit, lib, "SteamAPI_ISteamInput_Init") + purego.RegisterLibFunc(&SetInputActionManifestFilePath, lib, "SteamAPI_ISteamInput_SetInputActionManifestFilePath") + purego.RegisterLibFunc(&GetActionSetHandle, lib, "SteamAPI_ISteamInput_GetActionSetHandle") + + purego.RegisterLibFunc(&RunCallbacks, lib, "SteamAPI_RunCallbacks") + purego.RegisterLibFunc(&RunFrame, lib, "SteamAPI_ISteamInput_RunFrame") + + purego.RegisterLibFunc(&GetConnectedControllers, lib, "SteamAPI_ISteamInput_GetConnectedControllers") + purego.RegisterLibFunc(&GetControllerForGamepadIndex, lib, "SteamAPI_ISteamInput_GetControllerForGamepadIndex") + purego.RegisterLibFunc(&GetCurrentActionSet, lib, "SteamAPI_ISteamInput_GetCurrentActionSet") + purego.RegisterLibFunc(&ActivateActionSet, lib, "SteamAPI_ISteamInput_ActivateActionSet") + + if !SteamAPIInit() { + return fmt.Errorf("SteamAPI_Init failed") + } + + SteamInputInstance = GetSteamInput() + if SteamInputInstance == 0 { + return fmt.Errorf("failed to get SteamInput") + } + + err = setup() + if err != nil { + SteamInputInstance = 0 + return err + } + + go func() { + for { + time.Sleep(time.Millisecond * 10) + RunCallbacks() + RunFrame(SteamInputInstance, false) + } + }() + + return nil +} + +func setup() error { + if !SteamInputInit(SteamInputInstance, false) { + return fmt.Errorf("SteamInput_Init failed") + } + + cwd, err := os.Getwd() + if err != nil { + return err + } + manifestPath := fmt.Sprintf("%s/game_actions_480.vdf", cwd) + if !SetInputActionManifestFilePath(SteamInputInstance, manifestPath) { + return fmt.Errorf("SetInputActionManifestFilePath(%s) failed", manifestPath) + } + + DefaultActionSet = GetActionSetHandle(SteamInputInstance, "Default") + if DefaultActionSet == 0 { + return fmt.Errorf("failed to get ActionSetHandle Default") + } + TouchActionSet = GetActionSetHandle(SteamInputInstance, "Touch") + if TouchActionSet == 0 { + return fmt.Errorf("failed to get ActionSetHandle Touch") + } + + return nil +} + +func GetController() { + c := make([]uint64, 20, 20) + _ = GetConnectedControllers(SteamInputInstance, &(c[0])) +} + +func ActivateActionSetForDefaultController(actionSet ActionSetHandle) error { + if SteamInputInstance == 0 { + return fmt.Errorf("steamworks not initialized") + } + + input := GetControllerForGamepadIndex(SteamInputInstance, 0) + if input == 0 { + return fmt.Errorf("failed to get default controller") + } + + current := GetCurrentActionSet(SteamInputInstance, input) + if current == actionSet { + return nil + } + ActivateActionSet(SteamInputInstance, input, actionSet) + return nil +} diff --git a/pkg/usbgadget/config.go b/pkg/usbgadget/config.go new file mode 100644 index 0000000..b8e1458 --- /dev/null +++ b/pkg/usbgadget/config.go @@ -0,0 +1,114 @@ +package usbgadget + +import ( + "os" + "path" +) + +type Config struct { + path string +} + +type ConfigAttributes struct { + MaxPower uint16 +} + +type ConfigStrings struct { + Configuration string +} + +func (g *Gadget) CreateConfig(name string) (*Config, error) { + c := g.GetConfig(name) + + if err := mkDir(c.path); err != nil { + return nil, err + } + + return c, nil +} + +func (g *Gadget) GetConfig(name string) *Config { + return &Config{ + path: path.Join(g.path, "configs", name), + } +} + +func (g *Gadget) GetConfigs() ([]*Config, error) { + entries, err := listDir(path.Join(g.path, "configs")) + if err != nil { + return nil, err + } + configs := make([]*Config, len(entries)) + for i, e := range entries { + configs[i] = g.GetConfig(e) + } + + return configs, nil +} + +func (c *Config) SetAttributes(attrs ConfigAttributes) error { + if err := writeHex16(path.Join(c.path, "MaxPower"), attrs.MaxPower); err != nil { + return err + } + return nil +} +func (c *Config) SetStrings(strs ConfigStrings) error { + stringsPath := path.Join(c.path, "strings", "0x409") + _ = mkDir(stringsPath) + + if err := writeString(path.Join(stringsPath, "configuration"), strs.Configuration); err != nil { + return err + } + + return nil +} + +func (c *Config) AddFunction(f *FunctionGeneric) error { + return os.Symlink(f.path, path.Join(c.path, f.name)) +} + +func (c *Config) RemoveFunction(f *FunctionGeneric) error { + return os.Remove(path.Join(c.path, f.name)) +} + +func (c *Config) DeleteStrings() error { + stringsPath := path.Join(c.path, "strings", "0x409") + return os.Remove(stringsPath) +} + +func (c *Config) RemoveAllFunctions() error { + entries, err := os.ReadDir(c.path) + if err != nil { + return err + } + for _, e := range entries { + if e.Type() == os.ModeSymlink { + err := os.Remove(path.Join(c.path, e.Name())) + if err != nil { + return err + } + } + } + return nil +} + +func (c *Config) Destroy(skipErrors bool) error { + if err := c.RemoveAllFunctions(); err != nil { + if !skipErrors { + return err + } + } + + if err := c.DeleteStrings(); err != nil { + if !skipErrors { + return err + } + } + + if err := os.Remove(c.path); err != nil { + if !skipErrors { + return err + } + } + return nil +} diff --git a/pkg/usbgadget/function.go b/pkg/usbgadget/function.go new file mode 100644 index 0000000..a3bd009 --- /dev/null +++ b/pkg/usbgadget/function.go @@ -0,0 +1,40 @@ +package usbgadget + +import ( + "os" + "path" +) + +type FunctionGeneric struct { + path string + name string +} + +func (g *Gadget) GetFunction(name string) *FunctionGeneric { + return &FunctionGeneric{ + name: name, + path: path.Join(g.path, "functions", name), + } +} + +func (g *Gadget) GetFunctions() ([]*FunctionGeneric, error) { + entries, err := listDir(path.Join(g.path, "functions")) + if err != nil { + return nil, err + } + functions := make([]*FunctionGeneric, len(entries)) + for i, e := range entries { + functions[i] = g.GetFunction(e) + } + + return functions, nil +} + +func (f *FunctionGeneric) Destroy(skipErrors bool) error { + if err := os.Remove(f.path); err != nil { + if !skipErrors { + return err + } + } + return nil +} diff --git a/pkg/usbgadget/function_ecm.go b/pkg/usbgadget/function_ecm.go new file mode 100644 index 0000000..d48bb8e --- /dev/null +++ b/pkg/usbgadget/function_ecm.go @@ -0,0 +1,35 @@ +package usbgadget + +import "path" + +type FunctionECM struct { + *FunctionGeneric +} + +type FunctionECMAttributes struct { + HostAddr string + DevAddr string +} + +func (g *Gadget) CreateFunctionECM(name string) (*FunctionECM, error) { + gf := g.GetFunction(name) + f := &FunctionECM{ + gf, + } + + if err := mkDir(f.path); err != nil { + return nil, err + } + + return f, nil +} + +func (f *FunctionECM) SetAttributes(attrs FunctionECMAttributes) error { + if err := writeString(path.Join(f.path, "host_addr"), attrs.HostAddr); err != nil { + return err + } + if err := writeString(path.Join(f.path, "dev_addr"), attrs.DevAddr); err != nil { + return err + } + return nil +} diff --git a/pkg/usbgadget/function_hid.go b/pkg/usbgadget/function_hid.go new file mode 100644 index 0000000..3d6c729 --- /dev/null +++ b/pkg/usbgadget/function_hid.go @@ -0,0 +1,83 @@ +package usbgadget + +import ( + "errors" + "os" + "path" + "strings" +) + +type FunctionHID struct { + *FunctionGeneric +} + +type FunctionHIDAttributes struct { + Protocol uint8 + Subclass uint8 + ReportLength uint8 + ReportDesc []byte +} + +func (g *Gadget) CreateFunctionHID(name string) (*FunctionHID, error) { + gf := g.GetFunction(name) + f := &FunctionHID{ + gf, + } + + if err := mkDir(f.path); err != nil { + return nil, err + } + + return f, nil +} + +func (f *FunctionHID) SetAttributes(attrs FunctionHIDAttributes) error { + if err := writeHex8(path.Join(f.path, "protocol"), attrs.Protocol); err != nil { + return err + } + if err := writeHex8(path.Join(f.path, "subclass"), attrs.Protocol); err != nil { + return err + } + if err := writeHex8(path.Join(f.path, "report_length"), attrs.ReportLength); err != nil { + return err + } + if err := writeBytes(path.Join(f.path, "report_desc"), attrs.ReportDesc); err != nil { + return err + } + return nil +} + +func (f *FunctionHID) GetDevicePath() (string, error) { + devContent, err := os.ReadFile(path.Join(f.path, "dev")) + if err != nil { + return "", err + } + deviceNumbers := strings.Split(string(devContent), "\n")[0] + + uevent, err := os.ReadFile(path.Join("/sys/dev/char", deviceNumbers, "uevent")) + if err != nil { + return "", err + } + + rows := strings.Split(string(uevent), "\n") + for _, row := range rows { + entry := strings.SplitN(row, "=", 2) + if entry[0] == "DEVNAME" { + return path.Join("/dev", entry[1]), nil + } + } + + return "", errors.New("could not find device path") +} + +func (f *FunctionHID) SetDevicePermissions() error { + p, err := f.GetDevicePath() + if err != nil { + return err + } + err = os.Chmod(p, os.ModePerm) + if err != nil { + return err + } + return nil +} diff --git a/pkg/usbgadget/gadget.go b/pkg/usbgadget/gadget.go new file mode 100644 index 0000000..87d8989 --- /dev/null +++ b/pkg/usbgadget/gadget.go @@ -0,0 +1,141 @@ +package usbgadget + +import ( + "errors" + "os" + "path" +) + +type Gadget struct { + path string +} + +type GadgetAttributes struct { + IDVendor uint16 + IDProduct uint16 + BCDDevice uint16 + BCDUSB uint16 +} +type GadgetStrings struct { + SerialNumber string + Manufacturer string + Product string +} + +func CreateGadget(configfs string, name string) (*Gadget, error) { + g := &Gadget{ + path: path.Join(configfs, "usb_gadget", name), + } + + if err := mkDir(g.path); err != nil { + return nil, err + } + + return g, nil +} + +func (g *Gadget) SetAttributes(attrs GadgetAttributes) error { + if err := writeHex16(path.Join(g.path, "idVendor"), attrs.IDVendor); err != nil { + return err + } + if err := writeHex16(path.Join(g.path, "idProduct"), attrs.IDProduct); err != nil { + return err + } + if err := writeHex16(path.Join(g.path, "bcdDevice"), attrs.BCDDevice); err != nil { + return err + } + if err := writeHex16(path.Join(g.path, "bcdUSB"), attrs.BCDUSB); err != nil { + return err + } + + return nil +} + +func (g *Gadget) SetStrings(strs GadgetStrings) error { + stringsPath := path.Join(g.path, "strings", "0x409") + _ = mkDir(stringsPath) + + if err := writeString(path.Join(stringsPath, "serialnumber"), strs.SerialNumber); err != nil { + return err + } + if err := writeString(path.Join(stringsPath, "manufacturer"), strs.Manufacturer); err != nil { + return err + } + if err := writeString(path.Join(stringsPath, "product"), strs.Product); err != nil { + return err + } + + return nil +} + +func (g *Gadget) DeleteStrings() error { + stringsPath := path.Join(g.path, "strings", "0x409") + return os.Remove(stringsPath) +} + +func (g *Gadget) Enable() error { + entries, err := ListDevices() + if err != nil { + return err + } + + if len(entries) == 0 { + return errors.New("no udc device found") + } + + dev := entries[0] + return g.EnableUDC(dev) +} + +func (g *Gadget) EnableUDC(udc string) error { + return writeString(path.Join(g.path, "UDC"), udc) +} + +func (g *Gadget) Disable() error { + // writing "" isnt as reliable + //return writeString(path.Join(g.path, "UDC"), "") + return writeString(path.Join(g.path, "UDC"), "\n") +} + +func (g *Gadget) Destroy(skipErrors bool) error { + if err := g.Disable(); err != nil { + if !skipErrors { + return err + } + } + + configs, err := g.GetConfigs() + if err == nil { + for _, config := range configs { + err = config.Destroy(skipErrors) + if err != nil { + return err + } + } + } else { + if !skipErrors { + return err + } + } + + functions, err := g.GetFunctions() + if err == nil { + for _, function := range functions { + err = function.Destroy(skipErrors) + if err != nil { + return err + } + } + } else { + if !skipErrors { + return err + } + } + + if err := g.DeleteStrings(); err != nil { + if !skipErrors { + return err + } + } + return os.Remove(g.path) +} diff --git a/pkg/usbgadget/info.go b/pkg/usbgadget/info.go new file mode 100644 index 0000000..a72f706 --- /dev/null +++ b/pkg/usbgadget/info.go @@ -0,0 +1,14 @@ +package usbgadget + +func ListDevices() ([]string, error) { + return listDir("/sys/class/udc") +} + +func HasDevice() (bool, error) { + entries, err := ListDevices() + if err != nil { + return false, err + } + + return len(entries) > 0, nil +} diff --git a/pkg/usbgadget/utils.go b/pkg/usbgadget/utils.go new file mode 100644 index 0000000..c25eb6b --- /dev/null +++ b/pkg/usbgadget/utils.go @@ -0,0 +1,55 @@ +package usbgadget + +import ( + "errors" + "os" + "strconv" +) + +func mkDir(path string) error { + return os.MkdirAll(path, os.ModeDir) +} + +func writeString(path string, s string) error { + return os.WriteFile(path, []byte(s), os.ModePerm) +} + +func writeHex16(path string, i uint16) error { + hexStr := strconv.FormatInt(int64(i), 16) + if len(hexStr) > 4 { + return errors.New("WTF") + } + for len(hexStr) < 4 { + hexStr = "0" + hexStr + } + data := "0x" + hexStr + return os.WriteFile(path, []byte(data), os.ModePerm) +} + +func writeHex8(path string, i uint8) error { + hexStr := strconv.FormatInt(int64(i), 16) + if len(hexStr) > 2 { + return errors.New("WTF") + } + for len(hexStr) < 2 { + hexStr = "0" + hexStr + } + data := "0x" + hexStr + return os.WriteFile(path, []byte(data), os.ModePerm) +} + +func writeBytes(path string, i []byte) error { + return os.WriteFile(path, i, os.ModePerm) +} + +func listDir(path string) ([]string, error) { + entries, err := os.ReadDir(path) + if err != nil { + return nil, err + } + names := make([]string, len(entries)) + for i, e := range entries { + names[i] = e.Name() + } + return names, nil +} diff --git a/pkg/util/file.go b/pkg/util/file.go new file mode 100644 index 0000000..c676bf3 --- /dev/null +++ b/pkg/util/file.go @@ -0,0 +1,28 @@ +package util + +import ( + "io" + "os" +) + +func CopyFile(in, out string) error { + i, err := os.Open(in) + if err != nil { + return err + } + defer i.Close() + o, err := os.Create(out) + if err != nil { + return err + } + defer o.Close() + _, err = io.Copy(o, i) + if err != nil { + return err + } + err = o.Sync() + if err != nil { + return err + } + return nil +} diff --git a/pkg/util/root.go b/pkg/util/root.go new file mode 100644 index 0000000..f8d0f04 --- /dev/null +++ b/pkg/util/root.go @@ -0,0 +1,62 @@ +package util + +import ( + "context" + "fmt" + log "github.com/sirupsen/logrus" + "os/exec" + "time" +) + +func ExecAsRoot(ctx context.Context, args ...string) error { + polkitAgent := exec.CommandContext(ctx, "/usr/lib/polkit-kde-authentication-agent-1") + polkitAgent.Stdout = NewLogWriter(log.DebugLevel) + polkitAgent.Stderr = NewLogWriter(log.ErrorLevel) + _ = polkitAgent.Start() + defer func() { + _ = polkitAgent.Process.Kill() + _ = polkitAgent.Wait() + }() + + time.Sleep(100 * time.Millisecond) + + err := Exec(ctx, "pkexec", args...) + if err != nil { + return err + } + + return nil +} + +func Exec(ctx context.Context, cmd string, args ...string) error { + command := exec.CommandContext(ctx, cmd, args...) + command.Cancel = func() error { + err := command.Process.Kill() + log.Infof("failed to kill process %v", err) + return nil + } + command.Stdout = NewLogWriter(log.InfoLevel) + command.Stderr = NewLogWriter(log.ErrorLevel) + err := command.Run() + if err != nil { + return err + } + return nil +} + +type LogWriter struct { + logger *log.Logger + level log.Level +} + +func NewLogWriter(level log.Level) *LogWriter { + lw := &LogWriter{ + level: level, + } + return lw +} + +func (lw LogWriter) Write(b []byte) (n int, err error) { + log.StandardLogger().Log(lw.level, fmt.Sprintf("process output: %s", string(b))) + return len(b), nil +}