From db2bb39981d67ed3b8721d0e4748245da4a2899f Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Sun, 28 Feb 2021 19:20:21 +0800 Subject: [PATCH] Add recent updates from ch3 --- .dockerignore | 1 + .gitignore | 1 + Dockerfile | 40 +++++++ Makefile | 8 ++ README.md | 2 +- os/Makefile | 7 +- os/build.rs | 2 +- rust-toolchain | 2 +- tools/kflash.py | 280 ++++++++++++++++++++++++++++++++++-------------- 9 files changed, 255 insertions(+), 88 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 Makefile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..df3359dd --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +*/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3adafb58..4b5497cf 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ os/src/link_app.S user/target/* user/.idea/* user/Cargo.lock +tools/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ac784bc2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +FROM ubuntu:18.04 +LABEL maintainer="dinghao188" \ + version="1.1" \ + description="ubuntu 18.04 with tools for tsinghua's rCore-Tutorial-V3" + +#install some deps +RUN set -x \ + && apt-get update \ + && apt-get install -y curl wget autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev \ + gawk build-essential bison flex texinfo gperf libtool patchutils bc xz-utils \ + zlib1g-dev libexpat-dev pkg-config libglib2.0-dev libpixman-1-dev git tmux python3 + +#install rust and qemu +RUN set -x; \ + RUSTUP='/root/rustup.sh' \ + && cd $HOME \ + #install rust + && curl https://sh.rustup.rs -sSf > $RUSTUP && chmod +x $RUSTUP \ + && $RUSTUP -y --default-toolchain nightly --profile minimal \ + + #compile qemu + && wget https://ftp.osuosl.org/pub/blfs/conglomeration/qemu/qemu-5.0.0.tar.xz \ + && tar xvJf qemu-5.0.0.tar.xz \ + && cd qemu-5.0.0 \ + && ./configure --target-list=riscv64-softmmu,riscv64-linux-user \ + && make -j$(nproc) install \ + && cd $HOME && rm -rf qemu-5.0.0 qemu-5.0.0.tar.xz + +#for chinese network +RUN set -x; \ + APT_CONF='/etc/apt/sources.list'; \ + CARGO_CONF='/root/.cargo/config'; \ + BASHRC='/root/.bashrc' \ + && echo 'export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static' >> $BASHRC \ + && echo 'export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup' >> $BASHRC \ + && touch $CARGO_CONF \ + && echo '[source.crates-io]' > $CARGO_CONF \ + && echo "replace-with = 'ustc'" >> $CARGO_CONF \ + && echo '[source.ustc]' >> $CARGO_CONF \ + && echo 'registry = "git://mirrors.ustc.edu.cn/crates.io-index"' >> $CARGO_CONF \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..2e339762 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +DOCKER_NAME ?= dinghao188/rcore-tutorial +.PHONY: docker build_docker + +docker: + docker run --rm -it --mount type=bind,source=$(shell pwd),destination=/mnt ${DOCKER_NAME} + +build_docker: + docker build -t ${DOCKER_NAME} . diff --git a/README.md b/README.md index 50834482..dd356b5f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # rCore-Tutorial-v3 -rCore-Tutorial version 3. +rCore-Tutorial version 3. \ No newline at end of file diff --git a/os/Makefile b/os/Makefile index 19321c38..3017ed14 100644 --- a/os/Makefile +++ b/os/Makefile @@ -58,7 +58,10 @@ disasm-vim: kernel @vim $(DISASM_TMP) @rm $(DISASM_TMP) -run: run-inner +run: tools run-inner + +tools: + (which $(K210-BURNER)) || (cd .. && git clone https://github.com/sipeed/kflash.py.git && mv kflash.py tools) run-inner: build ifeq ($(BOARD),qemu) @@ -82,4 +85,4 @@ debug: build tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \ tmux -2 attach-session -d -.PHONY: build env kernel clean disasm disasm-vim run-inner +.PHONY: build env kernel clean disasm disasm-vim run-inner tools diff --git a/os/build.rs b/os/build.rs index 88a964b2..5e032311 100644 --- a/os/build.rs +++ b/os/build.rs @@ -23,7 +23,7 @@ fn insert_app_data() -> Result<()> { apps.sort(); writeln!(f, r#" - .align 4 + .align 3 .section .data .global _num_app _num_app: diff --git a/rust-toolchain b/rust-toolchain index bf867e0a..a08f00d1 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly +nightly-2021-01-30 diff --git a/tools/kflash.py b/tools/kflash.py index d4cb1da7..b1fe4fca 100755 --- a/tools/kflash.py +++ b/tools/kflash.py @@ -24,7 +24,7 @@ class KFlash: def __init__(self, print_callback = None): self.killProcess = False self.loader = None - self.print_callback = print_callback + KFlash.print_callback = print_callback @staticmethod def log(*args, **kwargs): @@ -33,7 +33,7 @@ class KFlash: else: print(*args, **kwargs) - def process(self, terminal=True, dev="", baudrate=1500000, board=None, sram = False, file="", callback=None, noansi=False, terminal_auto_size=False, terminal_size=(50, 1), slow_mode = False): + def process(self, terminal=True, dev="", baudrate=1500000, board=None, sram = False, file="", callback=None, noansi=False, terminal_auto_size=False, terminal_size=(50, 1), slow_mode = False, addr=None, length=None): self.killProcess = False BASH_TIPS = dict(NORMAL='\033[0m',BOLD='\033[1m',DIM='\033[2m',UNDERLINE='\033[4m', DEFAULT='\033[0m', RED='\033[31m', YELLOW='\033[33m', GREEN='\033[32m', @@ -295,7 +295,7 @@ class KFlash: return b''.join(map(lambda x: x.to_bytes(1, 'little'), plaintext)) - ISP_PROG = '789cbcbc7d5c1357d6007c6792c924080a0e1890d82201a2acebc38a4aab960d428862eb636b85ea56171d10b5dacafa416dcb4a48263122453a60c0604bb182b25bd71535ada8011569edf787623fb4688080a2420522cac77befcc04d0769f7dff787faffec2ccbdf7dc73cf3df79c73cfb91f33f1e0dd331f5ec300061efdb7695144c4a64541f0a746bfe9161c00363b7bcca6dd2f44e8d4588c2e069bab9b8bc5ea62b1385d1ca6d169b0785d3ca6d569b179ba79d87cdd7c2c4197802dd02dc09ed53d8b3da77b0e5ba85b88fdafee7f8b576d2ac52336794780c500747c077f8b31f884bfc5387cc2df62117cc2df62317cc2df62023ee16fb1043ee16f31099ff0b7580a9ff0b758069ff0b7d8033ee16ff128f884bfc59ef0097f8bbde013fe168f864ff85b3c063ee1cfe283015617532cff9b0c50f85117f537bc6f53a8ba93c2fc40343636ffb5a08db12b7dc635de0b0e9a4eff8ce346bc6d2c5d84e32b7dc6375a7058371bbfbd49a9ee1c2ddafb0e4e4fc4f4477140efc041f6aa89d86b20287013088a40ef58baa4c9a37574fbd88e715de3ef3fd157d658de54d97ab8fd68c789ae93f7cff4cdbd1edf9cd0b6f0f60b9d89ddcb7a57f4dfb8ded27cb3edceed5f3b7bba1ff40ef4ab82306f55888fb76ad2446fd5943f79ab8262c6aa429e1fab9ab472ac6a4afa585590ce571592efab9af4beaf6acabf7d5541b5e35421df8c534dba3e4e35a5731cac1f00eb07c0fa01b07e00ac1f08eb07c2fa81b07e20ac3f01d69f00eb4f80f527c0fa4fc2fa4fc2fa4fc2fa4f6e0a0d9a7e2fd47bba87d117a79710e06ed0918501a57723364edae4ed37bd2c79eeca1b2bf1bf95fd6deea61b9b2429e529f1a92da992ade55be3335a323cd654ae49587b73adc71b956f24bc79f3cdd1eb0faf5fb8e1ce86d17f3ffcf785dbef6c2761df4c413a6f7222e6639aa8f32183b1b1a660dd5852895126a58e2243305f5388ce970cc5fc4ca13a3f320c1b670ad38d235598dca4d2c9c94998bf6992ce9f9c8c059826eb02c8706cbc295c379efc031668fa832e909c82294c53740af28fd804d31f7513c8a9d813a6a9ba27c8ffc19e34fd8fee490ac382d038010047a9d1a3696cebb8f6f11d4f7405df0febab6c3cdc74b4f544fbc98e335d67ef5fe84bb8beb0f985b6c4dbcb3a5774afea5ddd7ff3fa9de65fdb7a6e3fe81ce8be171c01f6f4de0bf69ebea71f2f93947b548e3e3cf6e8b813e34f3e71a6acacbcbcb2f2f0e1a3474f9c3879f2cc99b9fbe32b12feb1f05f2f5425da9655afb0dfd8df5271f31f77fef56b558fed41f5809d97adb99fbb650bc995f24830b02c02c099551a22945f74978bfe8d037ce5977fdbb47b1ab803ff8e9d6e0b06c09a8def409a969d1cbc72eeaa1babf04d659be66ebeb159925a9e1abfba65b524a33c23fef596d73dd656ae4d5877739dc79b956f26bc75f3add11b0e6f58f8ea9d57476f3fbc7d6cd0d1a01726fe3a71ecb4a3d35e88fc35725cc88990c4d09ed071334fcc4c8cea891a3fe9e4a465931f4c1e3febe4ac65b31fcc7e62ca99292bfe38f0c727a2cf44aff8f3c09fdddc8df191346d52a93b192dad7001426351a39e349aee65c4456c028bbcbbed166f94937ca42c19d3c460d95972020314c1b45848d85713d9429b2b805c04b22851ec715a2402b8de0da369b2c027cbd439ccb9b98c595b55d78deb3ee561f547ddb0088fbede749db98b68b100d45ad0030b09b25893ea88f933a1bd5fb812f82b4b66b472584789bbae217a1006049bf661eeb9181fbec5d026be56e8cf0246ae5e0c06db066940df2397624034efa51f27686743547c4fd27eb2c05cfd276b7ef29a5742cff6f7c722d3480c53159d0b34f50c5e3947688bea2b0be909c771f8f6d9e78b75a1812602d05d2740a04902749a4006a6e8669cf523715d7ef1bb53f7740e280f9240f90109304d76164188b4dd831f7d9769171d242591c409c0104a4060d76d3c35af7fc353c3cc13e8f996cf5ff32da4a6483a448df2033906e918775cbc6b314502bc9ab0a9a79a1ea363fd301d8476ea9ea2cf2802e0cccdfcf8a224bf1cc7130dbf4bd9161ba24aa421206507bf4bb5ef5a8c30ce961e57d39baf8040528ab0ef80d8d7b4021efbd43dc5ef6eb0454a61ad7952d49f5292ecb5633ec5da4900ce39f18c46d2ae1477027e541b3b6f40192256f263ce9ab4e74be840d2cd5bdf9863dfbd087f459f7dbf5817865aa6375f86adc23ec5073230f56a3360c7c156df0978f74461fa233de038ca5c028c0471f453fbb6f4b0f6fea44ac3164d7087a44be9db09e44d0050e20f8ef2f29d75a5b969825bd6bf7f46780b6ad069428577f5d57f6b266a2c119c5ea7d5a17ceebd7175be66b2f01eb4ba50f347e13d39b558f327377cca3ecd0c373cad48e6a475b6eb582d27b7fb25aea3eff3124cb88e5de2dfa4aea3823e5595d032c8bd2b9f29cb49dcdd4728e148223c39d859aec3ba783ea7622e166fd670b991aec33c97b37e2adb886fbe9182693490d39385fe645d982fbcd57ca24394f27dff52b19eab3dc75589dadfbf833c7888a7887455fea02db4613ec1ad85da704304f060187e3c817b3cc1e77aad0ea03155822e3037058d2ce643119d40d22ed0d227d2744208c28ccab6a59bb54acf4e604d22b0fdf0c926318075b9c60413cb73255dc11d539ba6352a27bcefc6fe73596af6ea49d2a9c67c8d203f5f107142d9032cd4318a19403de5fb89f9b064563f92350ebbf831eced4a3f37deaccbbc6cbab1667de2964a840348464a6ccd776e3916d2756ed8e5e9658d0bbb5ee8486c5fd6baa2c9d814d69770bfd23c75e73403eb01bca95100b78c82e3999353206b2f4f96a4dcd83c775bd936fc8d1bebe6be52f60aa6590971b0aede31b34d0fd4274df4a6265066b21a4830a124cc4c6fb6024a068da36d14f89659c8044a6420dbc2ca65b88ead3684c454cb9ec6a2f75eef5b6e9b90fec27d6aaf044fec5ad6b1aa955a7613937f0ee5dcf39fff237b257f5d59ebc5c6857d2bda8dede54de186a9e6a3e61339abf6068e0a0cb6eedc39917e4f065a92e3a15720597b63dbdc37cadec0dfbcf1cadcf5982614d1062d8bd52083f62d0a63c5002fd4d27732447a9507b861c936e56a794e8b054e1b00253770dcfe214fe9d709ca8d0970d426b9397e88b58821eda125d1c5d5e662f552db87ffa57e7f9252c26329d46cb0b3e41151f479413f740534924f91d69965d6565b9c40de0380287e4f26b29ef5e0873c3e2df93b21a4832fe854256795958963c28c1719374dea8f8edb20de51d1e7adb0e5e80b1617d227d59b103b7c1369c93747623f91f128f6b0d7ddd8116e96f40e5656f68cd6208c124829872b6deb6ea8c9e85d5fafda6ae986cfba199bf6c557c306aaf79c0097f22c1097fec2ea4db3e50d029d8ab3caf0c431efa31e8f7e943253fa30366d3a8f6dc9abbfc5667c95c7c6e35a3de64f36269e9ff5f65f7d3a3da1234a2605d083ea8a1487629506229e85da2ad32c6c97f4ed47f3f0d30620580909f408bcbbc5487f59e253acbc90e75bf2ff5a4938037c46e28a67034d72902dd7b1fe9f7f6951665500ff676bd36a49badb092047462bbebeb171eee6b2cdf8b61b6b31cd44a4ff490e8b73c037c5468a413e4999e240b985dedb027c5b154d3638bb9c2950fe530cda6c8a56ffa63a3bd2466f2f497b702bb2dfe106c106cc310fc9558d3a9811de52ad0401dc29f59f052f62d68d64de2a553230fe899f182fd3c863112f684c5fef0744f50428237fc8b39a9cbfb27b4851ed3b7e5a7a6dc313706001453afb584b1838692989dd961edc11d69ed02ae95ad874d272d430d5106ece3689ea0ae1583080d0220ba3af3f00ce92e8791cdc203f85fca737bb406ed29252914a0664398483ea768d11f4610f169a9f34bf590f4b58a70a28837a812ee9c3838a18734e6f030f536a13f8fd0f2c34b3f70b1b2afbe2277dfd1238c61a48791da4fc529efe422aa08c0c382b41ef5bc00dc9d5b7f5f597802e0cf54b54df0ce672cf6e60633ebacd32c4b455e4f2bc89aafea480bb6cbd6b907ebb073727edcfea02fc9c31776dd95a644d451a09389437511378450e989c6ab2437d3bafaa9ec43267e8545bba09496654ad4daf2d00a2f906484795538589e67f00e6caaad27a61de31b04a16b9b6176093a1cd12d5b2c55fe19bcb36f2d8e57590b7c48156cd4a5e2a453762b6446e39e4b6f91f30f38571b6220b3f6350affd1eeadb79288362ac8cfc264f14df041298b912f4d605564996bf8d8509a35eb1e5b408f65d0f79130c7ba987bd3fcb3dbbe188208df0d1d20627609230fb0e772bd7561ecfce8af16109b5d847037d457efe6e2d4b668945623fe8efca23398fd1b92d1dcd50480a04d9eae761d577047fe113997b7ebddf99ca4b1be477903b57fd23df73e247615c9f8e9248012531e23a8dbc1b9534cf164a9e146cd16cbe46f315b94b8ae6f75982accfd069dc584b7f853601ea3d6b943c2597406849d859f350a9fa571e834690a9a069f22ed8a6386e062b0eea97f7c2329979bae71aa659b1e8f62f8a45fd3f21dd42165929ce0716d4ea07e474812aafeb763dd49704260220dbbd5f8cb9b5f0334e2311f471324290708fe7a0feee27df07c8be1050efa047c0d1947c1e7a0350c2a03feac94b80a31ff9fdd093f5a4bd9b718a38e225b4d84711355ec35e3bfd90228f7839fc9b0784f20714f1b5579d8dd74e4957a5018bb342cb423b6d82342349ae321980a88e04539b08b1405b6f59ea75489da43dbc696aa390e7ca4e0d4e15f03e44140ad2d983e82a89a1039b81cec9684b6a271e549612a037299a817eb396363981399e7ebb01ce58fd362b0c3a108dd60632c6e1dd3c60d6d6424b5ee3458feb0645b57046f56cb3b192afbdfa931c3e97fa50ce11af54fb33b1e1263afd1cce66b840ade3f47576ab64f0999d27f7783a3c9d31a46343c380cf0dd80fc939f54e095aebd9b3272dab1cfa6736e849da8012fef613f0097ffbc5f0097ffb45f0097ffb71f884bffd187cc2df7e009ff00767f7eeee31ec1e065cca65f71018f717bf94bbd4c6ee5101be0d63b6332becc19eac5af984bce83d8ef473f7f7d996a697b7571aa63265d0533376093235da52cfcd4f47cf3432da334d0affb2d4e876b336faeb53f3c2fb0862eafdf0aeac35f9ce30c13667f93f2b1d8a3263f52af128264777570f3d01d64506153b2942ec21c8b6875e2ec6288b4a443f4702591af40cfcbb00aab12d2353de9ba79cd4e511c64448cbdc734040ff8b3ed70fc5fd5f38c1981734cf4a85f739e51b259bc3989694541491f03af76b25b38316a2c4ce4a13438aa095dc3018c954a833fd90a5086e94405f4ae97f1c2803ea80328200cae9f03789f050ce81cf2002578610f8c4789d06c6ac1e8265ba619173de7a97a28f7f4b73cd6914defae634f16fa641707f4e2ba2154fe1a49e9ba9627b2c7e0045b4be3c55b1cff0d682e914caef0be90e3eed007c9ae870d717d27785f201771f84f1e89f334ff799d022af67fd7c6ce1bc3fa75d9a023dbd90ae1758d2b058f954d70bc86b5646885f10eade95ae21e20f08be71e91de97a59fca76e4ff936a39df349aa4db14ee7d46991b6e81b082c20bdecfec58e6f5b8d7de55d5fb65f6efab17145dfaafbabbbd675bcdafe7eceb49d6866bd545fb2a1ae1e8d9efe690f40b954801e4b7ac3114da0b3487f4ca35b802da0c1a849b2dbcb8c87342b18617d6207ed45841144b5c79698c45df4a6bf60cb76499315eb7d17d15d15617171c74be8af0daa6b4c9851807f47915cc62430e51645a3901346fb13ded53bf7aa090d5dec0150295f521392d9a68ff2c0caad91a3acea2a8345ad7c6a2f38de77a8741806b4f627fde9d21f0fea4fca80fe9819d4cafaaf941be94d56f0a3714eb2e7ebbab0446302936da113a15c435c957bcdbb9054299faa04db7a75da968df19bcb374bb6b5ac8d5f57be4ef2ca8d37e7be55f6169e796343342383f219350a64e9127471d4825135cb6da815d169d44a5c5cb9c921f31a10417e4993d9ad64109d2df1a637ec93c2f7799f5e142ff25c5f2d2ec1bd72ad5e041cd509e0a6a5fa7c8188eeee024468b5f800466590eae5e7ca514ff839f809bfe67f6b914fc26990931423bdeb4ffad88e686017dc1ac49ea3163c1cf47ad6fcec7c9be7fa578de1c2aa4d4d3aa2a22c8dca70d9a83452aaf46f028a76f1d7772cd58663318ac6b8f3374d370df4a67e70d3244d8e365e66a2859a59db5e8a2b4b9bf0de4be7fd934fc211e1b8bd37d26bdf10b7757d9da7c38c6e1a83322b4dcb3b3dd7bbb99f1ccc6bbbcb856835db14ebe9e60a00c7fecdab38bd691fb8690883ad0954feede5b8d9268b5a91fcd27934facf3f1c1ec520c9d26f26db6e9ae87bffc07e3455d737e04fc1b14323576d6cc0714ba5c1115bdad37c5c5f0df95f6d0698a65656586df50804e955f4829f011ae915dc3843c805a53f669e5e65bce6f64a5f7939ee86a1da648a79f9bc22f9066ab91a4591617dc35164c2fd855de13b0fef64c5c40b581cb1a0ff2fe6f660e6b7f1238a1e451f430d396400fd4fd3dfcb8021f907bb3e4986d16f3e047af9648c36656017991798ecbdf3776121709e1f75dd3e21ddd85ed65ade84a2c217eea3987145fbaad6d54dac690b9cb71a71fa0d17ce76918336b49e86bcc32e0bfe6fada216d3fc5e9c58b69e8b13616ce8b873ac2fdc2c8ce44a96dcf8a4a50d5a10f3a7bfd28d1631db301de85428560894cbc16872a105a4b11272901ef73d00af9955f468935891e2bfde774da7b5cc5274c7f756511a95260745ce95a55443018c253a44d867f95abad929651b5280d564123d868d20073335b4ec18f04f49b7464a2c6afa853a8ffe2846253a26c32dcdc87e12b768fc98b77f4a7fbc00f1f30529ebf4058aa62a490180811e501e6db8c93d4f36b4b269e3c0ae3bbb6e96b4045bbc1d6ea9ab79115a67ecd2e96f102dc04ada8803707e5713b413463c0de721654ec93e1803aafdf89c2e989341e6db59897a094b36928ee6962eb62104b39aeac967ecbeebe97912a0585370b7e46615d90426db661a66d82d2d9082d3271ab9e7f1138d0826d8a248639d72a070ee83b83a9e147d6406f48d96715e7a4c1598a852132a261e71a36acf058e23552d124c8eea3f7df9475f9a82bcf66ef76d058eac3b810d103a36927200910de2589526a9da9a068a5b3aad12117acb57355fcb5c38ccdd18f942cbac1dd18c68fa0e8c0e100710a1ecd6192046dafc80ed8902f43e8902e161b5f7067bf3e4887711aeef518e4444a7c0992440a4809a82898ee500f68df383b4732f8834ed55f76be92f9ce1fc8c4b9a503d8a24dfcca27de9fea4c85d7fc1c20a66eda4f7f5f85362315e3664979233fb93c273e831dd41f46606a3adb3275afaa480ddd5be5354e10926bec779a0a5ce9df4b8f57256feac887e871ca7b7c9b0c4022ac51764398576b6eb21b43f4d6f75018ee23fbbdee24b9c993077a34bc1e53ee57a6b4925956801f9f1f4f50b6236310d588d4651a0711c081c370e8c962cdc43119241ef55b47f9d84d05a2f87a9abc635008ef3a8fe9c86b3d276ef56d006eea41fff269e6ebe00ac9213442df4ff90bc5c006c623dc4d822c944397e7c8e0be66c25d3ed1401e54502e5656ddd7d365185598d17c8a536ef75b4a40ef8aeeedceb78a56140faaac6e69be2bdbe736fd58e3d6afa2f4f88786956ac89240b40d99edd7715bc34db9ca7b8a7dd799275fa03cfbb9eb7144ee59171d81fabe8cfa410b2643592ad9216c51a54c7a77a9a21e0748cdcf795fe28c7db2d8d5ea7f355bc6c345f6baeaa345db5136999e4ac82fe8ccc5d85c7f5333de0cc40efe9f1e0da389ef657eaad8740a7a5259ea0565b056d5db495488ddc5901961564fa55657c00223d1ea823dfa804df0e8d6be35f2186806e18bdd38427f4543c07830bb104344a91446a4cb5498b85ef953be1d806bbd653ebc6f3ba7a24ed5fcae90b308771b623534bbf9a2462b4cae9d0f2c1f12e49ebb44642fb4e375b0094b44f9ce221fdf59f70a68acc89f9f83d0e5fb86bb5d9aeb7e500cab07320a688c9a9cd531e7b080ec45799724081b3e09623c3f433cb8cc286e6f8535baaa145c104bb01f9cc69e9c99e0f746790471dbe87f6ade3d6a3f55a0238726d0304d16fabaa3f00f6e54492753082d8a7a5efd4834a13bdb1d9437f81000cc910fbf31a061c379d03d0966edd02cbf22f8e2c617b3240bf8ddd6a1aa48c923ebaa35ea2af2280fe2803f4760213d9194c7f86c04567185c544588f41f31229661c4ca231940f41133a83c9604941fb930e52927a63cd220521eab17293f4ac395a7e422e8718a95a7c8c12db6dd2bdf27df2794a7ea071cb7202d4dce9eec2cfade017195b10ed0db2fe1aca66750a7ed97d36c378c1d224d3d40348f849a5c0fc22d4a4062576d91a66ea08f87796437a45c091a8043de037ba4d336dbe5cd9cff399f8fa4639f13d2f3f8b463c984f4b25663fb54c3a3f3d25126a03d3ea525f9d159275488cbceb1953ba735b9e3c0b254fd4cc320b4b0386bde82b1a3c48354ef8541fa419408cd64d443d9203f9ba589a05f53fa006009683e7b81791c378c4a47c1d9ecd5ea3e3a69ed0cde2a988cdeaf5499fe8015a5d1d21e406d710e46174a5309f3c4dbe9d62a591ba0bd7bc414310aa72f1c12ff68ad32af8de1e5338361db0280f2d86ce8c51a061399e09d6c6f00a86a5b8b29e74c1f14e4f1ee013b7d712d56f45ac92b457715b7ae196694a17d7a66279d2f09d64f170f5619ac6ad14903a0deb83898b0973692fe55322d462b7a3c5812b6f965efb8a23525eb29897990da29c614778b6e516496984ead97b2928df8b726baff02502497b42a524adae1bc2ca21df5e218316b12e3fec9450eff9422a7ff9aa25b55a6140c512e6f00689de1759ef228ac2485c9a96a4bc394c7a66389eef8ade99be38a35251d8af5255d34d902908d86f8441abb62237c1795dc1745c1b13089c5457de9f632e88108a3f4d35a3b7d61ad17ea2b472f23c6509f11fd886e16d1dd58ef41911b713ab31e40fa5a217ded14a2fa354435ac812b9215d715298a66c51a455b514a95716d4c663c9153659a8ed11b2f8faf5a7b0b205aab98148c6d19070a9a8bd26a894486f726049e6f77f7a4b101f1ec6a35e44087fffa22d89b1eae37b01dd14ad41b462ce67ba4873d2ae97bdee68e344bbf7cde46abd7c94ad617ad71f765a80f77eac5bfdb870df5e0b77df8d6fc47fbefb5dff93bed5f1f6effc2751bec45c7e020f458e9f09d0e9c68413014490cf65aaf59e82227d03fcd0cb2bd9f0e5e2cde508568e4686ba99770b46daf17fb27fb5ff74ff16fe668a3eb718a84fe334960be7481c337adc0e9ff4ad1ad32c6cdabe4f5d871ff35fe6dfeebfd6f73749270d44902ff14d1498a458a4edfcd302df2edaeb5559a824d1bec6e5a1bab37d82f9a275455b5b57163e3f6ed3e3c8de2d070c354c6922403ca7f925f4b9a2c47382b5bc8cf07281ffa075f97c4e69f53d49425e32968b717d59a6ad0178662109eb3caa57be8f90478794da5e5e55637148a0632f95502180fdcb6a1d8d3bd8abbb049d2c5ba48ef4ac354c334f309b3f93384cd1c8fc50b12fed6a3b5f57e21185abffcd298602c5b3b776d1883569684f521e2117af8faaff727a138880eec06fd19fd021637658cb08e31afb5df866a096b6063dddc38cacc34721cd94fd6b8391294339223ce338f7204f341168735a5b58435551a76d0c1ad12684d03eae6cc373b7919516322391c5d3929aa65d91e13f8f80edb4362e63b07b42535304e13a3382d73095a2b2fe356cbf37398cfa6acd77dc19271240d2dbb7bed063cab9c231623ee2843c48075d9fa514f14eb61bcc6458a26c0a78bb9b5178a044190cf93e81d4e184bbb71346ee4ca5c7240d42b1629c3c400ad2e2bbe57acc734a707b9151f17299df1933bee3c6033e760da7c87ffa297ce77da1f919932f230e45029c7f3e5233954ffcf473934bc767bbc168dfa48de64dd5d8f4e3df07bb5e3ffc3da11bf2a7c1d51b7cfc1c33c52d2364db31e08efe3b0f9cf950ef7b7d463ce9a707371bde7eb6128fa5f4c80cca5027629cc5def8653cffc3d8e374e471c9ff296ee53774e4d24cc91f0b4d7fc826851fac3b1108b3d981c6e958da322abd1fd56f32d0f0bae0dd398ec89d6471318b4228fd648f5165e3b280b298a61d15a2ad212e87300c42dfa1d893791063de7ac1ff28a34caa26e403fd7083e5a4a2734425fe7d17d83a752321b4487203d7ba244ca5231a07f944817ad41fdcdcf29ae57c44d787bcefae273735e82cf14cfd775f18827fd49fa44a82d13ba41897a8b964e7549a3487510414626b90091f3c7affa3358936bbbfffaee3cab85c4d0daaa7f23920c248394254a44bf44221fd417d5cf77cc6b9db25e591a829dfee6715e0605215e7e6cf37c1d8b6760cb386c39dce818dbdd53a2fef817d832e3044bb57481538cea4fb0b14e092781994bfe64d77fcc608e9db20ed1c704f0ca511e9981cd963d1da3d3fa384a9e7ff9bc6379d05d14f95f342e3472a3ac2d1d5a3bc9b45716cf961d5277daddebd409ad8fcba0ba1e491636fff8b961590cfaf5ff92c5c68f86252cab0941869bff136c8d7d78e46bda1f1df9619b38d520e98231fc0f286e9fef12e42b05f9d3449a218d255dfd910693bab328ace04f5999bed3c4f45fd13e8c403ffdb81ce83443ab49650847b9c531bebbaf8ac852bbf3b34ae1984de8c6872193df1341c8fef82ab259edf8aee701f2ad59a3b1b860151ffbfae55a5aa0d7feaff2ca95368453b78b6df20506a705faf2d097ad2c894d6066431a515af94fd34141bf97129a421b6d249e402719c29ac20d870d9276a17f89fae3d057318a07e597904f5ae7645dcd8391c61ee0bf5af4148131e6742b3de113f1498bb94d64130f16bfbd7bdd494bd1abfe77bc6f5190a6a216ce639ad5d0c4fb8ada3c1841b63b81608f8b77d1ee1dc9c6b660666eca8d64645fe5f70607e1ccb1c8bf059b57785d7f5c3c68b902e177b436e6be7dd8b26b1dbdd34fa6ffc8087d763f4cf9d147a08a6801bb52197361f3498bff3a7a825462b98456349aafd2729b447e195230e3f235eef9cce59f8bee14dd24e6f1d4214f5de17050d2fb14d9f1249de212530d51805f3360b455967ae09dfa4d1e57737acf152b2907568b1cbc4d565a8a68ca480eee86b8a0377037df46ccfbd0eefdaaf75dff9bcb6dd4650bf0d1d22ba19fd3900602499328502217a2e2f03d2c1f1517d549186d60924a1d0999a1485d9e67419cfab7f3db5d7776df2c68f3bd5377fa79a8ebf5b03e8a8a29b48a92560fa80694e3845131855651b81c17ccc98051310ba3620aada2b4d5dda71a54582059cf47c5304af15f9d6e75dc720e78c2a898f71186e6522d9c293e2413dc3345a9549829b468a650253c3a53cc480f6b3ecccde6c16d92dbd30cf4cf30d66a80f35da904208bcddc6549a09e7030423ad31d7d8c2d5bedf85fa2ef57ed3369446a6f9e2cad388fb5c178fa2c825434c248276b72297c172b1a31cdcde48494ca14af95fc6a38d23e4917d23f73ce5483f92b96dc4c2a1a15ed8c56f1b55b9f9036bd0822a02516e6899eb2d47073985bf3ce61b7853933689babd3caed26439bb5dc6e710e422fbce2ad53edc826481ba5edd01a49394f244df1f53b5ae61cf26b866c880cfb14c1210c720e034aa13d5ca57f085668ff0d473f2067ba395ad33392a3e48cdffa6b7c4daede41729abb5e63ebc87aa63ffd7e3d61fca6ba6ba96f3e327e531fad85fe8f262c0da8efce29488e337f41d608d34cd3e37ab4e737bf0d49226c6f991c6280f15df8c4d27c5279981c70dc760e4c6db20471b44d117ccb5eb9065986504638219625ec4d9904b8361eaed14b483bcb52e59707072949cb3a4c63698096a89a5c35f414f61a050b9f294dcbb65cb287bbfd58198fa3d4e1de89acb9278735a15df9ab82ee7c884e63629a6f6c01e9173bbe6c0f371f36cb0f70fbc37d96039c3d7860ec2bbb5fdef56debe5a6a3869982bc80f485c68b0c0fa97928e07dcd7288b3f3a388f9964af8b6b3f221e5e18d8b3e32702b2805c9a213329060b212bd3072954d76c700ac5986d78a1d13e6f7cb0fa1bde3f92a268eaf1fced5f74d163d2543f12e58dc3df36bab490ce6f645df9fd92578462b443365b0e4e9afad4cd7e0b4f6a9ade11d7c0958beeaf5b3197846d9d655abcfa686377eb92391318708f4a75232191e2376bc13f79022762f1678a5a7e43380a582df4b3b6911559100d797688a36e43b645fe85ba0edd6462676e3f4eb97c18179ba5b251b26e41575a2dde006bcd956054b441026708704446ee9c6ab1a60fc230122fa672926fa84009464a3887a7d1ea07dd10efd468220fffdeefe310d03a9f6ff7f5af834aff8ab82f98c36a0b4dad483cf86b81c6f5d1e3830cfec74b732fba56e7cf7fcd910f35963f5ff4bdc9fda96a72f6b15cd96e02f74bc3c57146e84767514286b9ced71533ddbe0548bb417003ac5b6b02bb1ddd8b4a20949d851837e9611f33997395d5f2f06e69cfcfa9279315a3adf29d9369d7e518dcd16c7c594245b4789c1819c03e7f475e741716d02935dcc43ef3bc741bfe714b30410b9f359228b80dea0033e4994476bd404ca0ff4100326e7d0ad44eb81d2895a47bef341e67464f5018ea0fa673b166c1c0c24bb06cb4cf46231d069678b5dea6d3608b9dbd987f02fb1bb5bc99cb5c536fcee58a0fe4dbd743bd7b2711dc678e8eb1e0cdeee2d499ea8c5420ee4d0d94efcf7f75bdc94644e772cc81ae8b44f65e44a6e9fb8b56025a60d8df59b7768dee4855692e867075c63742a7afb779895ecee45a7dae8e4fbb8952c7c403372ace0baff593882e41d9c9188ea7a069f79afd306e11e2238c7aafb0310aeefba1dcd4409d7bfecfbf6fee52ec9ede0b61f3ba0d7689ed6c4ca309cf2f81a673d80a82c8d32c9c1ac267d1d098adbf8b9c5636dcbb6f837cadf90bce9c59de25b84d366e403cf6cc48bb357cf349419db6c7aad4e845f53e298b8d6aaf4ecc4fb93ced47cd99add4e492270374e7466625bb3084252300ec02e6c8390d7718a2cc569aa793465527130709e9b4b198c20fa1afd130910348ae6a834180942e8e86b684d0e5aeb47b072387c9a3d791cfe6a8485250d805e4c82cca49335d1068996d6920051d9cbd1c792a564b811e2f1284b638d723093c3c36859c96e093dea9227ed28f766255904ed75692c7dabdcc3318ab8837a0e7dce0e965884435fbf78a28d1ea5c168b044e41857f72bc28de6cac9a5fc1ba2b713f7b1f12939f8c2ce8d231c9150ed4c836383ebd78dd229eed38cd77fd54eb00d97d3afb8c07069e92fbf6acfd43c63cfaef9a39d95d448745ac7844b5d65ab59d338100de99640ba1dbe755dac298c4b8f03666d28848c90d0e24b6359895ae278f252d3a1f813e7e9174940684fd6c8b4d428c89d12a7549a4cbf44405e7d8d4712a56a25de299ee22f58f423a907210e92f6b8349abe510e39d621a1b16e4fe877900e7977a3bbbfa8978c5606611791347109875c83e597aed5c2f6b0507d3c1c6d827940cdf003b7f77a104b72959eefe301a5bf43cd3e276c5b9a3cdf46206b80ff67cad41f3e7d50aa7697ce7eac34ebdf11fe17872299e413c441423bc51f6207d2e45adbfbda335cbb0ce4286a15b6a8be6ed36bc4509e511a2f5e6620aa15c9e8fd4ceb33ef598d24a0250d8021f5175c83ba1624d71a5bcb6a4672a2fd926d42fa99a639dee83ce1d4ae69f767f69d6c3cdb7aa13dbae95e98ba93f709ea0f6626461ba699fea7c2e989a9c6808e315b1e5e89f573305acecf9fe42ae7bcd8f2867254c6bdff93fc0003b9a5255abaa807102a2eef03e70775b621887f906518e84f24e25f56eb54dd83e8aec509d3bf545d9e3aae85dbae97d5dfdc421ebdf2605d29f72c6f2e45255c9b53c87731d07b8fa74f7b29dc58b67a076d21d07c9ffa9130f6a7510fa0e68cc0dafd5011475b7bc42fabad46d720e7e14fbebc974ef81a70341d70eee59eff228b86e8ac208b3080d680f8f3bf9e7174800d302a8e8a3fb8f6e8b4c76dfd490786fbf52fb20003acf46b82bf61b609d2b2c48ef98435210e571a368580235e1a7eb6562fc1fdb0fc48a210b769c7201f31247249377e4f058ee8f21cb72b06f89527e10cb686ff899e324a85f99dc67ca88cfb634e5af4f524086e9d668051bf30f757a49fb48435e93f22c161c39cbf3ff5c6f4ad53d64f5a13f137497b76d1a456849762e200bfaa854e99859b10afee858223f272745aae7c31ef191d582cac115cc34a79b8c3a6b3460e8e2b0f4d9cb683af615ce43e658f95224f6caab99291172298d414defffab21d7960d35a4f1866093e58cd95b9ab2f0a50042dd46fe07d30990cfa607b910fb58efeef3e58d0f78ffb60b250e88371f56faefaef3e58f285ffe483d59cffcf3e58cde7c33ed8a3e754641c5f679a8e1aa71a2b052f33b5cdbd129a7c87f3d8f8feb6fe06ee861baef1d6301c68791cae79082eb96d043ec76ff0fd3c84af6504beebbfc1f7d310bea611f8aefd06df8f43f8ae8fc0f7b3fe1a01d05a82a49d1d8806b9daa9067af78078570ca10dbcb205047ed70cac57ba81b54183b183dda4f5521dc83553afdfc702c788007bff3e16fdf66cb90dd03b7e11edd3300b69c68ecf345532478d7cabccb560a340d18fc3ad967eaf5b18b8e28c9a968c46a7e4ff8cf6b3de961e2dd21f9702a87ff30e5baa886ea0af2340e0133300415415d66155ad1f01742ada71ae79808abd37486d7912509b28c0fa3d05bcdae8bda371733c3dba01ed7083069c89a7c735006bd10ecc7a6547ccd1c28083d622297c97c65cb2eb26d5d91da6d103270a9547fcb061edb9f0e4d6a6f18d6502e5a99f09dc3a441171c07df21ac6b902b77273bc10aff63811af688c0a841ce239b50550033ca7223739c03ec82d17d69f4368ab9c6900f18a252270c439e853c9cee02cb111276269e2cc237c23be73f30dd40ff32da8168b652fd9d4e6bac7b946412e1cb6d032917866a1fe2902dd31da331354310d20f2f227e81e51561d08344a8043d33230c25e780ff7b7f9bcd0dfbd23fb3badf0397be60c382b2657e0313e320d3a6b37cd20442dbec18c3b52a588ac07d959136b2df57f45762c86ee41f092a6e1b65a64423b12415a0bca929f5f82eaa213974a5bc59aec2cf90c0fb46e15939d155a67b171e729628ba0bdfc771a6f87a32da210d3a05e553f5869c07db3e5d86e2fcdf25d42ccfad0e67a805b4da4b43aa91b8fd28c415c0cb9918c4ef4572575e17353221b2af02a57055ed488694a9a782b8a6c33531be33395a12459121d3a33fd35b7befc625932ead370df8623b4fa127784c668e9db158095878228e84d47172a62b8b5bd5a84357309c4aca5ef56000fc2068161f41e0be7849c80faa25842b3cd5a748e87599ebe4563143413bc05231fb14ee3c1f054942ec0e2e5fb21d744225f4a0abca9a444353b2e0ccabc0a9480bd7bce1496ccd51f24faa82d3d98f5b209637656ef2907552de3b089a4b2f4529f075900665af461649fb5c1823139d5b00397f29441ae3e22cc315ef270a709d346117f5834317ed762769c0a54ee51969e18bd2b9650e5c3fa273c1d7ef503e5c9fced1e8aecc02b4df4ba0ae835778cc95c42afb6e1d97ef20ab452a8f2e8d59d2c2c8ab149fa40ff388471275969d9925774cecd09e8c1e1f49d0ac9f01ad1c2a6171a11a6a986fe247a8d4dcc925924369f7ebb1baf344479fc61112b03a24042c671f75b4b492c1777ec35015a5a88622e52e0988b98ec98f074ff881d996e9d7678f5165f17cda035a77eee84f33de8ad502612b74980673039d3a20c82febbc5044eee91a5c154300994212440f72544da34b59fc346004f4ae22d46bc39bc07ad9a112ac7f8fa01426bb9006556d712cdee69005124f09c69b14982201e6e654d02e6a2b8d972410a58e3eae7294990f8844517af3c02f11f83fe5f582de9d87b6100d5807ebd64a6e5c5585d58f1d9fea8e641260d4a70d6bd0a6d04e299b7986eaec0918f654d5a02c693272d3bc92815f03c096589894763bd240fdda11ce632ac71b302e07e50e23cb7e9a20b3d876491eb3b1924ae323971e4bfc8ebd15a8c495b697907e2dd05c34e0e663a65d18ae845a4c7d3e9923e74b326a163613baa7b6fb23a02f18d2a6800330ba2c820106c898b154d26812e84da23c6b17728bf3028410573b1c9ba30df73149925dd14a58dd894268e70b7cbb7a9551fe5da2ca265f53bd2a636e196b2541bc4175d1057732f431c61463cd871af828cd8b47b7e843228041bbe79339541376fa6c2b1ecd4d2372a46e382049221db74b3a0fe31e693966c531419b748f1975eebd55ccf9fb2b350ea652ef5ec50ca5ae807baadfd36772a14dcb6d60da56680abd64fedee94069cb67e3354b604cd58fd4b864a5351fae192a1f22d28ed7a7ea89cbbb577f7f9a1f24294fea570a8fc004adb0b6d3649dca29f976566580b8fc3feb8b6f7e65dcdfdfcc7ec2c3edf5a5807d01eee6d9b3b7d096ccba8b3bb53cd203de38ba1b26eb032e39ba132028bc9f861a8cc0fc3325287ca42b128b26f3075a874064cdf1f5c3954ae81e98ec19543e54b60ba71f0c050792a4cd70c1e182adf02d35983ef0f9533300d06df1f2a2f44e981d0a1f20328dd173adc2b946e9c68e722fdd60adc3dbe5aaf91e38ba04be211a64c39c28978b5eb0292740ecf63259f5fb8cae12f4978acc6c55e6e5414499ca434d40aa991b28152c3b28152c3b28152c3b281528fc80687f5d9860ff9917f04ebcb8f607df911ac2f3f8215a578895932542ab432542e48d850392f51cff33c4d7c4ca22eb79de6f31f93a8c447242af111894a7c44a2121f91a8c447242af131894a1c92a0a1725e82560e950b123454ce4bd081a1725e42de1f2ae725f4fda1725ec24287ca05091b2a3f8ed2f7270e950b1236c43d5e7f8b4fbbd3bcfe161f8ff161a0378056ed057fe5e3c91a61eff9c96006dd30d069dc770cf6db58b40ae4a890209f27900c1dbcaaa51b9d38da957282a87a15089567e6118e28ed15909937c1519cb3cf397407f71bf79708782942ab378a862f6cee14897d611f7ac7afda902d6fd3d2ce8ab021dbd7ccebc690e55b9c6b46d2d66cfd224ff11dd20a3e779f19c919ebcae86bb60de770fd77fcc0f1a7e485cc0ce8af92ae2791d4f5e67d9157f2ada0157f3d6dae455af28bc6ee4e233985753b602bbface5ebaf386d46f2c620791bc3d5bf166373972089d340896be6724e9b4b5620498a223b07bbf9d1f82b778fc7457640bdf94518e115282fe6119c3f5f9bc195a19228121bfc99c39329bf24d480e38b212d6778edbfd6cf8ded9cd861ec53cedd3e8e604fcdfd2df653677b8f8fc47e6aee48eca7e2511e2a432d101857e342fa6977d9c896ff79e1f9e3a8e5b7b4c32dafaf6738d8bef8dfb6dc7721fff4c896fbe247b6dcf718f6f60b1338ec73168ce8d767329e9284980c948b30a316382a2f5eade6a8893deb57bbb536bf1be2587fee8b2a3e0fb574565ecbf22331473b0c33a53eb31ac95cbe966e479e052f73a6f3c33287244c383fe44768f837252ed35cb543cf19174a7ca3ea7661d0233f38f2fccac226e82113dd1ea15f98b534e8f6e4bef0331fd3f03b4741e72bcd090cff9e7cae9259289c5b2d9dcfed18f23b54b5958c1b06e82b0dc16ef89a4ae411f2f0f17c5ecd4758e8a3fbfa36d857d121133068b3b4d4b65e60fd3e0eb05d5d2242c688035ffc003cb303514d8811ddc76d3c743d38afadf90d34b4dd62a815f3bcb498c6da5081e5e638729cf785b92b03c60b75a4fbab11b74493c598781e98e7c6815a42789ed98185225c8418b361021ff7dfe62c4b7bc51437e7558779ce234b13dcc8f3df1d858dbcf714db476846a6456d324d1d4fe52226877e9b0cc334d71bdc7708158b904f686d9083dc1c4615b9a41b60a11b6c68fc043ffb3eea7dc95f4fe7e4960ee796f6f0b9d6062db83a023aa8db9d4f62bd23f24befb9f3ebc172fb08f85fb9fcc5d68628b00fb63f7b49373e6324be0e77791aa81b89ef8e3bdf02aedb08c1a226ffe86ec504747699900b7e40b97db148ca030eba734b1bf85c4429a342e7059eb7b9cb1a2fa1b253da5e6ee40f2ea0b6748f2142ad723f80a42210ca049c95b892c0250cf842b8e15cd384f27efe2b8a57326deed51d3e0f51d46c73dfa273e791d83776f7ea0e478df6ba3bddc8a73bdde95ff834c48399edeebed7032647f715a6c9af17faf43316cae3be6e776b5ddd1074c363d0353ff0d0a7623fb6fd16daf91874e36537b4dfefe0763d060dbe77434f1e01ed7e3b5435c2727ccbc7fbdab53b62d1a9427714740f460fd9597c597dda8ed86813d1cc59b817ac0d2ad85afe5766ade5045abfed49e1f351047eea5b29c48260a2204cf1b95cc84194d2c254db2f0c1c517d1ac1ad4f0cafbda6e94f5ad0b74a4ab468e4d81e17a0f35b70bee5b4e492589dca5caf585b7c4eba21dbe29eb1f7e5f8681d7b9d038a95fd0dd2d4661bee47255dc284f5d3bfebb4b30ae9c27a10c890203bcb77e595042267cb4f0573d1e972fe54f917420dd48ab2945cc6d7ac7b4358fb583aabb0281653a173253b62b150c8174254df3d287a8a00fab07918db23c19b07309532a81514c1989de3c3872d2f76dad0f762ace21383e88e7fb839b8239011835c2ddd5d815bf6a05348615b2909186329e4d6ddff2eac77e9792b99958b95166a69ff1e1cda646b8f98af61dc424922841a9a3785fdf72cc1d6eec44aa11d2fec11b0c76fa6241b0558e675be4f4ca6701ac08495ce6c9ada08713739bd71b9b0ee1d8ab1d196008d28ce6d25c12fd9a97800b5f4076cf692e378d9ea40dfe9a06ca335a90e50f75d6302f7d4039b6b0cc87cdb62e1eaa74517eba36460b6a44e1d98180f4415f51e986a9b5559eef4e0df978f787f867bd71fa8f7a0327ac6048e8b07ba50b6590394e5362ed7671fd2745d6877319f438ed285f6162b0fda3c3ed7224842fa79bdb28cc48ace45467d0d02e52aa0c953965678c6e429cbe114adba527f45cb7db1464565b85c57ea8b1660aa746bd16757e058a27b8457606cbc5b5454cf9f86e17926a1294996b7c0df549e679a75c26dc0cd58696a287fcecab114f76387642cf4a5e842c54ae5619b94fbda8b9cc0d12a19334fc7ca3f8191b6f4e017f43e3f922d84325e84ce644d5a72b848542505fa49b14037896d9d0494c114c64ab3f0aa1d72d1d922e5479f00e5a92b00d2825799eaf140f17170928994dc07d26f8399694d82fffb2078359c67b95d9be6cf8473690ff81361eef360fbbbccf31cfbfc1e0eb7bde6b947dbf67fbced23b0ed6357008a94c153beb1d6cb3dd8ed01452cf26c3ecd53706b1c313e5e9aa155ec539dc84a04a1b9c2066b14fd6fedd62d799933e6fac5e42fcdddf15591963b990dc7023dafd41768a3c8dec14c79efdb05f5df6bb7e4c1515ac0957c869e2af82c5ab00d8e5211972afa0ccd9cc2aa1fa78dceae9316425be4e0fb77ae869f7fa70ad141d0b3c2de1023fb54b0c209829def1b5e2545a78004ddeea07c6780326871244d4367774f0b36f06e9905ad1509b607a6507d94c39f8f0e6b4a68e4f6fd843b8a208e8554ca6d9c5fd61e6d61e27d1d7caae266b4c59fae34d04f3600f931f4c528c3ade8026972409d40f572658478e84469560f5af54d60ca52f0b5fc0e5370238f47d582a80db62846505b7a54a0cff1287da309815f8e4acb0e3a724629ba9f4f423f3649d1ecffa9e2b6a213dd32cfce52a416bd52d25cd456f48aa773d72dc5ca1d9d0ca9b80eed64aaff5a457ad14d5f67411acd96832267110d6be099f222c70e1aea0bb7aa66acd069679aa01553ef5a45ef3d00b2e52b353ab6ca548147122e889d515d9738d8fa012435c23adc07c335f26fa355e1ebf16895eea4658703adca293f22015a23e46abe57dfa7d37e6147bb8398f67fa29a3d318893db938ceaf644b9fd0f61af1ed92d44238346058dcea6c9eacefe2437ec69873b862ced9a70dd7d8612b864b787ce53f60ab2f2501f9505f0dd8ad44207a355342b9f0ac574716ea82c171a9f30065fcbaf5dfbafed7fd131563508f9d6dc66ef7ff10b9b70e3bdf3d13bf18d5d6e0ca5ed01754377fc9f425f9819e921ea3bf99d0d74aaf45e88ba53a0b373e8ab34adc3d407dd1aa61edc11fcaebb6eeacd7711f5e291a74479ca6ef9afa50b9b00a2f8926da8fe4dcb79384286b89d2529fd8343bdbdb5a177f80cbf7b2736a1b1d280b82bf06be83b0ce09770834fdbd01ab06023d42d8a35b4bc1757a4f4cb154df4bc0ed86b31ec3dfc41b947b4e16bc39811f45d1f3a972e7c4923e8469b5dbe07ed5fac1e8bcee008769712bcf3a04d68b79d97f713fd4ba24d878dff2a6ff0d48571bbd60fbe8f2dbcc168910e2927bb8e71b37c45fd316ecf19bd7f481e45725582665bc0847179071a8e0abbed366e3ffe0806321319cdcbb168b71dedf61e35fd4be51ade6d8f2d74c88f23fcc70f734fd5a5c35c6dce1790fc13edb6676749b574713960780fe1839e7feaa03fd49f74dd8ebe41caef2407376e4223ce9f234ee36765ed3f4a684a3e1dc67c272d3e6968cd19cb2f4b46dfacbb12ab7b074fc1e54bf298d550179f8a5c72092f889d5548c11908638bce61f957623f39c75b3c49136a45188fea11df6578fed1b2c653b26661e4e911503739a8212e9b4afb13673e72a2a1484bcb5bc43fc7069a5c8372d4bf909677d1597edea76a78977b569225433c2d234b3030dc6ef271bfe611edfd7508ee303a41f0a88e6fb1f3b302aa2dc0bf8ba2d9f0f6a9add39a366d1447b8a511ed7b95a5cd4dbbb19a971e63b7b05fde6369e46c7d53e612213a6de367b6a16f453a396b2e47a739554ba3a1cdf5200a68bef7aa8f8a5b2a2d994965c9451c77a16db7f8bf06ff6bfb6dcbd38d4d658dfc79b765ade816fe8aa6558dee7bec35d7c37304dada28dfd9c2dc52915b6629a22d1fa39dfbb61ccb7819da775f4c90458e489319f8d27481c45719a1c28a68e8df8d7709dfff00575971966795b8100fdfa95be0f7ece58230861925b4f33e6b30e08ea29c4108835789f7e27205ec89e7ae344b20c49e336afd688fb092556e3d7db7dcbac26d51de95fbc9d0fee0ab61c2b7b9c03e3ea72ec39dd368e5730ea4bb736af65ae4dc79dbf86fe19c69c2f854457cf45e65c41b98309f66a35e8a669809d43361fee5f2e84d1560878325b3e0acd18dc3a7274b9841e4a887f8b2c244779bee678330df65a159fbe3fa85058ae48f5ed34799093add2596a494273f7ac60e5fbf52b02afaabc2bee15ba8e67197823ec6d5735c6fe841dc3d64ffed093d543b3b0b7d5f047d5b049d9045a730467e5f44d0a8f3686616f0ff0de1a7df21c7b3e343851136155e867a1b6e0ef494436d3061f4584f09cfa3b4c8489309f8d3670cfe34718e4915e4f412cb145608fee797421bdff2d2a8fd13fafa50b0a580f6a6773bf8364daffa3a102739cef223f29d057a1aac2125b7caf42256126b0980ad99cd53e59510a7c7ce549d0a7b8e5ed787b39e50863c8b45722817d4a89c70cb3f00fabee09a2a722708b69659a31bf551a340097dc071b9a03fa9a42678af3eca03b0d0333df51a9ab7e19b67a4ec073cba7861b17ea60c4009bdd70088e778bac8b468383ef9e788e7d0098f75ccef7f8b049df7e05a794d7fda84e9cb7301bde56770b9a0a066be9d6572c15466aadbe7cdd09ff022ca52751729c64b24d8c92d65a9a2e3b984fec42840779503e5917f00b72cad2e5c58c84335a667a7e94fe412bbe939af3a9c3dbf12cf85da84f14a449cc39e43bc3b645b9a7eb1d5d855d651defe65d3b78d897dcbeeafe85ad5b1ba7d5debabee9b7807a800f758932f5e449acbdbe99788fae86246ab8ce805e81b402d29f16bcbd74ad6dd7863ee9b656fe26fdd583f7743d906f455a08b16cb07686c429e43de5aa42904f3a51574e5ce8255a736d1371b8094f6a461cc26ecd037eea6a09e471a76e202bd0b234d2a4c340b7aa6dc98cef7615e89de05c7115f9823df0747d7ebc3236cce3f3eb6c077367743659564afa8ca64045f424a27d617a5c5886716f8a72d2c2872d2be50f2d28a9c682507695d95e9017eb160557124518947172e2b54a496bc56d2a88c988f9524c3be7aa0952fe52c578a978d0ef000eb8cd79857dd5ec2c1ccf12beddc1c316c7d59e883f271a5a6126a44f16702fdcf4417faaf9cb39d21e70c2a56f2967cd8ee96e6b96b69e7a25a85e78475815928c63ab53dff3354b75f8eea9eda5eecd8942c8e403f1ecff274f4bd13f4f5136481f92fa01cced9141e043297b24b35226ae9a780ededdd8e85476e3b04d865dd223a4b86ce356ea7737b71463b5a764d8eee27b0e6801a6c812e2ef4204dc8a4ee9bfaa5d958a895640659d9d7359101bda0ca7c48dd66a74999849543bcb28eed5e0b6ae31c75641fdbb04094f9e29fb258976b7be4e6cd18fbd343d1c7f5146a47ef9410dad1e215f2a5bb5893bc06d3b2e3fe007e78bbcadcabee1ff7b136f2c21b18ddd2201162d0a29d92c37b4ae61eb6649b74f38905a76da815d9827d36af4f86be4bb88da72ad2b70b9d86ac31e76db1c93e67e5e701fd2589be73b2dd4b5b1bb7d6aed312dae7ed282dd3a6dbf8fce76d949f01d0af9bbdfb0374f1d48b045ef59219ef4fd2b16d79ba772233ccf8052612c685c8c7a0df22b1700bcbdc1fa4d32aa0869218cbb40e865b22493226f225131e9961c2ab8ca4889593f85c13ddd7002a2d1409a19d10babb015aaed641985340e085794b7755457580e59003bd7fa697748ca6323603af73322dba7b458b6f7a7a69e9d1373d687f0f406574a1b344a206e023a6e49b017f2fef86bc5f9e096bcbd414d9b8dd2b8bfeab0b98619d07d25c2d2d7920a5090f69e68b2c2c639b5e04994bd038fc49b732f75b39e5db235a9e1b935b5cb3a9d417fc8733d9e37aa1a40480bad389c604c33213c2ec422b37b8cb97d13ac6b9bab038a26c85b1d9c6361900168778996e27e216fa62f15eda4fed1307268af3ed937fbc6d9f31c0977e684f1d60ec8476df2d76eb66106cda7756e8eba89b52d857d14d69b009e914bb15f656431b4e406ec581952ca595032287d05491dd6a686945f4d91e295ac742377fccef55257e00d0a8474ecf024b76b19087b9e7cc3c5ee28118d23be6018e78c8f23cd4231eb27238de57481c416171b0373879876d0a019da7834d310f28d200d6da824d4bbfbd6adff660a2f853fb4ad80baf2c9dd6319abc49b97c41adddab4668037b80c336c807c02f8e967800222ea034346ec3805716c2a7d3ae3d1e1ab7e5c16d1be20be201165f1bb7eff470eac3e3bfafadf7a0b6f6bf48416dfd7716b5ad77bb2ebc6d17bb146aea181927cdf428a4a96f8bafc9afeea264484fb104bf83b5e787eebccee3f581825a5a5500b9c3dd933c7d9eb59c0714a7a331098efadfd7d1f73fe374d4f0fb3a7ad10235541379611b46ed9003baf9928785415a7a60e5b09662504b193bc569e981115ada18fdb8966ef88d96c624ace4b474bea0a56b052d9d3f82873109bf27cdb4d8036c0a1aff1fe439330963d9cb629c5d22c67b776ddb154914e0910623d4d61378155388b350efcb0a0f17d0dd06406871f9c93d992f62bb235d06b0321797875b285f0bd0ed4652b69c3d511093572e8e6c6a029c362e23a56c922f2076405d26efff995ed4e1e1d665f455153aefa604cab7f9a6842efa2fba4c4a055d762c72fd0a35cecc49b0ee8198cef210f3ba4cad5b0664b685c6305e2709d758a4edaeb1508ac7baee63714f733a19bd639ffdb95f278ab7d99fbf76dbbeed575e03bfb1a50e6c784c03390a73380dd4731a58fcff8d067278198efe3ca88145bfaf81088ad3402979835a170e428fff170d14938d54c6784103b936749c069a380d348ed440844fa7fda19ad7409e03d7ab86a5684bf53d15d4b2242a096a999eca706d67258d7f6ecbcbcca72fb400e2eebe5b281a9ad0067363e88b2d38138fa9be1d471111b197f202f605920d3591972a00da55c742cd5f31da4da572b4daa3c53457edb5dadbf097b9a4dbe68e69bdbea07c6144ba7b68d562341ccd58f37b1419113b9b70a9237d3f0054b76b7b7fd2bff599bb5ef08bc91dfa0a419c4e7bd536bca2b398201a11e536320b042edd82f5cbdbf2a08d18a353595fecc6282200500590b35b9ac760bb7dea2842f6d456b14eab2ba5c7ca70a8599f0fad7548ace412411777cf9acd54c0797c624d14a906bd798466a11fba6bb9553c7fd73b2445746ca7b6746fa7e41a51e612a47503be4b7335b9c5e7509ff995117e9d0e41a2f9354b1499d40d65dd3526c0715ddefd9082923221af0ae6c9196e5f21b892db879c4d54a8d13784d197c0310d7feb476411fccaafd01a5b8c8f4e23ac2071abf6ea52619dfdd00e7ace86fc73d916d89f64ceff4ac47cf8b2b4cae05679015a638cab9cb00badd67819ce187c9a86d770327d59f974500e7dab6c4bb3cd5d4f5eeeaeb7f900aa87d636bd0cbab86833bdc780f330aa0f7888900f26ec12cade333cc203d6321d5c84981971b6e5ba6d64ea07fb8474743fe24cd3d42efe8e84e0d7d594d0d3bdc38dbb5e896624edeefbafea513792d17d01c53af42d3bfe3be8c3df459f6628beab0c91004cf3fbf64e5f418c3a6ce6bef1b982148b9e9680ca9d997fd14f3703fd6c19c012cc59ba102be901e8af2c40b166a7384686be07286eededc334dcca5488182cb7b1f27070cdb22b76a13151d8cdae791f0b253c1c546a7fe692eb76cb112efa68e8b55118f69372ba4f88724e7eb0f2a9894ae5f43f29954fad9c78d830cd3c334719a10b5146c42895739e9fa89c9e3e11a627b60cde7c78c75569164ec9c728564633671a798cc967c25bb3dbb26fd33b65405f2123f29d0b19be24e8e294004238fd92d5adaf106ef2370cddce67c41ec20dde537acb635f392dea02a886f095d3835d1ea8defb39c5e74ad6bfef2847bbfa355c9c54e3f5e9ff89b966dd922901c2fbedfea4e876dd41043759cbf65460f4be1689c81282d1f34989086221727c6e3d8e21e8d43086a0b61f8d09c6f2a13305357f096fd7437e89a66321ab52f5d3754ad1d39852ffb42e58340b0bd6cfd24d14cdc626ea67eb824473b0a081b756649ec984dc0fcdce2a197ba633bb37fb129610bd2bbb3dbb156fc4fbb29bf0f60403452ec2f55a31283e475b9dd26a8f901834a37c99a18c988229a7bf8c5db69ce93d9c14e389be4f77f2d23e2d7dd50976fb7034f1d6ea0a4d112f529ed8b81b69d5a6242cba9dc3e678cd1b3cc792be603f8979f42fc73bb0b8430b429fd35f24017f2b8d95941294a414b79a3c06024d2470dc6a89cb96cb4ddcfec0afbd689f33169d1b20c4b9b685c2773582262a52a1243409b2b52fbccf5d92f5a4626d74e38d8cdc73d969c55fa13b2bbaafa814b11a0bc10eeab59d1e6627fab6fe7eb2d3a32cedb81d7d9beeaa4daf1a05268e8aee70349003e8065d74079512077ad1f7e4f11fecc1696569fa04123024fdee455c347514a42b07b0e346e3f4771290b9c2ea351accca896ed2d867bf5d2d6248fd45d760b5d71d60653cfaa79a1c5b4f0f4437f9d974cfd5c1d6c4d203f6b234bc03cfb0eef4e867c8d9a65bf86cd34d80eaccea083479f43b36dd1ea8859006f0b1ad2c4d548f66d02c09edef026569b07e1043426c0893f899a1f21aa11c95c112dc0795634f43fa73407417ea1de2bdd2b37334b680790ed362cfe916e89ea564ba31278df4621b76c7288aeff4b09a2420fa3e76c18aeecadd67b786009473a643577f36c3ac82391971d2335d1fdfcdce38bbb5dad8a0a6977d2dae9d27df01c7492abdbf6fe1e125ca084f6ce1d640c60f5432d4184fccf1d59549cae93f83a37b4ef4565eda17ffc62efaeb729c979993f785f5976301b5af79abc3e23390e4e8211d87ceb2924e0f65c4cf808947b5bfdc1ae3c9484ef41e4e44dfff3b0af1d05f5f780c4bf21184a571228fc598f1fe39df0084c13c84c1fc9f30740896f49f0843cd040987a13c83950029bdbb4132b2b731f3fe9ff6de04ae896bfd1f3e936d121605230404351871a1ad55e3be50628188556aad806b0b0e8b581565116d4bcb164258448c8a8a0b5ab77a7bfdb5a61a2b95c50db555ab15b0b65a34026ad5caad082890f739331308a35deef2fe7f9ff7ffdee1f3e599b36fcf9c6532df73b64e3e1054a0a5df9fdf550c6d45b8ac812bf66bf4dda0b493aff6b12eed0aebd2b2a9d4ecc6a984ca9854c693461535ab868e774830136fd46d1c6fa1d019723d24432ac4f1de71578cb88f46151c6d3950d95bed9543bd63ecf2e42517e158510f1ca77e85c02130d15420723f10a25cfd00e1a7173fb18a115f13ff68c1cfadcace74a97e4297f05be8f0dd70f818b46c1cd61ea23fa33dfa44016ffd45fe785b44f9daa21807790fcc24c4cf8fd23654a5512785509a07eff7d34ae31f3711fda9beb62b4dc9f662a19ada5abf12e27480f0c4dddfe0bf20a582ef668b9694c2bd1cee6d6d519291ff922d8a30ea13d3d1b18a89b6d20f6ccd7ad296009d1d6d8b4aeea68dce46250f32252c4ffd86d78f309beddb28088c7818814f5563ce7d2afb840a66f88d38577a729940a6a3f9f167a9aa1082b9979c65f247bc910bfa9fb2973fd21631e563e605353bf6df3df06076a9464d3937f21e465155e40b621c7cca743de419737ff7649251ffec691bfd0dbbfe99187a15be42dc6023251d44981b40a5dfc34faef0136c17de1fee878aa07eae195f29d4f4461d23d5762f1dd553f8323d82e03d97d8d1e705a30afd1ebe664ee79850a3657d084cd9e4b65547db42a894b64138b7c41bd48667885a678f96666ba6e9a3040e4b17a50c2c7994da7a6129351bef19d92e534e794a97b0a4952ec19a678356682eb3b3a4b2ad535dd9deb49aad9f3c463bc92fd645116cbd8716e036f0ba39248bdd5b7e85eb4d8ddafb47a6e78e3240bca271cd78d6cd84ad3078371764e0af07f6fc7d4ba49bb6cd7c2ec7bb767fa40db4e565cdd1c74cac68ed902caf5cf644af95f8b48b92663657c90732bc322e6b166918579fdf8e27fa276aecbc6e1a32ead180c4b4d3307fc92c244954f2f893bc9fdadb4286e40ec9a26c9a9c9346e39c8ebbebf5803d23e0bd2951f716ce6e60725ab19b3f4e8b087f7fcde58c7b1aff9beceefd71d4a4225ed642c85b86bfe69e65078a85d05a3d4f27e21db4a8ee648fe38975b5f87736fee926742052aa7d623e5acbaf7862a678150e52d11a3ee558e1703c915fb1177d9257d71e992585d6196fe3a5ba368f5afb8b7b420875f23ea2bedbaa68195798d16a1ef5a3f4c9d336a9d041206d7ada3dc58f72de8326e2af32ba6bfc14e8a61d1e8fa86a11f48bc5191b54b47ea206bbdd6aea5a752f29d960374ef3a99f543c54545c7f98271535990bb524ff93ee4dedc515877954e05567c6fca47d954c2af2b0a316ee75de4214934f7c4aee14e7aa09a1faa196f25313d2c49e88a89814559c5b8fe0a929859e0034a8e411336ad425ea2aa4f49371d346144fa8938e126fe09124bad82b7b7631f1c6a669aae2b69092c741873ec93bdcde784848e75f6214da7cd2fd5afb78ad5a659a4bb6d71edb931899057d2cbf58db8a1ec048152aa0b29f617e8960692eb5ee1972cbed0dbdb76bd1d2dc5cff79ae4f347a718a9d545c6ebb483b232350b3a299fdfaa711ebbb97eef847540184869c6f320d59147d4c0af119ec3e25a84dbff0d2fcf1af01888fbf8f5aa1c19a70198767daf38de31f494e09d5e3edf7aa16fd3827e4e816ec4e3f098c7b80e34d6861c8e7b022a948860ed6e2efa0864499161598c34a712aa675bfdcc635a6ac7f80888154d01aa1eae2f1a8f1da02a2d8de48e86d4245c78a866b4b320e1d129269ea26b350845be0ad4352d2c36e532ddd6b100d765272981d8cb67c9c860235a1dd87c67e733c0af7db4b4aa96fa7d26d52974b541c875629805897893cf61d8fc212b71ce5bf8618bf7aabaad0be0fb498c007ef08414c3365dbde4e79c3346b0de4ff51f7de4575b9b9a5383f9a690da590d3e905fcd4a8e29cfb3efc0a19b23fa121352f79d7a4d2330a936375fbadc4f1dabf11e3c9261e2ec7e043c2572a8d78de33eeb1144604dc3fbb562c88da68eaf741bfe898476ffe04b644c433f82f9080bd624d3dfec2325e205f10459c5424d7a39ba51b4f5c2ae58e2cf9dfe09185d1b214359ea5f81dc523c683263c62e0d1415731c94d3ad796dfc08e1bd20f9e752f2fe5613bde4d76867333515f41a6e15e56e1d2605390488fa3ef95dcc77113fd756abd4d3219c4faa567a121fcb1309e25b7f19666d03b60317df116af6c134fb8add8de5345bc71f728d817d1fd6c13debbbdcdc8cdbbae02c7af517f6ac4396e7cc6cdf17563678eefb2393e5bcae48ea44aee77c6b495ae05acbd2917295b3c32213e5d1a183b3e11bf654bf4676a48a76e39c684d7ce2fa753bdf95c3d2db1aaa7242393ea7c633fad46fdd511a1da945eef2c515379f536ad36d2c40ff0d7f8fc4ff2eadba573c723a9aec94cb9d8f2f42b7a22dd39acf398cdff49f71fdaa5ba7ab3b45bb9d3522df146b1fd1015d313e0563fa785f43e80d9f594569b4fa7a6bc24153feaee394d9958c8c7b109bddcd8f8a93c5b5e6a149e8d53bd9ef2a84c924fad58c2bf95b8278298a294b4f8786758be8a967f9c1a85634903ad4ca948c573da1a434821dfb46c498b77f6396d90512a29778a8cd2273e4349ae0c2b5d3af70d786a5a90c9c9b67d472976fff499307b6c35aec74fec1b7ac62007b149632350d8bdd51db7ff9cc7318ff22eaa448a11eec441985351d5072cf3b4d6c3cdec9acd9692093f8c4c3cbd42da9d70de1a285b89e7b99901e593cb47bd9f0365bf84c3db31e1bfdf6709fff8f023363c09e1570e48cc58a1ef06e127ab20dc0a08f7ea4fe574b8c32d072ba9ef8e74ce0f99319d4fb90be34589993d7008d5682f089177432f9a2ea2aa82675be63de09b19a3cca2c4250f587d7f0dcf1bf13eccade84088903cda6259a7a9ecac566acc9ab88d72152ec5b3bd6b21d4693581358fd292bd32a22645a9ec14635adb8f472926b4b65f483c4aaff95eb0e2637e656ca19c848b703c6e92a7662a582e2b9fb2f58d21211712a5223ba210fab2515aea84175f96488fb3a31423602539f43ef2de74aee530c4373c87faa9ba47cca398ea8204fafdf088725231f46b84579b253057a57efab16b7affa07a0b17ace9a11a3b3c27baf45a30e5fb9d33fb3cbcfad74b5ff480ea257c87293dd6f5c04443d45354999732f07c454694946f47e0b8a00620ae3f29ff5d28ff1c291f629a4355db088a6da6ab366bcbdfe83ded407060a28adeaffc6006553157a2f83284285881e73e91038e6e5e67aa82d6c7f155748def36946f268e0fafba15b0ea7e9db84ce7406ac7273a7271a2fea46326d54310c883b5496a08f576353a1eb220eaabeddef72f24e67f631d63cd0d98af4c4fad8479f90c5c62ddd74c4d0bfba8c81c93e2cb44e228b40435ff7c57fdf811f4632ace47f9b8cd5aa7d23a58256e262e6714e68a1035f773fe5258890c0961de0b7c4de0fde70ec2ba046a7a3ea73cd5503f9371ba0fb3a8efed51ca94e2ac792a2a64bab07c1aaca48299b6533bd12b29a8abc0c483197a58f150c7e73b611d38ba19d7536ff5901c6ecca1972817e1247abd9328f031d86c22f22f1ac80062ebb4921c6aa36d0fcac5be073653bf85102559a0e5a7182d5f7ff19fd5f1b27394543881d696b933368d97cc561593efab4ab2a9992da8d05682a879cbf8b8b5e6415d7c48dcf8fd77244c7b9c82e77b148eed8696fade16a5a8e7cc1e2ff1528d873ed0142c373dfffc982e7b9d67daec14ef8f9f0e7919d5473894793a124a55246e1bba75af9fe9aaffc740ff87e4400f533e6e48cedf8f51427b9ba4897ba2162432f5722bf1f97aa1ae3fe85aff5f41fd7be172f0a28ab5f3547c35f4ec57ea117e7f43cdafe1596ae43efb04a9ec0a336408f2f2f375d4b5c72bfb12ea7700b31ec66f234894e29f1aa59a8cfbf5f5dba9c98f04ccb851b2d9f3506a54dbf83699774d7ea92c81de37a4014a39f43384fb53baa43f5bf4b8c4d29f1e00fd976724e6f480fe77f4901cfc5c620d639e4cd3b4e6a7f829679eccd3f78f16e0770625a07343724c95e79bad4becb39fea2bec8573a929851ec61f6b3f2e93903cacd5db82cefafb13fa04e84793853d85fe25958a11dfa2a32d5803aabbead36eca4de8acb7259cb12696db2a8e3cc39af8f5b33fd544f94e68df1e38a4651c28b4796a6677c4dbe6597e20243051f1e55c265792a39b159fbf4454d1fd6cb92df4b35ddb6f6bc45d1cd31642f1f933743aca3f4a9a634be0fdf7b9b930fd587f24a6e6dd36d3dce96daa80ded368edccb0b5f4ee3eb3f9384da687df7bb50452fd0531a91ecc8e36329aeb590d9afb7931527cf912a3b973af77ed6fd6bd72734d8ff27107b3a719997ac3e1bf30a6264e4ac47ba399e6257f5e3e45b60aafbba32fe12704d7fe8544360fe7b3788a1139c87bdd4e3a9ca9b2fa40177d5f6db2137e46096db3ae692ceb6e1f63cca3ffd141aed86709fa2eced3e493bdaa11b7b66a44406967b832c392af2f24a6d4a75642784d17ff99096017837707d8dc7607d664b551bfbad9dac2fc23cc492af9c229e651f7fb78d74929417ca118e331adb150d1b38187dfc07be906b9969c4aabf684e7c5315031342550e1d4c0dbb24a31217fea90f492db5f870dbf3daaa6e4fe1689f77def66bcafa5f765ef9f52cb983d17e4535323fb45be2cb6ec062bcfda9f4e4d1122da86c9f31ac7f2b6996921293cfcae605385f5b942835c99587c26a741bafc1144e0000d7f2c312d6d44ca34c8e99bcc1eb55ee9cc2f32bbfd86dc1f7e67d46dd1632654e864a6071f28dd48ed8c9c1499365adbf756047ec34aca15139afa6c5427f951efd65a7ecf51b1bb44042605615e1b6613d89f913a8d46deeb15634e39f0f791c384fee39deb9072e119bc57e530c5984ab9ce9f925d41697bd4c382562bf6570cc56d2e21bb91eb2a8e1630f1fa4dc831b1bb6004107ef38df39707dfdf9fc5b869c6e15f43e6dcb1fc7afeceed21596c2e26b0f32632ed2b1209df48fb52d7776bcdf3dfc531f108478585c9bea7bfa49f94eceb26796cee2618b0ce142069a52436286d74ba9cd2a889a4b1613d1b8d5b7c7b86b78da60a06f215bbfa138d55fcbd8261e39d5f22d2fe968e96e7110325bfa66ed8e26bda98d89266d4f5356514b4be65c4dff8fe509ab6d77758dac8748706988ba4d4076b5237dc2c1d02ffcf965a4e2a62ce28c2bfee78651d481fcefede55f3393e3fbc9157a87d6c969235dda9c8261e3e4350134004b4cd19fc06251d2bc49c1aeeaf40f4371ca2749b01192fa31996f7e7a3da42f6eb4c3d1b5badf52429e89271023a5780f7ba9be78bbf5d9d75920a2191fb4c6a6a13a2b22208f750fc36dfa546916c84de6795b15f8625c6a2fe26972073127d2a34bdfbb24ee8b718e17d3520ff8cf6cdfd9ddd969956edd77112ddd4ae27d1d1f7f43974fd32ce59be847e805d0a0542f47bf1150da0776466eedfc69a384033299a39af504ffa1112a1a2afb0ddd4bb913e41e32db541d488f8552264c868443a52b1e634c2e7a862bfbb397ec37ec7af620b6610e4aba3fdb13b7d5a07f8e143fd51da3a24f4a3165623cc59d591a6350c07402ff2c35f8608c356f0ab45489f20439bca31534cdf381009d336ddc5cc3fc59a3a745ca61395eb4d7d2a5b5393dfca7b60d48b2208429d563d00b96d2009b760914aef24e251578408734770a8e332e98a81a85cef7a0f9f31a4c87f8226c95a9c557a93338e03a7510b2b653f4440ea69552494c2ad2a03bd95a7d85edfee569001bec16fcf46bafc745a5503518b4cba81f6b3b61e49ab3390c99971f783314143e8481c8f627b75bbc9ada95de12ea419161a420aee2d32da2dbf1ad16e3db0db4da114624ef1c77b17ad92b5381d2e480b56a3c3058afc46549ea67f224326696dfbf3fed6e7617febf3b8fe084715a5cfd4100a370229dc09942f1c214e92f2abc5a8c5b9c54558afd82e6a376d723243fba80dc2069456294406d113bafd0ed3ed877fb155927b79fcab039021310a1d2f905e25095c77fa1092c02d74b4e0788162cd134857b1fd49fbb4d2b4104f02ef77242d90f125bfe8494fd0147e48ad59526fdab2976e61a15fe77986ecef676d92bb7ca7fe845b5023d1f1bbbedff5d2349927d1f9cb3ef52329680b51ac013d83347644b5c992a0c4b530320a09aa57936048ba65af6279b3f0ac9e0c478a1e0224d59c34af72e5870cc6a5829634f5fab4ddfaf768c51a21f22bd593be482fdc45603f64bb69abb1bda154af8d408ab550c34edcf058ab134a7b2fc727c7e093cdbc74072ca71a3e0a78d0c14cf8d52bfdaddaac8ef386927fb373e870fb073f84406921048fcaa9e7e9d437fd95a206c40f1621e12d0707ef0df8cef51e3c15ebabdbfb692ca7a0e1dfbb85be113f26cd6c346afc1d4fb405a7a86ffa29491c92442e0ede05f49325ae43a3d24d1115ade305dff99c35422cdb8dad4b4a57cd9cdfa575885f3bbfa944b53b43f1ee8b4ccde33d843c89fd5a4b6dd4b7e3701e514932e8755cf1593c6397f76bc66737e35d6766dc09bebd5f37249dba24b41b6ff3b9cf0c5d8aefb12954c514bedbec00541e542cfc8ad77bca789dc6674a3ae5efc52f0c1a816e86146b1a79af6c11de518e5c83b6ee4babd611811ad5b8eb396c7e2e941bd32ab05dd8588b5dd1e9b346c65fd2084db6c536f45842a9e5d79c9a33e5234c5336b51706f9a2723a85de01c5822fd100487713cf3a5dfbdb845fe75e359316ed5c8477ab39903e31a0dc68890d1dbf39e2668729f468d288bbc6b410e81fac182eba8b98f546f85a78b4a15f87cd944a1e3df9fbbe345904227ca51264435d2279fd34d63a3740a3b2ec7077ccc4ef6f4eab0e478dd5f83f8e4d7302874a2b0847f83bd1011a21ebd7e7a02958d73ebfb4834973c4a37f837100d4061bd76726fbfe2d69d5bea8b149552a25855d5ad05861fed8fd6c6ab297360d746588262d6420da74fce68a4aba45f9c103d18ec836673d1985a8de55289aeeb9b6de02ddd952dfceee14f0b34a1846e23eaddebcca49e1588930a3d6c20ba65ac62e1ff0787fba8da65fb3a8d5c2b62bdbcaafbe8d0a65ccf909fa0d248f3a2be2291c083ab5843cbdf636a27a548b06d0b9da74a18353f498691d0f7a47288543032a2f9590a66df5ad161fa1eb3bb852956905b791e92ad9e1263f12dd9016d29f084bfc0a4af71805952a1c6fa2ac8c16191ff7eb22c55a189d363f6e77fc45d183403b353696981e461b35eab6201b8da24783e594e14ba78c58db99fd95b0ae8fd279a78b5af7a71fd00dcf3a9ac56ade0d989d4af0778454f55ede8ea81619757ca050afed8fee0a2c6d50d0febcc651efe22f165113f57a4bcf42a1a483e1178ad7de3da9741b87593eee3e944d93400abd135b3799f37ca464329f4aad77a62635f508535369f52e52723a9fcaa8771e4d12c949a3679d11aa15eeb7d12b6a9e6c18e9727b966f5a703ae1418ac87e050aa76ae45e46a9e4fc593e38d7945d134aab4e270668f0a94b2d96dd0fb4eebefc605f62aa8f7e830cedd980fbfae57952d1233135f930d20cfcc2cf0def7afafda91e6921be041ef971bfd29087c782b49070c2f0a41eda93caafef416d9538ccf3e587a41319e49e82b46a12b5e451eb49076aaa9c60734036759e3c92863578538565a7f9d08f85921d518a2d021831a9cb03bbe337de4d6645fe6398c1faa29b02c51a3c529bb63e6e9f76885bdfaa639a808463f8eb2a2aa59e27251d48537efdc3d15ae4836b282c11d7d060a8a1c164cbb1aef566af16c97a93370f8dc65f91914ddd679d81581cb08b2bf8772593c00509c0e5630811e54692c995c6e7fd3a825f4732e090146a70c0067e10f4e9e57836a27010210d597ec8d4d7e6e11e4d67cb8626371ea28224302b10a04bc58afcfe448b61ecf21977f8733c51bfe621e94c7f1b7c7b4ecd709dd4d69f1f08b37db6d60ea7cdec8f9436fb3b4fa63fa4cb9963a9c308aad297df4f3329fed63246f778ef9dd3eec9e0adda194fa8613dede76139dfec127df612f30e2b2225800a28429d36352b35011d7146a7859c15f4d3b4f5c43aa3701e2b64fd44bd55ba7c79bf86010f026b67dc14354eb95bd57aadf9c6e35b781fe9f483ba5159acbfa4dee5173496564e9ebd7f994dfcbdf029d1fba36d16d5bdefffc19e0f441f4e8327a49f25edf789531734fd34e51dfd5f917e6207cbb166adebd982089ac72ee9788632069fefd0aa35aeb51dbdc39a61672de9d624d23b9130ebc7442a7b17ef66a262530bea1851f2761b997885828eb492031a3bc688fd3b8c8a4d1284739565a9b7847e1a3b07766d1ccf9fe981d2667af0a24fa68578f00664b77c931a22954ce1eb6def09145e4f797a328b6fd1e1447356bad4aebc3d650abf329d909cb7735883d87727d4394d267b8fde490b392798a269e969b8f7d46237d724dbd27a4ee3eec08739a14ec8ce553627981b56cc59a7135dd6e2a760e706c2bf4a237660f36ee89883383689a04fe65de874dba639867b7a1823ce75f48e6b9482229f574ab3b4b3c4a6b5f5ed1def7526e41f727798e562caac6f67c2b45db3c4139aabccdee533d648f7e13c3fe3b0e5fd5a06344e6908ae15b5053e987177cecd1badb79aeb1eb36762b1ba219f273c75ae4337ca46df5b36257e7fbccdaa7bd15316ed5f64f35edd07fe1feef970b09576a0d90484e8a769615bcbf3ae25348a1c7caa43cb96edd7594294c5509b248261e73b4eb408e9d484e4604ab7cb1d34c1b5a5f3ac9998bf97327147982cfe7c42e737749c22a3fbb434a1c5e23bf9a52f8c9d1a51f376a746c8dfc6da80b5824a1140dfe98176665399644f5e885e9bc597668356ecbfc72bd1597ab128b3980d5936843f2f0ca91625f532d84de5a785a8209630de717267c18c74aaae1529af68f9937aaad664413d58f4c6c7c7cea1436f06516f17f172908da5662674baf90ca05e2fea96340287efb4ad192b7650acd1219d84350f37398d6f7171d08bc2f847b5297efc2a35e8eda55cd39627ed299e86919f439f3c85cfac9d24a4a22fac7b7a3d6bcf8f34a4c7abda9cab681ddc033a68d18e9aac0e0decc368e0398da5b435098319add2d6dfb3d22a26e70b0da089970e59692853eb9106db5d3ef38f5934d4123bacb06e295c25c8e9d82a2821ae01d51ab64e5f929432ba391b7fa379fd465349b69e74900c79942a7b79799b4c3134057fafe95165bef64c31c2b19f6242be5c3162988762a8cae3d66f75bfdefbe561bd7fcdf0c7254fbd407bc7654f4ad79d1f13a4511717d4a38da77e3ac57cb398fc8562a8442476c811b3a92ec964ef6a5ea5ba099d356af7b25c3595f3d4f9922f9563830ac96766bc5b7d613a8cb0339b905e50c44b1ba892658abd2d2c372f583b31fdd25a939bb0071e45735c32c50aaf0667b67f16806e8989296e05b01a0e81b99733ccbd7e10213c8b6fc9532d52383688937ad949609a8a79d2bbd878093e8432f524edb6c08cc2438cdbb211af43dc9bda7324628b1e0cb09364b2f745284792e9c2a6e9097960ede5e635bd725c2004d39fac4b9b5dee265b0c69d92dfe1f6ab304ddd25842d5bc44c984efe06f3aa89e367836c453881b7ae4aa076451854fd1786d3ac23b2b8dcbc26fd9a53645bc4fd5d4fa96506825fe27e00feff4af9034f490da0d9352b37d08fa974f30278d93daa9a4c2fed46c9b77fa653075e597bac0726ef7ce18e44028e4021476e89af6ad636932425c10448f1f1f53df931fe5b830f5b27b2d5b4a0fc9256a9e70829d6b47591fe647156aebc5508f76782dea7da721513750358d29e3679bbd2facebe520b173b53068cb3e7fd9c5527f45f78e47a5cdde2130b4b5a07fa88bb56da824979af648d051a7f5859b2444e10f1295be40c2a32e930e29ea312ede05788ebc51c2aef292eb4630798cc864b54aec793ec7d29e5acbef6a3fe0338024c53946558e64904b5d2efba4dca242d6d86e91446a8ff953dfd6f10aaba621698188b7459294e65d006b27bc2b6a30bd9f4c4cb1d60833b7c3f4cc0de7a0314fe1d084be2ac5a3966ab48e2c3776ea02712a473244f396511644bff7fc88cd85eb7e9dc58f8fcbfe1c2a4bd283e2091d185f111f5a34a6d357683bb5f6d36e1df12a53d42e124ef9b7d5412f13a036adab6fb773b5d42caa1c2fc8456fd91544d1bf8d664ae7f74626f7d63a7816ecb0668346ba7eb57d79a21b39b0e9abedb8edd8fab8044f029d1b4d1c9b83ef848b0b66d37cebb80268537dcee2543bd748cb9b75640a28ba60e7daf1a6dd4cf916f11c5c0f6ad6f5b2cc0d8af21d24eb7a1decfc4d3066bc200749e7b92353f6a66f2e49a695b2e55f9c637966ce5a4a8ceebb36429ebbd12b18c7866e84d1d453782fc79509e1176dd1413b9b8e3a7a003d5df7b4f91eddd3667b38d2736497866e06cd441535eb739e5d2f31fb6d75d1290863f98eee648eabb8179bf64983e62b150efb55b99e7cf484bad828b2f84b26f6e7f271ec2184230e4db93759bef5aedcd20bf7ea173222331e6a2c6984be9252d4994a72d9b00796d094b8097568c257af1c82323ae69ad264618e98e3d491c713d0d758e272723ce46e2325c37ae0be5e775a2351b80e733c85df6ab834b55796a64c33bd5df4c4aa7c46abf21db62adfe189a56c7ebed54cfdd448ebce86fa73f30db89f645a3a20fcec45dc9be55beaf76a8babd46e7a5b5a484ab7add95ffd0a4fbf25ef5f88d934420f59eecabeec742f3b01b1dae60e54b836d8c0486dfbe05788c32eff576c4bfdd2027da6d815d6ed4c7e3e3339a43febec397d44c3f6ad71b1d69cd0379246e01eca5449b677f6b63e7b2c3aeb37992de3ee94a96dab0ae663566fee28a9bd8f8352bb952808a19f05750eeb3bc26fdc1d654e01c16ada954951d48c22418e649c65b67299f215f2d648b02e2fb0cc745e6774b9c33c697c3acc0bd497ec8acfb6f04c5b3f6bc1ee4c0f1af1da70cb781cd670d0523b353b71d93503db0e75e47f77676feab3a5c3df27963b9f4ff0d3cad6e701763461caf92ad4078c2711470b75123e1e013e110f935a46815bba62ad4e454d7f0a2360b99ba35a9ad36236bddbb4cd494d25d78b6f69a5a21a9e5e98ccc7dff9b4c9f0575fd2c426929a6183bf60e5e32fb684fd5b0e768c4e9b3b7359936fc94ff2a68efc6eeccc65e82eeb5c96f54f5397bb6924f4b787792ded7f37da75b41dbac3d4d56e6f8befe4e2890da6a936ebd22aee9af5da816d985f849fe25bf4773e9aec1d4daf1ca51c2a787cb0a3596675754838107f61647f50d81fe7bcb294aaab053b5c82b10715c9d5c8de5848f6448aa267480f65beacc56525eaa5e474c1d87dd20c19a204d5e8a0f681117f07a721715c49cfce1bdb6469a7a17f4daf43a6f79eb62b9249e8be35e4b0fd8775bd77b6b92a9225885fd162c6bbf62bb5012a53457d3bcc5d363d157b6ea142044821267a288a6cd1655dda5e5b343e3b8ab07f49d15f22ca574bb515449aa74e4425fdd27132badc5531f453d1658de54d70d92ba951f915065d806ae726c5e7a3897e9a349881f14710fdd246a478f0c7121e696353e4fc7184bcfdfd773e28f9a0cf87ed8bdf5952b204e66e8ad4648da7a2ff57227a6fc052cf97a28d785c36499b5299baae459883cadc7b9ac5bd24e16c3fed21153c5eb6330a664ff21497ce1954d1dfa5368494b86b99ab95ad354984f7f09ed74dcb709d5ec67b14f1f4a2ef78f49783dd2bee9becabef5fd66948ba8d325ce92f5f4d4ee47d895aa3a632ef217c2a0675251de67702bcf7383fe2987bd8d8e57662ef1af7081b8da8d532bb2bfb06af1509bfcef7e862360fc959f82dad50786b197e7bc48cf5b51473c7f7f38b863ab8a9725c8c083fc773ab8252cea5a945684b39df4f84f0fe4eccdb5d5770d1715cf0f97513923a77acc2bf55e57e73e443bd6c2c3a5a90498dbd29f41ddfb33fe1267a6cf610d988347e1dfb7a663a9e57f4f7247c4ef52ec23bb0ef60f7040dd5ee6b0d6b20fcacdf7bba846d34e1dd8c1a8dbab35bfcf08e7e1392f0eee21b6fe698989d838c02948cd3d7870827e94f3e361b9e1480fe0ad168d1a38fbdb4d4cfa705caba3d881830ec1cb6495a3d24dde916d3bf91f63914c34f924fc4a1a5a2a2562f2d0eef50e6e05358e5af2acc1850f6c5b69e65469143729b73639e5154f35a655e5b70cada545942defc3c26a7e33438a78ecb4b1af119eeb805bcaf789f2b4f2cd46ae507ef1e7e70b4e178cbe936ef6f9919c6de60afdb5e8f32a9759221192fa38cda51ec6e4445829d51f8c93910ea08b1ed87274f084f9846889fb18467d846728ed9235043263963db8867de1b287d05e648c29348c2c8f6bb9c31ddc1f43fe28c59769249bec172c696d1b53237ad5a4230bc317ca2e4fa6fd28c3a626bbd5e88f0d3984cf765bb159f7f8a145f7f8a5c6bbf502be42da82dc4fbd1fcbf6126aba2a885f73b0caa47039e6350adf3b130a8b6a0df67505dd058f64d285b4cf5601854a9893483ea9185415533d58a4175ff2f32a842e8eff0ef776550759c0c7b9e6550d1f55214e0d5dab186ffe65f6650ddef6050ddff7f9941757f41d40b1854f7ffcf32a876b9fe2bfca95b515df853b368fed4dd17f2a7caae7abd883f8535e66833bbaa0ef857f953d6b1c8fdfe15fe141dc3232606a47a317fcabab42c7f8aeea9eaaf3dc79f0a78317fca3a95a2717fc09fa2e38daa7c8e3f15f062fe54e773e733f239fed4c64efed416f41c7fea32e64f59851f66cd9f5af387fca932c7e7f8533a2bfe94d4f67d532acd9fcaaf7f3fc6a1a8fb7f843fc53cf1595ed739fc29a6d77993cb9f2af0c5bbeef82660fe14732f48b0e64fedb0e64f31e3dfb43fe64f31b1f45f8ef953ccfded6556fca98d7f993ff533c39f829a67faf337f6a7532e983f2561f9532d88bec7aca8f5e42e0b479697f0cf7064db42a8e47f9949b5fdd9009a3fc27ccd1ef0b22bfb962f95e5a68f2f50d37a3a8f6652312d30a98349c5983d6826d575999afe6efa1d2b26151d76e0bbdecd05fef43eacb31926d59eae4c2ae6edce449a4945a78afab34c2a26572f7730a9e4ccd7837f894925e9c2a462fa0d19cba4a2733af02d0e938a7966fb7098544cbf25c5bfedb14caa6e7fc0a422582695c38b98549b7deecda3f23b98543f6ff5609954d7ff4526d50f7f814935ed392655d47f8249157494786397ebf262afec309a471540f3a8e6d33caa369647e5c8e1513df8a7785413383caa299807c5acc476606ddf9fde9547b59ce151e5ec627854a7052fe251316dfcc48a47757d91358f8a717ffca73caaf57f8d4775ac0b8f6af69ff0a8fefe9fe051e5d03caad9ff091ed5b017f2a878899847b5333ce6d1af3f5af3a878899d3c2a5ee23fc7a38a38f86ff3a8e8bea6def98f785469233a795403d83eb948ed956dea26dc526cbfde8778e3ec518b7de827ff511e159d3b6db77f994745872725ff3c8f6a13e651e5d6f794a8a9d52fe251e9d35fc8a3cabbd2ae4fb7e6516df3b1f0a82efeb8e777795438362b1ed5ea0e1e95eb531e05bd109560e1516dcdf1cee8f8b5d0eb775854cb318b6a8f76bef1b60dc3a16aeb69c5a14a7e4c73a8be28bd6db3c32ccc1ef66327836a0d694aff3306153d53c30c2ae684bbed94731706d5643cd35d38cec2a07af53906151d1e33a898f08510fe050caaeecf31a8ac6788c91b29372e832a8661505576615031e3d7df680615ad0bea5ffe8c43d439e393af855907cda0baf72f30a83ae329caa57a320caa42016650ad717e11836a338f195ff70e548c8095e4d0fba864dd9e2e0caa37ab0bfce8df6d3c5fc411b14a2f8372c70caaf2119841758f615031f3911fff7ae97d522957864175ef5f62505de8f81ab02809ca4f33a8eefd1506153de7f19771195456f1ad84f27530a8f4e8771854c7690695a3207001ac4e6ea91906152ff1abedde8f180655678cc971304fc10caa3e0c836ac7d74c4dd7767b9e41d5194abe0cf4e32f30a8b6a01731a8accaf31ed4cf1f31a898f9a0e8390655f98b19549d31a3484a66c5a0ca5a8719545a5f86415560db8392d9f7c066864175ef9f665075a614fa2ed5836150dd9b7b795d71fa4c0b83eaca6354982df84b0c2aabf69803cf771706d5bd99c582cd3e5bb79842d6dc7a017feafbcddf322d76eab7df7d369819fa4c863f859f8de7f95356e59961cd9f3af44ff0a7acf423f0dfe14fb1fd1d531f017f953ff58a357fca8ffe5df7d68bf85338f61236f6a2d7ff127f8a7e2e4f5f7f9e3f65a56faf71f9535bd05fe74f59b5ffb87f8e3f65d5bea35ec89f62ca39c5b31ceb8d4680b5a6aa40f17921c2bca6922efc29abf61bfe97f9533f75f2a7ca7d9fe34f95cde45715b0ebbcb3983bd5992acd9fa235d7f38c853f55883af9539db9f119fc62fed4ad28963f351ff3a7987577f871863fb505fd2e7faa0af3a7acf45d61b2b7f0a72ceb6e1feac5fc29abf6965bf3a72ce1cade4df8fa42229547a677c97f6f980361e6547edb3d5883ddef644ee9059839f5eb7dcc42c2ec11d1e3c0dbf83bf3e1e9aea72604e8ea0b42e9b700b6ee33e79dc45c8f1dd9bb2fba2f3ef40d668ce85b1e77bf904ef346e69222f7c5e2d0de5bdda753018d6867477ed06b63efcef3157ab68d9e77b293c1329a007fbabd08c7a8c9def4cda76a6ad363ab5045e385373b7923ccd95c9dae3ee6804b6d334f19ad992aec6e62831edbec2e65cac2fe1e7946f4d82bfd707a6bdca69b847f9626b5006a41d688144502fcbd87d8bd8c39010ca7e0468a50fe793df9a83bd554cfb89663d6083e2b59c89edb6ed09ce231ac02b666c67aa53b9de78fd298a564b260f9e63067eabd0a815eb40c9f8ddb7a1a594e5780d51f9ffab502e1932f2ce7bee213263acf02c1bf582826609f429ea96f5d2b3ee302eef9109ad7624cf163d24bfea6c5e8bea8f7f2c056cc08c0cc8019cdc18fe73c7ae7fe823b91b717d51cd43db8c81f2f425e5943d253a61001b96f48a652628988ffca4944519fdaf2315f27919c7cf6a260badd62f7d0e2f4425e6e5e610e09fd8b3b3ab7a1f8643a9f7a720515a6cb50717a7f58cf913ed1157b3433346c79bdbc729de8b7f64fe9d64b0ab2309814472448b14b82544569e36c117f6c3652d9ce36da2d8ecc6042d624315ca126238410d32126ec2204df456e2d4e5fa7520cdd45f89e74b397a042810451719f120372c5a18b32aa2c6b1bcd3cdf3d9b95924d3ec5dabd2aac4bde5bf139f2389c264f71a405e9cc3875c2cfc262b8b56ad2fb3bdfe77d70ebbd498b772ee62dc1ac86019833c5e8a5d62b57d560b7d862f67979a411efe7e811a5bf2ae427c9da42cad3a4df8bf886e63b48d9dc489fdcdd98d796dfb696da043651fbd0d87aa19a9249f9ae4594930956d515a837b6d9b61051eecd88ea750a51599588da20a67d2b47d7a0711ad021f583d2b85017f44a11fe428a5a7387875d898bd259cd3c9340dc8e6d857e948b09cd5f2d397d4cdff023d63c7c12af1ced582885bcb5b9d079ab16f1952b5d08e56f90b74419c1e6ad970b6158f809a272c5229c4a4b48ef7d94b38c30dca940a68de2764a2a166177fd111724a9573eac4014ea26a00ac5885a07799d4b121a757e397336725bc8831b1af5f5522ae80e6a0b696b69349a6e45b553eebf21d3d23dad296ad347a75a6b8d20378b5bef1a4d31bf81dd4da3c9966c097a963239c8f8afe558a7fedfcb71efe5c18f236f5b9eabc0d619cdcc737530e770d66f83b16eb4cdd4ff88b5a33cd9d0f40b4a19fc2047fa63233fb7c22b0befc44a096ac5d2906bfcd502bfbce5091adf0b3d85c21f72bad918b2f722622a1170689fa17e17f24aaf3df169d900963b8a4e139e52d2c9ac1795f1f1fb40fc959494fc8eef96351a15934d3e8675be8421e431ba9ea3ece54b347c70cad690fd379f69ec9e8ff2e3105a2814249516a68f6e35f45c8788290da5867a5fe28713fa0201b13fdb7eaa2ac074826c4e0a51a5291fdf8332482b6da004ca15b5a84eabbff6943ff1b89b5084f36e87cff6d057fdc457deae43cb1384ea409950383b475f30010d5f8f771e7e19fdb0fa9df56dce63fd9593e7127ab113a2c2ab1c64a3f03901fe1159a2831bd64d3ab8213523c5979892f28670ea5823bedb9f2d99ea683444ad23765f1c56d68f2d77f261c2530fe59692506efc857f60134f2afa8e5fa81d858a45553e4ad96364a8fa09e1bc180a5ad1f2443f5229dcebb3bb34928d21f42053f625a56eda51adca75f761a67ab75409e9482e4a6174a2024964af5605ec30e2bd5b093fa17a6229be93a87b97daabed4b85ea1d91de32c2afcd9998ac0af0886c0b4972d6579244c3eae3ce2db995b99ab48f5cda720db54282821e294eee8a5edcb378b2fb1a1bb2a0a5d2b448faae0d4f5f45f20ca55308c3c1df90f2fd7ba825cfa051f30b57778735040f1948334f49fecc3364f8f3a5d784bc920da33653094f616d982af32ec0e1c76dc7312ae3ee21431384ce955ec371f279ca2613e24987e80d9a7a88e11e4f69730d6279c2d3570b7947371c2ea01aeb11de3779dc66fc7bb3f406c96bcc533655a25499be52c03b5ad098230d11f0127294b678df650dcfa0a985780a78fa99b6bc7e05473bf75d5e2fad06ffebe9723db98d941ffc8294da7ac230ba9ef0de365e54e613bb59b9229818dfed3bd58c0d06f3bbc830fa2932c47d8494ab4fa0f602e5387fc2d03dcae77281a1db4355aab372d4056418de44786fb8b6c130fa0641f81a56bfe343ad682294dd2eaaa423fa2025791829337ef6512e8576d794f8188c1791a1a40419b44f7c085feaf14524fdd20995ac371cae44a30a0c4baf104ab242a5975541c86ad528527a6524df501f8598fddca7adbd94775828957d89cee751734891b45a8634d952a7c7fc1ff2cee60993f323db9c4f6fa082bfb3933625a291788fe8685744e5dc15d8aba9f4bb02fccdb6b4a90939fa53820a8154ab86f235c1fceda8d673df9ccc01e997b50bb43a7fcab6ba4fae3f655fdd47e36f92553f2540cb7ed81f9c79ddb83f735529251323a9568b7e309a7a8bdb715ef02ed34921336449b2fcc85539a737ec88906a6bcd544035ca4d933a5de1cfcf95868c405b8b756a2aad8597abd667349aa9ac1684f75f4d0a3a9a218d9e8dbcb49b8a4dbdc4ed84dff95293084becebba51e8ffe93de99344d44feb719c2d8dee2e0f4a937c97d74f4b6d90f0a44f9af07ed2c4619ed45861c6bb49f72b70dc076e2e904fa3d6dc66eca735b9434e83d57ca9bf33126669fc0c19953e52d287a42657dbe9858ff86e5ae829ce55c2fc0609366d3354d623a5d315783eabd1b45c7a1ffd5c65c86da44f4c40bb2b744c2e56b7e0dda7335aa0cf9608f4898d78f7e98c6a01e6ae2a0fed455e05de9a91fbdc84642bfe424019b40ba54ca6320f23bd1889c76beff8185c6e23437d33626227d441465caf7a5283ced2f5aa97c188f01389705ab8fea1256c45c7a5d183d1c42ff4b26abe7dcafc5cfbb4147f13121dd727ba22530e5976dea8acd5a2dee56c0ed35be8bdab5bd07af88fcf0df128daada6dbac7234c23111ea14ff5387c1ce45dc7edeb82312a7d3e6dc72d8ba07293f6c7d8aed8c9adffacb51db0cfd651eb12346fa3d8c817df557457ce90f8d7ce98d277c436310323c0946d299957c65dc6fc46aa1b4ef48b45a240daa8219473391d27fb3ae3cf7ec6a7d9f51885adf4c18eaab884d2669601f1813a42a8f08bc8a958a403bdc870ca3dea9e946ad158b95e0c7f557aae789658687b50495d22d5e5f50890e643c5d4d9d2605e5eacafd98553eccf474db810dcad287c4d30dfa82d388aa2617ef88d248a4e006bae2951414e894e49490abafec83f4ed00e746bee1612561785845e8839da02e3c77510e2778741a79dd165bd2786b9f893cd1ae515f905d5fadc71a73a11edaf8119f3a51cf930a90c0758ba1200329ab1e22c3b6d5e8529ec1fb3b88b39a3054d5a1634618f95aa5a05386ea27089e7671058230e2f1e46d1fe53ae8ffe169b7f84ff14f31e2bc607365e9aa8b540fbe58595b4b383e38ada3f2cf84e29aa2b2c905fa822ac82b7581747635260551bdf6a215db0f6c3078ff4c3cd9f074f5699da391d29e11e0ef6edc32f12fa990e393a484cef1595228055d1c2f14aa0c21dd09e5badd48d9f77fd0fc1ce56b358432e636a1bc2a24566c1fb5d130ea67e2f88669a5d4e633884e378d9c87d33dad9b6d1c2f7ee443d7466e90d14d206ec5dfb628af68607e4a899b11c42f1e2f16aa94eb8510632d618999981c6154dec6e66454592aed26859cd59a9509ed68762d2e25e5c017504838834e4d47be4cb7e5970f09832603e90b60c6f93d39bd2df882739bf383d5296a4d80f4c94768f6434a448ec1bf02a6fc8dea7b82470985b3f4d59e7c4d36fef66f2d496d59889475109f48142cdd5085a8d745a85ced7aca80edf4a211b8757744b7b90695c2cc69fd99f614f5d9524a7081f7749b32bd12190e3f2494ddaf809e98f9fa1b7d106e9b7fe8f4c17d61340e30e69f84fe71c31544b94ad03f745f14419fb9ee0a32b949dadb82dbde29d1ad574bfc31239ed29d9c62ef4fa59c9c426d104c913e69677aaa297a4d208c0295d0660756077c9a7f12fab38db711e52641a00781ebd5d33e93a8a975b791c9958951afab3713ea25466a768d0db55e2cc2cf8489242f60cd773219bafdacc23d9af2b59f0943469d8ad5ff094933472627e4acc86afbcc243c7199d1857a11a30b7faebd4b8c269b131718cdbff44f69b2a6b45393977f4339319a4c3ca0d69f1941b7700a3986d1a7589d53316871efbd4899f75095bbda50026d9e51a5c29a1cabf32c35e9ceb4625d4e1948f7d66f3689a438f7be98cf8904bdb72a679991458f95e1820e7d335437d231e25116b7a421a3da27c1684a3dd38c677ff83ba31f72938c6ee950a64efdcdaca7f5b7986cf451ae87f056fa9ba29e566aa5bf62467f0d89b01eda463a2bef804b2d94ab07df8672108ba89ec20152dd6db374ac2b4af123de342c2d8531b8db20a9735fa40f31f3a5054e48bf6205f22e005d3e0ea362f6e3fef82483c7fd34ea943bfa154f90a39f74d4693355b8a7df5becf908585b53023c0c413fc00c4ba51cf52bcc202a7df2f3a41b609c9827a24bab4c3fcc945678c52756176d54c63421cab17b9fcebc6c2da59f1489b87f972765f3429ee3a9ce6784ea21e6c5ea4c3d841734fe3ba25302898073ce6daee5ea61fbe04959cb3c292642f82d76c56e29816dae52ac73f3489887439f2b823e37edac7b5248a00c9f430a73f3f555f0147923989f475412b9a9d4a6bb282837c55f724ae35fe24cf811013b229242da9ca4c1246168fc0a1d776a585d992b4cfbc8b50566aa9e04731640e799e6bc45f8bd86d206f47ef49b043caf3ebd73e8933e6e90226596a5260209a79c58dd2bf884107a3fc6b256c2d34de467c64fc0f03cea5485088ff7f9db943d4f2243f035149463e8730e51bea324c592d7548519ae88d63675b5006bbed33665df934859790329375c00ed008d1b550612e6734157d0b063ca982c64ead5bd39e0d80c4b7a8d6e308f5f05fda4df3365cf2c9412087d669e2ba373ef42afe88f77f180b8c510b733c45d77a123cefc634c7f67d09df5b9b9da942fd97058b3226f7e31d683f3a5f8e96834621b8f63259a7179ab8a356ae87d7e928eca30e339cef0829b067cf28ce4a73623ee5140cf0e61795a77bd34c5ff7c29731fababc5ae01e761ee21417acd6a74e8b0a9b7a4dd6443ded2d70f44230fa5046efa167aba8dd0d3d94a10f443927af330e8ed4c8557da4d1249bbbe3e043dc9101e02bdd8b8f0a75a83492cdc1ff1a569fbc2cfa67dcd9c7fa1dfe0841c23cbd34ead959275e607b036863917da5d77368f507ff52b7e732025e1c979bb9a379a44c9527593f995ed5f9dd20beb0815f4a98705fa2a19b1e35689ec6cde5958d94c92fd90b73c316580dfc091db9505f56876ae63941bfe855c54ed63a8aea6df826f2a1f4fd6f8ccbf24f4f728c26f2554fe71726734bbd4b213c6d8f3f88b16fdcc463e355388705f804fb4a0cfcea05b2df45a7e64a1b68e3f5ebbd7a710d620301f945d41b08e0a4e72965e1111cbf36ed2dfbae2bc6852f5eb1ff31bf37686137ed773f197afb02253a7f89eed480dd7023e03049f0082cf5891217c1e4efdc7d1393807f9dfe447e9b5649977813ec417c5f684beb3c8c394a28ef8c9929be44b6e90dbafd4d4c57a744caddbee463af1f1ba59d9f336c265af28936cbf0bbdb393d9002b4bc2b7d138f8a410d6b4a3f93757e338bd65fac3cee8873c8fa8b5a2d89e49328f88a4e024277d908888ce9b246bcbfd215798dadeb321f754eea6e37145cec8b2d3022e8b47548a3a4976c8f8db00dc923b16e2771d42be34b3d92cad6ae4b7e5ea0b4622652249e8832af9d7735b5c70e90ccd32226580becf4844658b9d977f24edd6cd67dc367cb60cf56385e4d3e3b9f8ac6d9efd85b61069b79a8f09f5fac93a3f7c46329577d82517fae0c32e9beaf48db042f1a3d276a37c92b8884f6f96ecd3a94d054f5aa3ab52d4d26e8f3e5e3f595aa0c56f4990b01b7d9e10ffa1735bf00d67fc5d3236e7a651ce0f514a051ebd08ffa4106942fbc7c3d26638b5e47de43c3ff7ad5c2a47dc33aec805a52653af97d9eac9b256eacdf66ed8b794ff1d9445c93fa1c2bd849bb01ba2ce4709dcc423117e7b21d96e28e84628ab1fd2f3f61ff20c30cf399f4704367c232d90217d4222ccf2379dd7f951b9876d72fd3493a98cc336509e044b791c49cb793744607e51e3ace2cc7d98c12f305c7582391225fa0db9912305ca8db548b9f02aa11c8360862c26fc8c7a59029da2f4ea05580d92c45a21a4aa4e99ac6f4c44a635c2fb2609f950dae482ce1e33d45712443df6fb7763ee455873c8a07dae7af2f17ba6b5627df505fe5ae1f55c8dfae6a66bb2f379b82e615e510ae38e71e23f3cc85ca366f2d81bb5a5d83e65f2f2632647d19d1de4a163267b515d2dcc93741760a652508724e798f61b5c24519b0aebda975cd3df9985661fb29c4783cf114df1871caaf5e2357c7ca2a8573a354788c60b927d18bd46fb5079d24c37a10059de135b87f94e88c350ef08117e27ed952ebadd193274172a278a88a291cb07d48a1e1c48ef77374bd31912095f949a7c074e8d6614333c899e38e57ba153c2f787dbd3e74fbe8c325ad95f7b9eb68550924634dccf2da8d247a8ab5b864f4f24fc6a4bd9b2b129d5bcb05cc99b714ad627615b8779f4c2303e055dc3148ca6bf9f89dd48e1d05e69de99bd7f61ce6685de1a6209b5c7b1402edd1b115b3efd176afc4e9f39a30f875718f72ee58f11e3f987bf52c8272c3960c24ed29b7a46b4d2fe0c7b174fd2dc3476cd2712bc289f65395df3b9254ce1d088b644281ce17fc0bcf29f02143de16e9ac2a911fd344de10cf7cb1532f8bfcafdeed76113ee6e993fa1f6eb08f7075f2f716ffc3ac1bd2d351912418b279acd9f01ae01867b9bcde9806f017b7ccce6fd80d3800f01f75566f32c90aac5d1514b23c2e511b1b131b11e3668aeafdfa420f57cb9dfd2f88858b9ffe2b0b885f26931e11188bd2ceeb40b7618278f5e1a1d2f4f088b8d873b08149bb02cfe85fe16c7c42c43366c3c094b17862d0d5f0c295b05c25a1a131b210f4f58b26c9cdc33cede06bd1e961017211fbad273e8b0518b57be2cf79bfe7a87099c516c44d45ccfa1c3c3e70ff28c1b2c9f68e5f3f75cece92c44fe9ef320cfc8c12fcbffd019c7e03204d1f5cd9593df7ab1bd457e37fd8fdd77bdf6c7ee455718b98f95475859c6caef587995957758f98895a892916256bab052ceca311cb30fc73c9995d359b98c952b5999c9ca35ac3cc23197b1f20c2bef70cc8f38e6668e195575358b396607d6ecc2ca411cf3508e790cc7ecc3314fe698a773ccb338e6508e7921c7bcacaa6b7daee4989339e64c8ed9e17b363e560ee2988772cc6338661f8e7932c73c9d639ec53187b272212b9339e64c56ae61e5e71cf3115696b1f22ac75cc331dfe1981f71cccd1c3362f55ccc4a39c73c88631eca318fe1987d38e6c91cf3748e7916c71cca312f64e532566672cc6b38e68d1c7364fc503996fed3e5f1114b96c5c486c54647c461fb6172c67d382b95ac1cc1ca91ac1cc5cad18c8ceb882f2e6c0574c7d0f145c7418f1c47d726b8b3f18675f80b8b8d4a5812b1343eeed5d888f884d8a5f215618b13222cfec32cfe873fe7dfda6364189bbf30367f616cfec2d8fc8559f2d711cfdb2fce1f1b4f1c1b4f1c1b4f1c1b4f9c259e31ac1ccbca614359398ca98f3196746676d6abfcf5b0c58b23626977365c3c1b2e9e09f77e446c0c76981c161bfe4a62742ce4105ba1d830b91cdbcf60aa282c3c3c36220ef21cb78cb17f3b3e8c7a4fbe2c861efdc01cc5daab17c72c085bdce910cfdacf5c181b11166e653f94b57f5166e10275a0dd411d681937f4d5c86538dd2e95f86a646cd89208ab6ca038365c577f0885b1e9f9272ca5e2a36396feb116807f369eb0e1bf17ae6b005007c6ff08568e64e528568e66cb31fc45f98b43716cf838367c1c1b3e8e0d1f67093f86956359396c282b8731f5c5c613cfc613cfc613cfc6b36f0c3bceb2b29995f2b1ecf3cacac9ac3c3d8e7d7e5973fba83f960ba099dfa3db83a997c8b084c5f1f2c888786aa19579714c58b8b57b5c3c4c9b687334a840142850f4d2b8f8d804baca69fb25d17161ec54af3332b882e5833c17270c86c9d638b9951f9c00d42e15b32202ab7458bcdc73e898c52b87c817472c1d87e7452f638d1e475bbe0cadc0d88587c5878d63e75f717444e33c8785bf1c09b1c5e33bb7b94397b013404e5abf970fba60ff5e465e947a97f43b2b0ffd777ef5dff955d7f9ce7fe7575dcdffb7cfafa09318af1cb6c4cfaa3b7a33215e1e13295f12b124267695bd0dd38dbc3dfbedd75553a732fe954b42acfcc7ad8a7b376225ac89293c1e87cb17ac92d3ab5acfc5e1f2c4e8f885782d8997a15dfaa317a41bb4342e61190ceef1b88f5a158763c3718c93870d9de88957b861c358391c4b0f4e8cffe225628480632db436f078ff66225617f9cf78e6ff659f2b4733edf9841d57a7b3e6dbe3197994959b2730f229eb6f086b064d41048007e0f785fa78ed732404880024400c90006c00b6003bf0630fe806e80e70406bf88e207b407829c89e209d00ce702f03b8f45d835c01bd006e0077406f401f405f801ce001e8075000fa033c0103000301830083015e8097002f035e010c01bc0a180a1806180e50024600460246014603c600c602c601c60326002602bc01af017c20af2ac8e72490af83f405e907d21fcaaa064c060400a600de004c054c0304829f3701d3016f0166403dbc0d7226b805411cc1801030cf02cc06cc01f35cc03cc07cc03b60f72e201410065800a000e18008708f0444011602a2c16e11e03dc062c012c052400c6019b82f07c402e200f160970058014804ac84165e05781ff001e0434012e023874de86340322005900a4803a443180d2003a085f832013a4016201b9003c805ac06e401f05f3e60edd04d480f5807f7eb011b0005808d804d10d7664021600b602b601b603ba008b003b013fc7d02d805d80dd803d80bf6fb009f02f603fe06f80cf077c001c0ff003e077f5f000e020c802f0187c0fe30c0083802f80a7014500cf81a700c500228059401ca01c70127002701a700a70115803380b38073806f00df02ce032e002e02be035c025c067c0fb8d2174f8dd6c0f4620daa065c05fc003574adef26f423dcff04b80eb8016df033c81af07f13700b6002dc06d402ea00f5803b80bb807b805f00f7010f000f01bf021e011a00ff00fc06780c68043c0134019a012d80a780678056401ba01d60ee4b770033d8aee46d46f49fce9a67e27f57e69bcdb7008f00ed00bb77cce65e8041803180c9805980858095804c4011e0734009e05bc035c01dc01380e85db3d9193000300ae00f08064402560032003e8039805840166023600fe04bc071c077801ac043402bc026d46c76018c02a48799cdeb00fe703f00100c88047c04580dd802d80f3802380db8021801fe6f837c0c10c07d4f802f6026a03f201c100f380eb80ab8076805382f8034001300c1808580f701ab01fb016580ab80fb00310579063c0608c2cde63e20c70166002201a9808d80cf0127018f001700556cb8db009f08b33910f00e6001600d600fe021a05b24e40550c2ba79c37d157b8fdd4bc07c1950071812056503b9089001780ab001bb5e518cdf87567162390fecb30047001bc1ee6064a77b1dd88d8a60b08005b643c883d5a37eac54d012af58de4d8c0e87e981f744f948f98001722bab0913e56318dfb05859b600e6161e13e541aa1933df7d7be69bd3df1dfe3bf6c3de1d69190f3dfa052c85057774b87c59586c74fcaa7eacfd8a776171b5e2dd0509911e1303836042c35eafe2d45f9d1e1bb328828235fef0a1c3c6befa5ec4d2f0d855f111ef26c6c4be17b72c8c8ae8b07a252e3e6c6978d8e298a5701bfedeab8ba317bc1a1e1b0d8bb6b857f14f03432836de4130b3f10c1fccbcdca7f345bf09c1cb3b58a825ca3dfafdc7d28f8c8d88888d8f897b3506e6634be36357e14cf883e58c996fbe6d29e70be65d9d59b49a4fad9c1916f7de7f385ff110651c5b33092b67c62c9b1e1b1d831b07d7937c71745cfcd480b767be1bf0f6bb7ed3a6cf9c3d48be6ca56fc4e2b05511e1383353c17d6ec2cae971b101e1f3e583e58351bf95a0ab803eacf45fd5691ec002db6133cf0a4300a11f30580198c722f0fd4e33be9fc7bacfb1c2ba0fb14ec345f0787cb804ec25e45ca23fb9c87ff312ff2f5f2fa740df9fcbc0dbea1ee36172e7bd2fb8cd63dd83412e60ef3f0379f02fc03a5e2e4ea633d23d15fab174c66c01d76f95555c07b2baba796533725176a7dde7d97f9c3637fd399cf47f4a61ec9c53193fad6cfa8b21ed2f01ed0087d4cef4b1bf6556693e62efb1fdf3b37dcbd5fa5a5733dfa7abb9fdb580407ffa2e7a69242d035581b45c1ac6bc271b3a6cb872c4c851a3c78c0d5b408547445a4276daab26bdeeebe7df61cf4a39e71acab9e481618172ecef9580a591f817d35572265c5723026fb4f4f30ca725d48cb9d5dc6c7e64ae31979993cdc88cda512b6a468f500d2ab3ac55201ff2d68b277628524cafdcd87ee1f219c1f53d576dc253bfb97cef1f577a7806dc7e65d48837df3e70e13dcfd71a373ef9febd59c41eedb1b2906bbb53f7ec3c347c58fd15c13f4cc3bf1fbedcfdb2e2c7e10f03d6cc19ebbd53fcf6b3f620c7fca7635ddfa73c5f1f56f6d98425b76efc38ffbb27860f3f2bbafd75f887fedf5d0ff2967bf55f6050576f54bcb6fbeed8dd03ef7dbccfebb5b9b6ff9892b2fb81cb8da8a3ebc6fafdbdcf1717d7bdb6bcd4f0a96ed5c2922153e7de9c76b264ddfabab3af4d9b38f1c3a3a35eb23fded67a212cdd31f1dbbfdd7e2da87cdfae401f69d889ef5f8a70bd354b66bcd4f09a4df527cb2676f9d7fcdaf96fb9d7334edbb357b299b65f11b176ca94e4d79ca69edd5c20cfe2cd083de999307cd18c0b9b875f18346defedf9134fcd1f9fbd6ded9cc5332e4ccf6e1834d13e426697909291b0e2ece469915b9a674dff7ae6bc886f4fe777db73a0c8d5f4897f1f8df15efb46b735c5d7736fb46c7df6cb47fbdf7a7d246f34f1c5a8c5ca98d33fe7dc4d3eab3eaadfa76a9efeccf3bd8ae5ef954ea8dc73d11cd077e5f1ef3b7452fe68eba1652fedfbfba9add15df554507ad66df5088eee22f9d725ae5c3be4b3c5ee393b7446f8bc5d11ef793babeb75564e7ff3ed8059208774719dfefaea2f60ae738c4191d53dc6742bf31eb82f61cd47409e64efbb1d84f9e35f8075bc5cf43fc4c88fc0df975f32660bb87ec758c5e55cdcd56d2d6bbe6c65eff2f51fa7cd4dff28277d6fd66e259bee3bacbc7214fa43482712107bb0337decefaa55fab3d8f4b13dddd87fd29f747a18842fae77b93cd3a1eb25e8b8580b157d112fb818f749f4c57bc1855de57fa44cff87ae5ddf9acd185f03b6b1083fd769c6f7db58f72d56d87301d6638f3afa4ee66516c1bed32298774c5e42f6fda8a0b38fc5575923fb5ef909fb7eb3a9abfbffd72f8295dd94e7ff36a2c792bafb3784c8f67f3547ffbdfe7bfdf7faeff5dfebff4fd71d765cb5c8668e143777952e1c398823c770e4648e9cc5910b397225476672e4468edcc7914738f20c475ee5c83b1cd9cc91e296aed285230771e4188e9ccc91b338722147aee4c84c8edcc891fb38f208479ee1c8ab1c7987239b3952fcb4ab74e1c8411c3986232773e42c8e5cc8912b393293233772e43e8e3cc2916738f22a47dee1c8668e143feb2a5d387210478ee1c8c91c398b231772e44a8ecce4c88d1cb98f238f70e4198ebcca917738b29923c5ad5da50b470ee2c8311c3999236771e4428e5cc991991cb99123f771e4118e3cc3915739f20e473673a4b8adab74e1c8411c3986232773e42c8e5cc8912b393293233772e43e8e3cc2916738f22a47dee1c8668e14b777952e1c398823c770e4648e9cc5910b397225476672e4468edcc7914738f20c475ee5c83b1cd9cc91627357e9c2918338720c474ee6c8591cb9902357726426476ee4c87d1c798423cf70e4558eb45c66f31fbdeffde7afff747cffa9ebf5ffcbddd77ec9b46b4961d7f675f995d5ab078cacf9e93f23cd5d2efc5e43f5f6eb01017f90c33f73672ecb7ba3ce9899f7463ecb6c3aca95fcc05bf6a711fdc5ebff019e470e55' + ISP_PROG = '789cedbd0d545357d63f7c6e929b1b10140d102cb14522a04ceb5851b1d53aa084285ac7fa01b6535be80511452b554b6dcb3421b9848816e905828253c40a96697dda418d152d5245ac9d765aa78afdd0a201828a1f5420a260fefbdc7b1320759e79def5bc6bfdd7bbde49d7cf7dcfd73efb9cb3f739fb9c7b2e1dbbefd6e71f5f22108186fe362c9a3469c3a2204014c614b30821363b7bc486ed8b27e9a288685d34315b379b98a39b43c4e86208b54e4dc4ea62098d4e43ccd5cd25e6e9e61171ba3862be6e3eb140b7807856f72cb150b790f8a3ee8f3b5ed9502e9ab4c167125a82d0ed7f02961040014b4440014bc440014b2440014b48a0802552a080251450c0121950c0120fa080259e40014b8601052cf1020a58e20d14b0643850c092114001661db44957ffa9e28a0cc9457ffb45fe9aa86f434854a79cf043b3885105af06f9241019d216cff6e11da36efb773d72f7d1be8ae6ca96eaf6fd1d7fbb7da8ebc8ddcffb665f8e6d8dbbbaf0c6e2cef8ee177a5feabf72b9adf5dad59b377eedece9bed7fba03f2c88f0091b37d2276cfc589fb0c79ff4090b8a1e1536eeb95161e39346853d9e312a2c48e71b36aec0376cfc6edfb0c73ff50d0baaf70f1bf79d7fd8f8cbfe618f77fa43f9d1507e34941f0de54743f940281f08e503a17c20941f03e5c740f931507e0c947f0cca3f06e51f83f28f6d08099a7227c4678a678eaf885e46a25b41b7ff34bafcd6a4f2e736f8f84da9489c9d742549f45ac56bb3375cd9204dae4c8e4d694b91be5ef97a6c665ba6e7aaea557169d7d23cdfac7e33eead6b6f0d4fdf9fbe70edcdb5c3ffbcffcf0bdfb9f90e056d3306e97ca8b1c448e358dd482a9818650cd68da25484dca8d2c9a97184af719cce970a21fc8c213a3f2a94f03786eafca93042610cd329a8f1448071bc2e809a408c364ed08da6c289478ce1ba47a8df1181c6dfe902a9c709a5f1719d927a8218637c4237869a483c6a9ca87b94fa3df198f1f7bac7e40411b401054de235367aa4b46543585427a3a1957644aacd106fd396c7dcc98c99b4012df2e9ae33fbe098e33f552412ea68225babd884909c54079833410f8c8d0ada54851462a4958bc5ffa0c56224d2676b719abe91f2676e61fe66843934f79929a4658d557f377da548c13c1afcb814404522a351401995d43e0ad701b5bc819078eedd916334331414c1d745c9cd10ab3f2d937bcf2da36704041011a9144184959c0834f6382e9c2035258dd545f498832278faeaef4b7421814612d15d8750a0518a74ea40064274ab88f5a344ba821d7f9958dcf940b58f42aa0f2844a8b3b52429d6743b0eff33ab4ebc8f924690871043aa10495cb6f0d20478f0d2307305793cf97899274853227349a3fa4041801cfe07255b97c82924aa252d51138d6e72a40fc8416a2616977c25279188b956105b92e097677db4e9a1926db260a9c46a1224dbf7cf94baad4b30c719b28351f4c60b28909261eeb9c07d553be2b94f2cdef197b59608d9212c276e4f3945f5d611237768c62398796219b5b44325e944c218755e81512693f811648db6fb657420e5ec5bdfe803ff5c0a28f9eafb25ba505c33bdf13cd40a6d8a0d6420b4ae15b1fe50eb7ba3ff72a82863480bb81e65ce21468a7bf4cbbacd19a11dfd09d5864deae0dbd22e956f275224834e484e7e648ee2f46d5f6bcb1835ff1cb4f719e129b14aa70e119eb51f7faa1e2be86b7364038ee79e83220bd41384e7c46945ea279c3a3d7587fa4967fe29bbd4539df9a7281339dd9b6eefa8e7b4708fd87e6d37af8f227bc739fe89b45fe3fba4ea5a19ed01bd77e12b55252572b611741bd252bdb8bc91f6365d2c1f13369f8835a9b9d849f636be97b53f55ac176dbc924ca8d5d0d31384f61c2f9e273ca11d3a2c29dff6f795e95ce9a7ec5770fd7b18aaf9435e2289fdca0f9a220b3132b8bd48136e98843c19861f4fe41c4ff477bd4687f098aa50179a9d8c47961829273b91b44390a54facee841ca409a76dce3069545e9da8348124f600651318c4daed2382c915dba45dc1b727b64c6e568dd9ede4fe73454af6caf1b28939056a417fbe266384b47b44887518f300b7946f273192055bc0bac67197b871ef50f939f96acff3bae9e4aa3dedd44acc2388fc9ff0883a3b9447d4c9c13cb4fdff131ee89ba13cd0f1c13c9074b0e5447de5b427217ccc9977454645f3c2aec5b7e33b5e687fa925a725b42fee6eb569e296c906d613f9c8872191f92dd094bcbf9ef0e8a84c94265fd9387b73c566d19b5756cf5e53b1865027010fd6de3b6286f15ed41123bda10555184b0d141a53166aa2379622b9074cb99661e82cb39009947aa06c33abf010e9d85ac3b8e85a8fa788593b2ff7adb08cc9587c57be532a8aef7ae1f62bedf217ae118a17c1debc16f47aac29585dd17ea67961df4b1d391d952de18689a6bf990ee5bdb23370586070e9962d63e9f73d505b622cac73d2b42b9b67bf59f1a6e8ad2b6b66a713ea102c1b8c6aa9c103e6d948829520519186be9929d68779a22be66ce3360ddfd312a1a70d48ae3070bdfd43becaaf1355e6c481f68c77f6f86ed62c01d943ca66eda835ed887adef2f1bf29df9fa092f25c8ad46beb58ea13f1ac93bc9d867d5248633b116ba84f4c9a5ab30d295e8750eca18ff02cde887ec8e7c3a11f934238f8942eacec0b5575fc88d09c338c53a6e3ff75d0027c87cd3a590a35cf3ac573d7543bb987550fe67ebe7228f7d82a2777cc9ba57c8255d53dc3d598a3d429a971ef769851f83553b3d70cebabbe6159c5aed85a851dd5161f42e7f2cdc04b7f2aa76286a2499053f9852a3c7ec46edce2e14325ab2a1fe0965ace73dbb4ebb7dc2a77f1dc785e2b473c696162611d47a968cfc548e3e11bfd09728a9aac6b9537d81dd95a7d2c890ee6ef8e0d3caf404c5e2d753baa37ffd3fcfe69bab0fe1e92ec9f7ab9eea98cb8db911e32147c37b42b421242541bc85816661a0ff5c20e69df1e8a40faa70c4898c7ff01fe864fb704cf3d2cf925515924f4f5eba514ac5e5f5122e58240a302652b746cc0dfbf31abb4552860417d6a3d4577db10f4e270e5b757d6cfde58b151b4f94a1aa11e8be7ae04abd9f6c037d9424950012537c6a04a33bdb30df9b62b5b2cb0327e5ea8fa4882ae5a94ed012d0d75d8827dbca51dc1ed78ed093708569b6c72e962d0aa6086f77d82b69592248210175fbe52989592ae24f2336a35031e7cecd8580fb5620eee3ff1197da31f123792a882fa21bfd468fb952da6c4f5eff969e8d4a6d98a04b03b4a73823587a223e6b239cf6754dcaeec38d39ed3f54dcbd9e6f8be17eebed4f5caed951dabdbd7b51c314f364d34541bb28de28622184f06911a3c4be91bf7a22f284c0fa22bd4973086f4463bda96c094974479e48dbc25efb68f1066641311529030b6b52c8ab585a1b2e3ba84a9fb94d1a6bcce9f76abe5a441686be265b16629d26b621011124cadc8d76b92317fa01b813fd611da50253225ec41bda872bd74635b726c5a659a74f5953767bf55f196e8ed2be9b3d756ac4d81bed06bbe47fa9893d0f2867c7d4c0bfa42826917ba22b9b85537eeb3047a5797b37f3f12ea36132159bd7b2d7a4d215893014ae27a3f40e2980320c30ff9af48566c25c631a911a985a85f91953fda969519f2bea5c701334e1b62120e5aa2d7ec5ea39b27f7baef60b7de77906b676d61e298b535990682b9556b9c124d5f4d153b474f5b4e848cae2f4a18532784f3f13ab5cc519f5a6b8a249e29b3b6eee8eb3c569359486cb37165af640e942d214236770a33d556bedceed4cb969acc18e2b21de7b65eaeea77e64e2c2042a2fb85b69af8dcb5a6678819866d28ad0e8f8135976ad1379e037d6940782c2aa81a9b86109f6945b33d6b52ef417c377ac53362f53d448497925b847e2b3ff5efc6ca5a56d5827b531f6340cc6a7e243e40fd8f289661af3b64173f26078431a9b12f257eb0d56c7e81a0b77bc876af899644afe1fbd1bc02e6936d4ffc854cb776cfbb589fdaaf68cdafb7d5524f47d397533d9e8835033f9609d9493a7d95a250a1ed415ba0bd8f92ede6db0e0758d5662284d1583dbb1fb045b0321544afac70e5d4becbf7fcb1b28c3a41be42e9c6caf5ff4ac3f8194a7fb2c65e487c769d93a53d9374ca92f29e5396c4ad5003577b5016c8124036f321edfaa1b28c1d24cb7166776a511dee0fdd7dccd9da5ad53921f626e36a9fd1c9b37c03d7be1f85f6ad19ca33942181abd03e6dadc74ca2565286361d13c358eb1b1b10376a8dadfca835760ba3365263dd693b9b3b4fe8c1da1d9f656ba347b2649464a41af650bc3f6b62c945123fc173d5e6b26490c4e9eb961b2b12597292e409d8a52922b85ecad99c81bdb6d08eb8764127eff379a3b6091c567a086d2b2fc84866494990732613eacb72b6bd3c1fa7e01c63639dbb44ed5b0a351e31324be0fe54a45486e4d21c914ecdeff05a9f11521e33a7726bf2337c89d6b714a932a48ab0cf147a7eaa4eed9424d10cabd74ac89d237d5a2185dcd2533b4dae5464e639a8df14fa69b22219ea94c444b292a07e451aa47998a679ad625a958b6efca25cd4ff139ed1b1efa092142033aef5036a9a2095f7e53a3dccd271cc2484bd8c3d12c2391f7ec0ad0338f7416ab230929ecfc2aab187da8df0aa46421f810fcdf77c0af8cf30f7c10ece8bd74f6b3fdef7c2decf8bf66915c9c94fbc05be7d72f2b87745228c10978fbe2fa73ef1b606b43e10d2efc9c96fbd1b2c9b33f0a849bbaa0d444c29ac67b4cd82f83d03f6a46b8c06246ea0d0c4165222c8d65b917219a4937684b74c6c16e2ecd929c129ce71c712f2758a7bb05c65d174602bd2d9184d59fdd87daa7212f526cc6260a7a9a18d36648aa5df6d02dfaadf52aa20092c636913156df5697d60d2d483cf71dc9bf6ef4625f5e0fb795db5b0d26fbdfb13ac23cff5e1984fbc53ea9e99136ea4334e88d84c3baab71ebbccbe2e753cb3e548b197d5cb164d59d7363d187905da213d11b5458acfc88a8b53b595484559d01ea905a9007b48a0803d12a0803d62a0803d22a0803d0450c01e0414007e6877f708b69841e7b6b1c524c1fd2b3ab7ed790b5b1c86f83a72b26ddad07bc5da7ac598fc59c5d68c137777599ecfa8eca8364c642a606f93d325e8d470b386f3cb967ddecc683e6f510654a4ccea3069667d7b746e781f494ebc1bdea55d556073cd61010b646013415c4f1fd28749863179ba5b7af059593b15b4c32627259e7ceaf16ff40a0921378789e96729e4910a3e6c4017c225366766297af355e3bb3c439949b20ae78c34ba7fe9c8cb1fc6fc773c83feb958bd40c63f6bf7e3d53994694bc66bb16073f66a269716e6cdce6a2343891bed8eb58e08a62a2acb0fcf31c1cd52f0fa550107916a7403ec5349a49a02184f7aaa66020d2245aa71a4686cac0e3c06c653f094ae98159cb7d9a5ece39f52ed339b85a7be992dfc93d181eece6cc7b28a9239ad57715a7fc4ecc7ad1cbebc54739ee1670bb247483f2a84bbf9b0b8810f335dcef242f88e907ec2d906613cfa67ced57d25d4c8db593fbf1bb7dd9dd9214b863dc9b8aec52c6558a29adeb518efef5493248b85b2b764abc8d8bdc22eaefca62cdd23f64b2174fc06a399793ac5a25cadb3e934d85af44d24313aa3e2ee99db67db73fa2abbbee938dff263f34b7dafdc5dd9b5faf6ba8edd7993b74c34849bce3596ad6d68c4a3a77fca13c9ed61881e45f9c088c6d15a2a8050ebe613f369346cbcc78d17723e54bf24f89d89b9b437194a92b59e9ba2e3b7d21bfe44bcb05596a84cf75d44775585c6c41c2ca3bf35845d62427384fcef29132bc0f5a9342b9b8598503a80f4a9ddb2338a54d33b3c114e15cef3c6655dd5477a1295a511c34aa36a0ce628d5f49de860df87e50379507b7fc293e79ed8a73fe281f4074ca8dea3ff42650ebda114fd983333d1eb0d5d687c4e1c936da6e3cb3d31afea9da6ad58ab54d3abd1e65e9da66d7decc6ca8dd2cd6d69b1ab2b574bd75c796bf6db156f8bb2aeac9dc578807e460e435a5d9c2e463e7fd8f115165c8bf818ae2526a6d268f5f07e2086fe9225b2af534174b6d4875ebb4b06cf73bf3c2359e4950e2baac87b5ba93709a33a065d33d79e2c14d3dd5d880ca995ec25e49954d48a1395b825bcb7f6a85feba71a55502fe22cc84649b0ddf5277c56876560e75f7710cfcae7df77782f302d9867f14a5f97132e9c5a1ecfc05254a4ca33ed16792a255305b4206587e4db9be65ac381686573ccc96bc66b067a433fba669425ceca39cfcc124a6a372f8fa9481df3fef2930189476044b8dede19e1bdcbd5db3af03443739c320665551b57747aa53b7b3f3198b776bb1dcb6ab228d3e9d62a0463ffd64511bd6117ba660885da04295f7b316686d11ca54c5c7e128ffe73f707463148fafc77132cd78cf49dbf123f1a6b1b9b44d361ecf0c8d5e6348944e66a83754e794feb417d2df47fad0911ea7a8fa2da52cf40945143cfff19e1917e891b67c839bffcc7ac63afe45c12ea8d5af362cc1543add118fde24965e2155c732d3eef08ed1b38ef88bbbbb02b7ccbfe2dac845c4cc490f3fbff64ea08667e7bd281cf39c49f81857c6840fd4fd1df7b2043e20f75fa040f827eeb3ed22b2610b4319338c32c66b277ce831d01acf3c32ed78dc9c8e9a868af6cc1e7178befe2d38d973a5e695fd9c21a37c1bad52ca2dfb48bd82eca61c167cf30efd15d66d1a71a653da17ed88946453a77a2214122ebcd037de126e74e8fa5d63f667e1e6610d3bcb174b359c2364d41ba30bc430d5428d0706aa119a5b252ca41fb7f8fd0aba6307ab851a24c0e48f75dd5595a612eb9e97bbd24559eaa4025b6a472795321ec606f8b89af0a3474ab4dc63625a352a351ecc68da41c596adae3000a48ce288d909aa3e8c50d9efd914c98f8808788f77699d1b4e8804f40727fac90e3e75332d6e68b942d35d24264867da6ea505300473f6ff26753fdd1d69b5baf95b5059b7dac4ead3bbe146667e2dcb1efb02ca894b2907b617d8f22691becb39b4e826436e92e0bc4f8f1315d10934915d4b1d2a8652cd54c595bdbbad8a67144a9b1917aa6ce379d9e2b45ca5585b7caaed5502d6882659a616a9d391e24f8f4900f473f3b341ce709362b53599b02296dbb80d7edc7c4874d88bed2e6efad27c202e3c3a2c8302616f7464df129ae476adaa48402977fe6bccc9796435ffb74f8b623abf6666013e49e1321b722b10578bc922aad793d15ed68eb2c958af1534158eba5ac8503bd1bad58687e3a7716239e924bd0a325a3c910f6f5a9285ad67a8fed8944f42ea912f36135771cbdf978afae9a6217e118a9984e869564b458099642880fe421f6cd930edab61345187746f56be8af6de1fc8a6bcc53a4e23d3ea5d5d2be747f42c4d63f11a1854f6fa177f504c8251251856b5e4accea4f08cfa3477407d11b19822e9d31d69c2e43ecd68eade22a2f34f67dce032db76da5fdd315ac6281987e8ff2d75b3c88f84279b22fd2daf87a8c7a3de40ea0e9d7ed08c7805f9dcda7a46643ec7abb928b9d69d72eab96c79b51412c7df994848d4f45a53939e2c01c7f14e8ef8f864b1716cb49a9c3e7153aa0414a6a4acf8746d5f83721aee7713f4c6afa55d6e1d38eaea29b1907bf8ba55b4fa152e921b21efc3fac2fa7101bdf081cdba45938c68f8fb143cceb54469d9c047d9182bea435dc65e3c388d29c53d4f3169fd5b4b401f9aeecdc695dd3f440b64e6df14df649efdc59935b1c45ffe95131afcdca55115421aa28de7e4b798dd3e65a1b4f6b6cd7585b00f2bae5755d69537de24f3c51437f25839c652bb16e95b52957e132236b271b461f8b56f8aee98fb4bedbd6ec7dac208cd78dd64bad35d5c68b75646a16f574617f66d6d6a283fa699eb032d0c53d9e5c1dc75269f9dbf7914e434bbd50bda606e6ba59a5644ac4962af44261965f4de60728c2f35e54c49bd5e8ac6b5c9b5f060ea3bb513043935ee0a97839828b88383c4a11644a74ad514384ef5424c0d806dbd7c9573fc2dbea81d48baa29f3096bce0c6b96865e97206634aa2930f3c17897a5769646c0fc4eb79a1168da699bc465bf01633eafa1f2a23f7b9fe3176e5f65aad35bf290dcb0e541740993579faf3a701fed8dad31e6a1425be1756ba6f167961946b8d6f8a39b6a614621847903fa99b3d2cf7bceea3ec71e757831eddbc0bdc1d16b4864dd66794092fd969ac6bd68575e04d5003b885d1afa6623aa36d2eb5b3df5a748c4500cb927bfe981f59aed01cca5af6f82b482338353d89e4cd46f615f373ae439d23efa76a3545f4322fddf18a4af2309711d43e83f2745e2cf1991b88614eb0f336298eb24aa4f3291f830e3501d4840aac3764275d446a83e6912ab0e348a55875345aaa30a31d89e447594726cb26c4fda4ded2655471b1f58af832c2db69e6c2d7d67afa426a701d1ef9c13b1ea1e874ed3afa0d96ed83b44187b90782e0596dc88c2cd2a4411172d11c66ea48f8538aa1b2457a1266455f4408b749a56e1a482795621e7bccb914278011f163f3226a3a23da763a261e8baf4376674476c725be2d0552744d8979d60abb74c6e71ee032b52f4d30c0e986145ac6913c10e9338e4bda71cf4bd48315ec9e4f73d1cfc6a962a06bfa6fc1e22e2f07ab69871e70dbbd261b09aadabeda313d2a6f2678fd4419f3535c6df1125a9b4ac07c937d91cb38a6429a469ec8d8cd21a8fab88f6e991c8c96122fad487921f4b6b4c69d1bc7e66d6b05747039d015eacc111cf046f617b47a39aab69846ae61487a08fb7f6d6d167d2889257cbd694dc525ebf64985a81ef37305be80269b07e8ac45163288d121f3120f99b671c713be91c2aa0c64343d0ca1e4f96823abfe9f52f5955962e979a1cf22d124279abe4ba9cd24ae89446192b5d2f3a6ba4fb4f21656259bb32b9ac03d665316d6d94444b58a344149058620d482eb105ac2ab95e634c26b0e4dccc1569afe4258f24ca9299bc9aaba984eac01422deb97f6bf9eea07255d96d657a59174db5213c47033fb1ba4eb91e9ec56577c59130164689a4a42fa3ae023c1061947e4aaba34fa579e3b672f2321202b719cb8fe566b1dccd8d9e726abd88ce6a44205f3bc8d721c752bf8aa586122265a2f2b23259d9aa5ca5bc5a925c9393169d154be6d518a710f4faf38fd4a45d4758d61a269960dbfc51616b496a3d19cff0de84d0e7ef385bd2dc84fbec622df4c0ed80f412684d0fd71aa8479c845bc348247c8bf4d0a2b2bee72cce9d66f937cf59e8a8d51e65e925ab9c6d71b5e166a3e4a16d58db887edb86b3a627ea1e567fe743eabf3c50ffa9cb9678e7891d1dbec52a22db701e39453a7a4b2f99e9121bd23fc538d8de2f1d6776acadc13272b2b5354a39d9de69940424065c0e480e68e564a31b45720afc678a247ce942ab6f6aa12d604dc9f50ac6d95789e9c4c18055015703d2036e70725230ea1429fa12cb4949c4ca4edf8d1016fb76d75baa8dc1c6b5754e599b6bd7d69d318da9a9b97a951b1ba76ff7f131bc0f0d374c64cc8ddc3953a5f3dcae39825f0f703c6ba4f696cd2938a13c5e91284ac6b71d70a989067d510801f9b959b9bc989e47a21757559b5f6c77e6c2bb812cfe9400f603372c78efc99f13c5b52f6c9176b176caa7da30d130d974c864fa0a7333c512b18286bf3db4b4de6f1c21da58b1fe9b9cb89c8ab4d969a10c3e5912ce87c821f2f0e5dfe84fc0fb203ab01bf567f60b5c9c9231c239c6dcf67e0b2e259c818d72f6c6df9869395c8ffc952a74f6080a1fdc233676688f1023f18cc31a53af87b6541b72e9e07629cca6a31b66ce33d984335742ac80d15550e27a96ed31a2cf6eb23d1461bab95753761cf66912bc4fcb5a86dfd05470ef680af298af1e4fd77dcd5231140d33bbf3ec062d50cd944870efa8c649106bb7f4e39628d361bfc6ed148d880fefe0ce5ee4140a827e1e4fe7da602fede4d1bc9e4bb32b10d9a85ca40a9520fc7e4cf9bd329d501f7370273e764a36f527e7be73afc59447680aac018b969fecac1ba233d554b6ebacb76f700f35ea86f6d0c0c9eec17a3cea83fb467b2b1ddfe4e17791dbfec5d9117f0ab0174bb7cbcae71992f2f164753a1276902662deb3e503ed2df79cb92adcb4a3d1eb8d50bcfb5f42a2ace705ee32884d77e68b9af6b01e6f9e827bfcf1b7755fbacedd232046cacb7efc172c8b2a00c64222f164f2b85336de263e703e3517f379d1254e469eeb9ff1f9681ce8f1ec347c46aa37f3d6213753e268169fa5622b019f03e1dea2df93fa90a9d843ff21bf44ad2ae946f4b3cde8f0f3745c33f83afc49ebecb40a8ed3f4e4ac26f187204f71a458552e41f48f52d9a255b8bd05793b1a953163de9d99bee3c4cce54093bdded0c5e23ee94fd0c783b58ce94665519b34748a5d16494505915444821d91794ffca33f9335dadf0948efce2f3553043e5b0d68c69a8175506e8e14d3cb29ec83fae2f205d6b9ed8fa7abcac711c7be73efcba020dc979f59bcde206219a859043587e7584775f794457df60bd4ccd8d0f31abad026c1e5c758589b94d3c0ac654fd6e93f6308eb168fdbe2cf48e49da7fa642a31c3e3a9689d66a4b5ecb9174f5a5704ddc23bff33390b73b851d694bbce4eb2eaaa77ccf0f830aab3ce794e1dd7eeae83518d58b38879074f70bac8eb4df2bfd045de12b306344cdb8273869bfe95de36eb07463eeae5a1233f30274e3448bb600f3f0fefdbbf0c11f42b19fbd364aa2195a5ecfd11066354674968e193da2cdfc912fae5161427ac4151b4bb1ee8d4aed3a40acca3d26c7da4bbaf86d44639e3b5e5306663ba45033913df1743cefed81aaa35cafacf9e7bd8b7667372fe52f80abff7f5db668e07abffafca8f922c98a76e2bdbe28b0c367302b679e3476573e2981920230eab3e32560bf6fd3ca92eb2d039e4a3f8ee4f684bb861bf41da21b42f5e7f107c951c8943784fe2cfda5b1d11393d2860a5783a4930a68c527acc69c911b3e9aad82271ec7877fbea23e6927501377daecb41a6923685063ca6594da384f7d485b083ecb0217e3eb6edda4a3bdf6f365f0d6666275f49c4f3abe28ec3012bc7a28036626ed165fd4189c33c17f2e7ae1ab1edddfde6adabe92d7e1efac339e0b3fb11aac387510dd986b6a630a6a2d623e680d5f41899d4ace6dedf79d10a8b54118bef3b9df7e2e893e7bd4a6e965c23e7f2d2614f5d69b5ca6577e5d4edc7e864bb44de1489f833034653636e443e29dfe57325a7f7484b29052a352bd0bb54b5b98496e7508eedc00bbc815b051672eec7753eeb7c6e055c5b61919f37a3911a3a09fc9ca654144819c5815285b02b0e2f66f95d71498394d10426844545986d4899b2221fbf25501db4a1ad37b75f2bbcea7bb3e1d87360eb8d501eef8ae5f81425b511c99b708c0d76c5727c8ac2c5d821261376c52cec8ae5f814e56ac35d795318114835f2bb62d8a504accc28b55eb73df0825d31ef23b8d6520b68c35e2ac8b95268ab8495c282b526ecb1a12bc5d48cd0d6fddc6a1e7c557a63b281fe19f65a4db0de954b119eb1995b2c85a2c6ec9b249be6dc7d8caa5869fd23d9f7abe6995432a537df2375473e6b81fdf41738a7b2594e92da09e5f02c513613ea6b8971c9d5c9de49fc6938b63e6917b63f53de4483e91f2cb59152362b3b188df25ba73d616b5a8a26c14c2c58704f454ab8c9f96e35ea04714358338336db3b4bf1bd053c67ada833db1ce08557bd7db403cf09b2665907cc4632ce1349557efb9e863981fd1ad71ce2417c89f3610e0a8e030e45a4f5c2f338a2a8ee373dfa11e5e9ecd1f2a2c13d4a79fed65fe34b72e5ca2999b3dc71e3e07246e9c3cb09e32771960a320d193ff1d052fc7fc34973136ebd6634d6e4ac5ff07c44a827eb457afcd62fb85d7d63a2817f6b55f59202f8c8a930c7d8f2024ab59f7a60bd617bc0bf4716f6fc7b85b7c8fceafe8187f0eeaabc4658690b1521dcfddce784b7560b781ab55478e3641066e17d42385b98177b14e71d0eb93474460543a8cd1a68e521ea1517e5f3740bbce6c952b3cde7eac25b9c5208fe4a96f39d6554a74283db51f59292eebc8fef2213eaef2cf824227aa4622af77ea8cce99f9687f331ada54278829cdcbe4478febd1c12cd919c3f597ac42caea190486f8e85bece697b5ea79966944b51d4d657e89d7b51b62249ad636b8c55a208d28ec0b30dbb2cb5b28d0f70ad7c8953f103250a6ee0fb4997636b8c4da223e65cabea008554872984df0c7125df6fecd369beae1b816e8f2034bf8f6cf522806756c211e37f45767be1d8fefbd95a56f62dc97f09b001e4c5be2f1ecfd096b8e60d13a23afb139c798f599defb7cabbc65c76cec2c8ee71c33523f70a3d775f1fa945a2edca94222bd85dab6a7a08a18b71dd62b163af187c8534de330e48eb5f6a1d15e6602865ebd5bafea55f5b8477669d43dfaa3577b96e40758c6e70bd259c8e6f460d7ed7a6efc4ab92b403af4b77c64539efcb743a4b1f6f1f903ee8fa80f4e8a630fbdc724a6fba85a5970c5e6778c9ae07a4d1452d084b7ccee22a7fcd1c032364f860665972bfc3d5daeb6b7b077601c4c8d016dcbb71cdd506dcbb427fb9dee4a25fc20d23afba3c5541bba2da94ab6845af4899dcaf50b6d0736f43ab25d07ac02409c2b28960073548becb2ecf5678171f74e56a1dbf564b3ba03f631096335cac96a091ad03d2952592926cf30d8b534a90711cfac45bb0d3a8f9223fa220822c12593423f0ac342e6259b7e84e18fa44976fbd51f580e7c3df39bd1316d54994c37fa0b7c4d7e6a697f1bc728eeea842dc9a555be59fad551cf4c0365e97ad7d6e19e4c277b4a7e7c8cc61dc3aff0e31529e7977c411b3be9142c1ed930de0950a3614f6ee1173688b1ef47cbf61e69fa7bf39e5f5c7d3c7af9af49ab423bb647c3b9642cec420be455953190ddd5b259293da7bb87da6afc40d249adc2237f9a16c73f0ca334c96efc8e3adddce95613233e0c161af72b2d0572d8aab75136e8cc9707fcf83dffdac6cc1f75575318a3898013ccff8f6bfa0980f4fc3feea7b70fe840534be992af710e1d3b150469a5699fcdbf73dbc74fa484fb4ad113c5d729958beb17b840a99903e721862bd462346b2e3443078c156ef8df7adc30cf7579afa47afb0e012f575210d7cbf5451253431f2d3547eec6699c5e38c0e7d58a3a3da20f2cd5610dbbdd52bb60af3eb448bfd9ea8d448c96a13ba4591ea11782e1b772511cf2635095da2d9c9114d55a21a7b95a804d6d7b2164177618498fae8911319b9542bd5e13b41cddc5da4ab1589b80578ade05bc2af33c21c3ecf3947265e186aa77413cca35c1e728533cff13e5e739c29adf1ce94a0fb4353d4cb5d6703bd4353f62e767df161e753c02f40de9a6d7974990de9f791486ebf4bd162bb6897869c4323bbc82587abb6a82e4183e79436cd25022f58a268a90cdffafd033ef50cbc701ac933a72372ee8c807614d8741765e50da7269b03c0679f65d66922865721f1c2e1a8f4d1a98824231ebd44d4d81a89c0e124b2ce19ee086c7a03f2cb8b02c4c3297a3e29a2478b45a6587ac405fc5e84b820626269ff0be0351a615f668c7eba587965ec3ee72eedeb3a5d58bfc52a153f38525c7645cea811b71a71d287a85d3dddeed6eebfd85049b49c9c84efc350f826a59c5c2f82b66bab5c6d576b5cfdd9e26c3b9912b85c4394266422c22f621349f4e7e19b9ef27316542a93218f06577f2cb723f91b7789e1b2c925e2a9b066dec5cfd5667a9858f44571ee2b81098d88891d9337c3dc86bfd4289e8622729b50447c03f735c669149823455675db03be3db38b65aff45a9c72354c77ca857ee2e5e266904355976045c44fc7aa7e06bdc32b2c3773343eee5c61c1f26f55214fd20201f059e6c0cc9437bab1640ea9de5c5a7202e68665834f3136c04a0133f22496d2caee583493eea44a26c929ad28826a1199ab38bfe8f2e7865966df574da7641b26b6641757a4dcc9944c32a5821de5dea9a2266df099874b8b6aa82e51a5b9648db7b52c75c73f223548345cf27421ff0ede39cf4ce4e619f060b9ba0666dcfd062c07fef609d7ef5ab1ee8c9228f6cab03d9c0f2e5226ed38214bc9360fadfd4e6acc247e5de5ad7545c626758eb3e7de067f41a2537b32bcc56af713b1f8e68a5c6c7d462e433ef284f828d63f14b17e61a80ced2cfebca86c36d8499f7c530f517ade48305b6a8b2b514d9b3f319652959febf3a40ad134b33e94ea2b6d32134c5eadb90a9dcb5705d9fbc850eb23d2fb5b8c842692fcdda2b1b15b97b0fe61a8ba58557e68f8d6396418f883e587bcac7e8d0f2a13f92f0b601725aa36d2ab4127a9db23b296d12b2da26c3f4518f79e604aafee485149b445da87fafd31c72db08fda945f7202cf4578fc58284ddfac920e78fd0b5b1637634e130de0d3aeb248604428621efd6eb7a8da10e9f9bb45ac071207921e8805f53a6b2e9b836ff2d33b8d8896152196d4524eaf869c601df354ffa033b66e9d66603f2e5a3d8b1bc17eeece1ad64bb9911259a4c82b989a66560551486e36a223c51ea9100a06af6c1c85f0be56acd1ecf3b35a48e42597fa4870dfec2fc6fb2032ccfa48e3035263c6fb466d5b255bdc842229e435cd6c9106011f6eaf2445b3b1a767aec47e60ec31b9344872c8ac8b557d02fcc1f36342eb29ebce530f7009965a249d665e3a4717bae38bfec8560783f5447ba74a3309f7998f846ead12e1afee4a1396a147a823e62d546418f2827df81c26168ff5b27cfc1dd9402f43896b5548e407b6e4b55937abc8abde99c6b59d0a92d4186d22ec1f28aab0cf9c5a536d7e0ff86eb56eb77179a6c8cd1a31bd88f27c2a43da876fe8c7dd5ed881cbde019bc3fd262f6c42d30a23a920146c8e99239e4021dd3879b14444bc27f70b050d2a9c4d4cd085fa9e009b946d88d44cdac0d9275f2f5f67d8477fe3ea2ca13d1a73532782d956a45880dfacc298e36e96ba7dde2455d03862e006ff4406dfe09f0863d9a9a1af540d17091a687b76b3eee9226534633a62ce364652318b947fea2dbdb8cdeba76c2d0ebdc88516b842a5457ea8bbb4dfe20c85a01ba50daed05474b1f4cb3a67488d8e957ee74a5b865797fe65aed4141cbebfcc95be0987edcfb9d2b92f866e3de74a2fc2e15f8a5ce97b71b8aec86291c62cfaf985acccd2a283d01efb3bbdf917b7fdfdc76c2d1f5f5ad480f0a9fc0d8b337c0e6dce6ca873865a5146e6d7aeb46e9494f99d2b8d24a2337f70a5f91144668a2b2d8488a4fa1c29aed4a910beeb4872a5ab217cdb91e44a5f06e166c75e577a0a848f3bf6bad2374158ebd8ed4a67208c1cbb5de94538fc20c495be1787fb42065a85c3cd63ebc02e44747b95c839be619183c717e72e8bc59cb2149827eeabada7b0a6737cdc52fe7eea22c7bf2cceadc4995e6e5494099ca634d50ba1c1ba814303ba814303ba814303ba8143437483e3baa0e9637ee487707d7108d71787707d7108571ce23566992b55a8c5952e68982b9dd7a8e7f83e8d77d3a8f3578ff1f16e1a153f44a3e2876854fc108d8a1fa251f143342ade4da3e25d1ae44ae73528c9952e68902b9dd7a0bdae745e4376bbd2790ddded4ae7352cc4952e68982bfd200edf1deb4a1734ccd57bbcfdee38e60cf3f6bbe360f44806fc34ec1f0bbeda6713d4c2db8409bc7fac533b3de43d16962a17d1d62a29becd1d4885382e6ae8669b089f33da506463180a5164e593d648cd0594953fc6ba236f97cdf9fddff1ef9cdf4bf35a84df24299bbeb6384314f1759deb5974d182e7f2ab1ada5615ea9afb86f3b6e19af9966c33616d6b2dfd3a5ff94f6c157cec2e13d633d69ed9d76a1988e1da6ffd81eb9fb2c55999bb4ca02b8f61adebcdff3abfecac60152f1f33d5632bf9455de70c633d85b2b7a1965fd2f8f22f1d33617d63b0be8de0ca5f8ab63853b0c6a941e35ab99863a6b297b02645529d8e6e7e345ee66e66dba9db6037bf0823fc128e8b1ec2f3e74b53b9349c1249118e9f393e598a734209185f025b39c35bffa57e6e6c67ce19e0fef8891b0771dea3b37fcbfde817bd0707733f3a7b30f7a3b1380ea7e11a48822b712ae398336d70cd1f9d7aee20aef96dcd40cde98d0c97b72ff6b735f79d2a3836b8e6bed8c135f7b971ef383586e33e73fea0767de5c14b12179d896331675c0327e5998bb59c3473bef0ab7fbdbea01b78a49ff8ba868fc3357da1a867f99198a919c8f37863562dd6b9020d3e1570ea5ceacd019dc31a26bc11f623d5fc934ae4a1be5807bb4c9190e21bd9b095809ddbbec1befcc216f0fdc96ecf90af4d1a1a757b717feb621e219c636851b5c9750bd951cd2c74de44cae1ce80f9b38e07d5aefbab8975d506e79778c7fbaa19671ea4174e0ebb8990a16f6a2cd056f1874664d06835f2cdbda8f4fb18c4767589490f4612b8f403f44c2e969a9460b90f5af8dc8de8a4e6f86f72c3dc2d01ab98ebad21d4a54d55c4b63c6b9eedaeb07679c04ea78172be7fbc2e9e20212473d15c270f5c13e6f34c2e1182799112c24208fdb8e70637b374543deeec79ea02dff378a6096ee6fbdf79023c78d73ea78f540f0e8baf7aa81b7829173179f4bb5428a1bedce4dc992b17619fb0b44981b6e5316111cbba1111b2d682c74ff0b3efe2d697bd7c2c6f5bf9406c790f1f5bdaa4411707e50eea76c65344efa0f8f23bcef846b4a26e50fe5fb9f825a54d916817d43f6359b768ea607eb79de9a9a86130bf9bce7833ba6c71be3d4cfcd1598b11e9ea3c9cdf64ff8063fbe6602d1fbdcf195bdec4c762499930fc06e8398b33adf91c4e3baae9e5467edf7cf9a6ee116448a9c20f61ad08049d8055894b095cc6a0af2dc27cde82e37e7e19ef57b28438ad958fc312b55a9cdf4538e328e23be11b436804964673d9196ee6c39dcef02f7c18f810a63a67db61cf9ea7fb07a12e6814daf43311c2f3be5ce7b4ba0657ee26b7dcc77fe0731f9df399e5b7b96d6eb99bcf3b73fb3d84b7dd2d37fade997bc2a0dccea70f6b06cd1c6707763e78479dad15ce10bfcd9d33cb48b672b3dae2d2a630a8a1e01f268d791aecbf2a7abee1e3f17b9ba3676573b2b5384f24e4d971621bf41a0e692074f51706461173d4db8c5f712794dc1904b51fef42b296bd381fe46e94cdc1239cadd5a79222eeab2c573ee35f8f98f1df7228d3e051657bec882e6813f1126a4e95cdd185991a9569b0db5f9b6d76aee6bbf2b8eff41e2893fa9b6429ad42ed552706b88655e2da5f9c0f73b7e8868308919d000bf693279c238473a20f749aa78be8a24614c850285beb9b74218eccdbf453e16c7ce390bf69f8b5852f81b9ab3ea28ef22599725e36e3d1a78b4ae61061f85d63ee1c22245bcb90e2c66e87783a89f4a17309b6472a6a7d4084a982da5109ecfab95efdb0ed70a76573465c7ba0c72107dead871b423bf04e7c9b86be5f8584f3be52e1fbc622a2bc48433fda83600637f78884facb8477cc2c510e337c690f1a1a1ff51e513ead65623394b966f3142984feb010ec2cf368b53ec63957965fca4e11f96ed2ce58765054b1b2b4700aaa581f18df80d8cc9e11818a5328b26704cac85784e26fff620b2617fa24cea05aa30213346841ac68a32e846d25650b4ebdf8059b691fa1fc221036f63591df229ca714aaacb1851172485105b57935e4ab42a54817f6e2a91763f1faa00bc35e208466c3938d92010f6abb58790aaf1ac2795781f08e9dc1279ad84f4c0991b608712f0792be8430fb726fbcf57fe2dfd6e0afdba1d557abb83372d5deaa0a46b387ea448a29dcdf34d92d8b72be99914575a6f0df58b20c1924d8938dd722cdfb035ad4a8c3baa9da4fc9f49a4eeeef1d308d0a124b98f29d20cd69fce6818f232f38dfc1eb4f09fafb41196dbacea7aabfe14b240a27d5d49e32baf39fceafd7457e2cd64d3f8ef79bb38a9449aafd1619f7372e14a408b78e99ab631525b0c797b53f49eff2a3d8229032807bbf9fb9bf445c2343faf173906e3cdb3e1ea982e5042bd38a6a7215e22f4a54874f23d5d10b308e10636c14054a0ea2234c84f42e929d0d66260bfd9af86bf04a58e1497c03803cc5c735ffcabff571dd2db8619a6bdde5777fa0ee7daf0ead3bc0bdee4fa0ee031760e65f9181bf43c15fa5e06f51f82f53f6e76d080f4259cfb3cfabc5f2e7bf446c6fef3b4478c4e60f11fb42b798d67a8858eaf63bf4b65ef0f6877b5c52e0f7c6ac69f47162be2e26641f4d7ac85c77bd338990528a71b01edf1e8f18dd8b6a4c1f465dada3290f29ab00be1eb7dff19e5f1f636da0fad8a6f9e2aca54f6a6134df89d8b891607fba2ffeac518eebd1dba4a466b8e425c5f35b59a3e238a161fd7f877e78b7c6d41bd5efff9926e2d49b04ddd624e5bf9b23d3b648f71797cdde0f5e826e1e39ff9805d7e2317f97c5fbb4ebbdd73a5eaa08df2ec452df1e37e56fb278fc9d559c44f43714fefee41d6f4d7d4c5a9d4e436a9eabc3610f4d86858f7fce22f73320fa0d934fff685dac7c2929aa596e12f527e8d8abf9baf722324da2534c04f4b148b12c9f7e9b22c2cd2c73d741a756217a1345b04cbb23dc1c4151d111cb8da2884ca3a8268712b30a4a34db48f735a16ab31c767ab0d341747713783eed0e8829244545f9cf6fad89bc8d56400ff4fe815e767bb83c7323f23ee1a1c1776268c9352f6f0d3dfc9a271de089e4995df8b45edc84464ae48a8d88bf2f7545d1afc882d21e5172aaf91d6f2dfdb21d5be5f07b3298e3a4f76434e929cb5aca421adbb214652dc3e3f0a42e69db5985dcb747bc625bf4b61dc73794fba2877f9bc4faf782a68c460dc7e273e20c2f1831673b9e3d45765f4663f5b777113164c54b39ad16b6c5808818dc97197564cc425f22d65bf365ddd80763250575137ebc5137f5019ffa715dca03a68ed4ecbacebebe11051b777d21b475d83519b4557c4d166ca4477b22f67568ad9a361c82de8a4149ac5ca340641ea9aea1baa358a9564c7fd123c36b09be91617abf26fe0384473d628a162ddbca421f6e3b61e2f992f72420ef887b22dc872cdf877adc87ac02c6fb0225c2b98818688d88bac9b68c439dc7828dd1f7e49401a559828dcf9fbd58b7f9de58f08a93a015de5a9dc63a9cba26b7fba2fa3aefe3421dc43d11d441dd437e31b4d4139131a3cb4362d63ef0d6627e3a4ddac190984df76e5870bfe03e2062eb63761d1b087d7cf0e1d67a07acb57fa91cacf5532d78d8efe8c2af6e659f074b1de1c169333d0c5beabb924b8a8b5be51ed84e8938bf7df5275db7419ee1ed410e565a5308bdc3dd5f3b7692359f4472ce46a3e3ac8d0fb7d1dd5f71366a78b88d9e318385aa234e6d26e4b90a44b79ef3e4ad3465f280951260a54c9d9cb3d2bd83ac34688abb95aefd8d9546c72571563a4fb0d234c14ae70deac3e8b88769332df1441b821ef917fa9c9540b0ec7989885d2611f56eddbc35822c14451872c05a0f896a9822110b765f51b4bf90ee36205223521c29ce5a4a6c8fb01b50d2369122dc2cf73523dd76ac652bd84385d1f99592889616c459e30b948c4df045642ed83275f70ff4a2db9e4e5bc65fbbd0f9d7a4a0dfa66b52bae4dfd83225136cd9bac8fe2b589c89d360dd3d09adf594f0b62c5ffd02f2b02ccc09e56d92b48fc2d66e1f055a3cca7e9788798ab3c959b9bbea9efd75ac6473dd73976ed46dfe95b7c0ef2c290fd6ba592027611e67817ace0277fcbf63811c5f86933f1f2cb0e4e1168873711628a3aec85787a39083ffc6022554b33cf311c102b93a749c051a390bcc196c81989f4ef3432d6f817c0f5cae19d0a24db577c2c0ca12e40960657af0a1de61a5cd7fb89a9f55409f6a43e4ad5dd7b1673fe62ac446d367da444c2c1176d65f4e4e9a732e7ff4ae40aae978c4b92a84cfc68810d33f18cd867205be43037be78b75f59a1b80ac65dddc5d36bcc7f5fe5aee4b8a88edaebf972286d19c637a5f4e4d9a3383b44745f87e80e4ddf677fa133ed5676d5dec17bdcd753b3c46a7b90856a01547247423eceb8db65e5674df9743ef8fc9af8138de9b211f54733bf419645514fe2b3df8ef733977e0aae1bc9fa52173692c914e3df8ae4ba282ff7edde6914bcf5c5b7022dbecbafb124a8ce4d3c23c83db15bed8bf93788ed98adf1f7a1b3e378c6c19b8cf91e5cb820358097e5cb6b9d5e22c5725759593e27271cdb8a42e6696892e3688842fe8257c8e16f198ad42dafb06c47316a561deac790a3a039c1949b6f9b26570e807ee56aeebfdbf6462c7e4f6692dd22ee1b6c029be064d5f095db172f64a7da4f1b12b29261b4b5141aa99f6474b34596afae556e77e00f1a59aff9eb50c9fb1603fd9fbb4dc2f12cd2a524d6ff011efa39e246367f8b7a18855e06d414835fd5c902996567c8ff4959a2797bdabaa6e9c542a55200f6a3855d878c4ccf34db9bfd5ca738e6a24d42bc0438befa8de22dc1fe8c5b3fe0bedce35e0a596895b04298e087f55e0b6fe330a91f3f5074c8fed6a962657260e9ddd847d56775292c2caf98ef5da98408f2ec7704968a1759e471fede189f49186201a5433eba924df6e4b598c6f727f246d0e13ab3e1847749f1757499e9ce1ff3b42ff5703ec4488308f5bd9c56531d692cc5ebdc5f49835c7dcf79c05dffff9a14e5f15f3a47eaac1a7335f17a6b3c533d9c597eb26c2bf5fd6f15a3ef0175e59d97631bee1156ea05f20d10c8936caf9cd0eaacf5a1a484a5cdf530c2ef32d89cbd02f9188df8d485b064a467d8feaf12d1f7cc3537a63bf21f8ea1666a024221f561bfa0ed716ca6c71bedbf6c5350fdce11c9df138cae91324bbd79f407b74a3c9eac065e7a24853db7a0fee1e4aabb36d424dcd0f6d57e2195c53303370fb717099db0f2d13d438b48cb09fffa184c6a5c3f5b372c75c77fed55ccc25d11b73012995ddcebf2077027fdd0dda9ae82caf3a5cf5bd783afecb98446c0429269c12f06567b356df943e2edf81aab3b399cb96a17222c9c3e42c3f3a54ceb224954f372a4b518d847fe7bd58fff33c15bebbffaccaaf1bfdfcacca1f9e33540af877b3f2ead1a49957cb56cc6c3d9aa2bc7174adb2fbe826657fb61671bfb39f381ca33e7538a20025806b80c97f7338e20e381c0b01670f3a1ca1404f591c0e11d0e8f4b4d47529c94129afbdf6ea6b633dd1829475a91b57b98268da94351048da90e2edc9f18f983c34ec968cfe14a39ebd4cb32248bd6e63ca6b41b1e9491b56053dfb6a720a4243d3b9149cf07450dabab48d419b925edb084f50e8b54deb373e345ffaabafae4742352879d3daf52fd3afbe96f2345ff3a675ab92d625a7434b063181df9f7f8fb40fa3df463e3cde49e7fe9bf4fd4ffff7e9a7a6f0b459a0f704ea3b95a71305ea358da7a102bd22d0f393ff7bfaca6b29496bd6bf0a8de5fb6365d2a6f48d412b5336d2ab0685d35f4d4a1e9cbe6123f418174e4b4f4f494d4a87dedab0f1b54df4c6b457d771f16bd33624092a31c06c683cc774507880e9ffe64771ffc24c32f4271d1c1089fed7d5b87eb2ff2799c5ffe39c95c2f8ec1368b9403f12e8f8089e6e14e81b825e9c15d2bd843042db11011001c48f2124f9c32788044801144006f00078028601bc208f3760386004c0076d178f043a0acacb81fa02f503f8c3b30210f0d876341af0082010a0048c013c0a780c1004180b0806a800e3002180504018603c6002201cf03bc0e38027001301bf074c023c09980c88004c014c054c034402a6039e023c0d980198097806300bf0074014c81a0d72ce063a07680c5035d05868ab063017300f1007980f58007816b010f2fc11b008f01c6031f4c312a04b216d19f0880724407839e079c00b10fe13e045c00ac04b10f732201190047805400392012990be12900a58054883b8d580358074c05ac03ac0ab80f5909e01780db001b011e236015e076402de8011de0c7813f016e06d4016e0cf3e3bd03b002d4007c806e8010628c300720046e0970b3001b600f2005b01db00ef02f201f8bf02c07b937620165008cf45806280195002d801bc76024a0165805d80bf00de07940376032a20df1ec00780bd804a4015c4ef037c08a806fc15f011e063c07ec07f013e817c9f02fe06a8011c001c84f843000be030e033c011402de028e018e073401de038a01ef005e004e024a001700ad008380df8127006f015e0ef80af01df00fe01f816f01de02ce09f80ef01e74086f38026c005c00fd0433f3eb603fd04cf3f032e022ec118fc02b419f25f065c0158012d8056401bc00668075c055c035c0774006e006e026e016e033a01bf02ee00ba00dd801e801d7017d00bb807b80fe803f4031e001c8f7113c062614a59c293718b84f052fccfb49f1c8e18c0624022601de06dc016c04e4035e008e00ce047c03580e867f00d0041808980998038c072c02ac01b805c4009601fe030e034e002a01d5001380af81ed001b807905d7438fc01e3009301b3018b002f01d2016f024a006d971c8e1ec03e78ce051c069c0634036e031e00bc20fd11c078c03440312016100f5809781d50093804c8019c029c078cfbc5e1781ab000f012e00d402ee02f80c38033809f01b7015eb00f08063c0d5808580d987d19ea00ac0468215c063800380d6801dc038c82b450c062c024c0f4cb7cb95840c51587e313c071c049c0af0019ec1b1601d603720141563ead1ce874e119a707b5381c53007301db018720eeef8076c08b104e07bcddc2e75d348827a69f437c07e091569011c2be2d03e97321aee40a8f9302701c4263053d0a16a88aa3c9491b935ece4c4b06df6fd6334153834243830645cd7c26683a9f1b16fbf5af80bf36f699a065d18b97bebc64e91f17bd3cf95fc43ff9f254e7ba383678debad7c163480e5a9ff45adac6cdc142bcff758763386094409fee18083f2200c7e170cfb501044378e94d1eab018b04ccbe3110c6cf8b84f48583907b0bfa811089c4f093083fd2ed27fd373fea7ff993fd5ffeed4b76fa198877aa08c1b722785f2748ec4ae77ecd697cb864354f17ad199afefff59ff3ffc3323ce2ebbf4e19b5b6ade3128986fd5f95e83fbffffcfef3fbcfef3fbfff3ffd64e9fcbaeaa4016e74bc1b9dee46e7bad1e56e74951b7dc38de6bad11237bacf8d1e76a3a7dde80537daee46efba51d9daa134c08d8e77a3d3dde85c37badc8dae72a36fb8d15c375ae246f7b9d1c36ef4b41bbde046dbdde85d372a5b379406b8d1f16e74ba1b9deb4697bbd1556ef40d379aeb464bdce83e377ad88d9e76a317dc68bb1bbdeb4665af0ea5016e74bc1b9dee46e7bad1e56e74951b7dc38de6bad11237bacf8d1e76a3a7dde80537daee46efba51d9faa134c08d8e77a3d3dde85c37badc8dae72a36fb8d15c375ae246f7b9d1c36ef4b41bbde046dbdde85d372acb184a03dce878373add8dce75a3cbdde82a37fa861bcd75a3256e749f1b3dec464fbbd10b6eb4dd8dde75a3b2d786d200373ade8d4e77a373dde87237baca8dbee14673dd68891bdde7460fbbd1d36ef4821b6d77a377dda86cc3501ae046c7bbd1e96e74ae1b5dee4657b9d137dc68ae1b2d71a3fbdce861377ada8d5e70a3ed6ef4ae1b75eecb1dc2cfb93d3f7ec9838b1ffcfbdfbc0df93f99c1f709' ISP_PROG = binascii.unhexlify(ISP_PROG) ISP_PROG = zlib.decompress(ISP_PROG) @@ -385,6 +385,8 @@ class KFlash: op = 0 reason = 0 text = '' + if len(data) < 2: + return op, reason, "data null" if (sys.version_info > (3, 0)): op = int(data[0]) @@ -419,6 +421,7 @@ class KFlash: ISP_RET_BAD_DATA_CHECKSUM = 0xE2 ISP_RET_INVALID_COMMAND = 0xE3 ISP_RET_BAD_INITIALIZATION = 0xE4 + ISP_RET_BAD_EXEC = 0xE5 @staticmethod def parse(data): @@ -436,14 +439,46 @@ class KFlash: if FlashModeResponse.Operation(op) == FlashModeResponse.Operation.ISP_DEBUG_INFO: text = data[2:].decode() - + reason_enum = FlashModeResponse.ErrorCode(reason) + if (not text) or (text.strip() == ""): + if reason_enum == FlashModeResponse.ErrorCode.ISP_RET_OK: + text = None + elif reason_enum == FlashModeResponse.ErrorCode.ISP_RET_BAD_DATA_LEN: + text = "bad data len" + elif reason_enum == FlashModeResponse.ErrorCode.ISP_RET_BAD_DATA_CHECKSUM: + text = "bad data checksum" + elif reason_enum == FlashModeResponse.ErrorCode.ISP_RET_BAD_INITIALIZATION: + text = "bad initialization" + elif reason_enum == FlashModeResponse.ErrorCode.ISP_RET_INVALID_COMMAND: + text = "invalid command" + elif reason_enum == FlashModeResponse.ErrorCode.ISP_RET_BAD_EXEC: + text = "execute cmd error" + else: + text = "unknown error" return (op, reason, text) - def chunks(l, n): + def chunks(l, n, address=None): """Yield successive n-sized chunks from l.""" - for i in range(0, len(l), n): - yield l[i:i + n] + if address != None and (address % n != 0): + start_pos = n - (address - address // n * n) + if start_pos % ISP_FLASH_SECTOR_SIZE != 0: + raise_exception(Exception("data should 4KiB align")) + count_4k_blocks = start_pos // ISP_FLASH_SECTOR_SIZE + count = math.ceil((len(l) - start_pos)/n) + count_4k_blocks + for i in range(count): + if i < count_4k_blocks: + yield l[ISP_FLASH_SECTOR_SIZE*i:ISP_FLASH_SECTOR_SIZE*(i+1)] + if ISP_FLASH_SECTOR_SIZE*(i+1) > len(l): + break + else: + start = start_pos+(i-count_4k_blocks)*n + yield l[start:start+n] + if start+n > len(l): + break + else: + for i in range(0, len(l), n): + yield l[i:i + n] class TerminalSize: @staticmethod @@ -586,7 +621,7 @@ class KFlash: if retry_count > 3: err = (ERROR_MSG,'Fast mode failed, please use slow mode by add parameter ' + BASH_TIPS['GREEN'] + '--Slow', BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) try: self.greeting() break @@ -642,14 +677,16 @@ class KFlash: sys.stdout.write(binascii.hexlify(self._port.read(1)).decode()) sys.stdout.flush() - def recv_one_return(self): + def recv_one_return(self, timeout_s = None): timeout_init = time.time() data = b'' + if timeout_s == None: + timeout_s = ISP_RECEIVE_TIMEOUT # find start boarder #sys.stdout.write('[RECV one return] raw data: ') while 1: - if time.time() - timeout_init > ISP_RECEIVE_TIMEOUT: - raise TimeoutError + if time.time() - timeout_init > timeout_s: + raise_exception( TimeoutError ) c = self._port.read(1) #sys.stdout.write(binascii.hexlify(c).decode()) sys.stdout.flush() @@ -658,8 +695,8 @@ class KFlash: in_escape = False while 1: - if time.time() - timeout_init > ISP_RECEIVE_TIMEOUT: - self.raise_exception( TimeoutError ) + if time.time() - timeout_init > timeout_s: + raise_exception( TimeoutError ) c = self._port.read(1) #sys.stdout.write(binascii.hexlify(c).decode()) sys.stdout.flush() @@ -673,7 +710,7 @@ class KFlash: elif c == b'\xdd': data += b'\xdb' else: - self.raise_exception( Exception('Invalid SLIP escape (%r%r)' % (b'\xdb', c)) ) + raise_exception( Exception('Invalid SLIP escape (%r%r)' % (b'\xdb', c)) ) elif c == b'\xdb': # start of escape sequence in_escape = True @@ -799,7 +836,10 @@ class KFlash: retry_count = 0 while 1: self.checkKillExit() - self._port.write(b'\xc0\xd2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0') + try: + self._port.write(b'\xc0\xd2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0') + except Exception: + raise_exception( Exception("Connection disconnected, try again or maybe need use Slow mode, or decrease baudrate") ) retry_count = retry_count + 1 try: op, reason, text = FlashModeResponse.parse(self.recv_one_return()) @@ -807,7 +847,7 @@ class KFlash: if retry_count > MAX_RETRY_TIMES: err = (ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) KFlash.log(WARN_MSG,"Index Error, retrying...",BASH_TIPS['DEFAULT']) time.sleep(0.1) continue @@ -815,7 +855,7 @@ class KFlash: if retry_count > MAX_RETRY_TIMES: err = (ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) KFlash.log(WARN_MSG,"Timeout Error, retrying...",BASH_TIPS['DEFAULT']) time.sleep(0.1) continue @@ -823,7 +863,7 @@ class KFlash: if retry_count > MAX_RETRY_TIMES: err = (ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) KFlash.log(WARN_MSG,"Unexcepted Error, retrying...",BASH_TIPS['DEFAULT']) time.sleep(0.1) continue @@ -838,7 +878,7 @@ class KFlash: if retry_count > MAX_RETRY_TIMES: err = (ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) KFlash.log(WARN_MSG,"Unexcepted Return recevied, retrying...",BASH_TIPS['DEFAULT']) time.sleep(0.1) continue @@ -854,7 +894,13 @@ class KFlash: self.write(out) def recv_debug(self): - op, reason, text = ISPResponse.parse(self.recv_one_return()) + ret = self.recv_one_return() + if len(ret) < 2: + KFlash.log('-' * 30) + KFlash.log("receive data time out") + KFlash.log('-' * 30) + return False + op, reason, text = ISPResponse.parse(ret) #KFlash.log('[RECV] op:', ISPResponse.ISPOperation(op).name, 'reason:', ISPResponse.ErrorCode(reason).name) if text: KFlash.log('-' * 30) @@ -897,7 +943,7 @@ class KFlash: if retry_count > MAX_RETRY_TIMES: err = (ERROR_MSG,"Failed to initialize flash",BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) KFlash.log(WARN_MSG,"Index Error, retrying...",BASH_TIPS['DEFAULT']) time.sleep(0.1) continue @@ -905,7 +951,7 @@ class KFlash: if retry_count > MAX_RETRY_TIMES: err = (ERROR_MSG,"Failed to initialize flash",BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) KFlash.log(WARN_MSG,"Timeout Error, retrying...",BASH_TIPS['DEFAULT']) time.sleep(0.1) continue @@ -913,7 +959,7 @@ class KFlash: if retry_count > MAX_RETRY_TIMES: err = (ERROR_MSG,"Failed to initialize flash",BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) KFlash.log(WARN_MSG,"Unexcepted Error, retrying...",BASH_TIPS['DEFAULT']) time.sleep(0.1) continue @@ -926,7 +972,7 @@ class KFlash: if retry_count > MAX_RETRY_TIMES: err = (ERROR_MSG,"Failed to initialize flash",BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) KFlash.log(WARN_MSG,"Unexcepted Return recevied, retrying...",BASH_TIPS['DEFAULT']) time.sleep(0.1) continue @@ -963,7 +1009,7 @@ class KFlash: speed = str(int((n + 1) * DATAFRAME_SIZE / 1024.0 / time_delta)) + 'kiB/s' printProgressBar(n+1, total_chunk, prefix = 'Downloading ISP:', suffix = speed, length = columns - 35) - def dump_to_flash(self, data, address=0): + def dump_to_flash(self, data, address=0, size=None): ''' typedef struct __attribute__((packed)) { uint8_t op; @@ -973,9 +1019,10 @@ class KFlash: uint8_t data_buf[1024]; } isp_request_t; ''' - - DATAFRAME_SIZE = ISP_FLASH_DATA_FRAME_SIZE - data_chunks = chunks(data, DATAFRAME_SIZE) + if size == None: + DATAFRAME_SIZE = ISP_FLASH_DATA_FRAME_SIZE + size = DATAFRAME_SIZE + data_chunks = chunks(data, size) #KFlash.log('[DEBUG] flash dataframe | data length:', len(data)) @@ -999,17 +1046,28 @@ class KFlash: if retry_count > MAX_RETRY_TIMES: err = (ERROR_MSG,"Error Count Exceeded, Stop Trying",BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) continue break address += len(chunk) - def flash_erase(self): + def flash_erase(self, erase_addr = 0, erase_len = 0): #KFlash.log('[DEBUG] erasing spi flash.') - self._port.write(b'\xc0\xd3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0') - op, reason, text = FlashModeResponse.parse(self.recv_one_return()) + cmd0 = b'\xd3\x00\x00\x00' + cmd = struct.pack("I", erase_addr) + cmd += struct.pack("I", erase_len) + cmd = cmd0 + struct.pack('I', binascii.crc32(cmd) & 0xFFFFFFFF) + cmd + self.write(cmd) + t = time.time() + op, reason, text = FlashModeResponse.parse(self.recv_one_return(timeout_s=90)) + if FlashModeResponse.ErrorCode(reason) != FlashModeResponse.ErrorCode.ISP_RET_OK: + err = (ERROR_MSG,"erase error, error code: 0x{:02X}: {}".format(reason, text)) + err = tuple2str(err) + raise_exception( Exception(err) ) + else: + KFlash.log(INFO_MSG,"erase ok") #KFlash.log('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:', # FlashModeResponse.ErrorCode(reason).name) @@ -1024,7 +1082,7 @@ class KFlash: except ImportError: err = (ERROR_MSG,'pyelftools must be installed, run '+BASH_TIPS['GREEN']+'`' + ('pip', 'pip3')[sys.version_info > (3, 0)] + ' install pyelftools`',BASH_TIPS['DEFAULT']) err = tuple2str(err) - self.raise_exception( Exception(err) ) + raise_exception( Exception(err) ) elffile = ELFFile(f) if elffile['e_entry'] != 0x80000000: @@ -1065,27 +1123,33 @@ class KFlash: firmware_with_header = data + sha256_hash - total_chunk = math.ceil(len(firmware_with_header)/ISP_FLASH_DATA_FRAME_SIZE) + total_len = (len(firmware_with_header) + ISP_FLASH_SECTOR_SIZE - 1)//ISP_FLASH_SECTOR_SIZE * ISP_FLASH_SECTOR_SIZE # Slice download firmware data_chunks = chunks(firmware_with_header, ISP_FLASH_DATA_FRAME_SIZE) # 4kiB for a sector, 16kiB for dataframe else: - total_chunk = math.ceil(len(firmware_bin)/ISP_FLASH_DATA_FRAME_SIZE) - data_chunks = chunks(firmware_bin, ISP_FLASH_DATA_FRAME_SIZE) + total_len = (len(firmware_bin) + ISP_FLASH_SECTOR_SIZE - 1)//ISP_FLASH_SECTOR_SIZE * ISP_FLASH_SECTOR_SIZE + data_chunks = chunks(firmware_bin, ISP_FLASH_DATA_FRAME_SIZE, address = address_offset) time_start = time.time() + write_len = 0 for n, chunk in enumerate(data_chunks): self.checkKillExit() - chunk = chunk.ljust(ISP_FLASH_DATA_FRAME_SIZE, b'\x00') # align by size of dataframe + # 4K align + aligned_chunk = len(chunk) + aligned_chunk = (ISP_FLASH_SECTOR_SIZE - (aligned_chunk % ISP_FLASH_SECTOR_SIZE))%ISP_FLASH_SECTOR_SIZE + aligned_chunk + chunk = chunk.ljust(aligned_chunk, b'\x00') # align by size of dataframe # Download a dataframe #KFlash.log('[INFO]', 'Write firmware data piece') - self.dump_to_flash(chunk, address= n * ISP_FLASH_DATA_FRAME_SIZE + address_offset) + chunk_len = len(chunk) + self.dump_to_flash(chunk, address= write_len + address_offset, size=chunk_len) + write_len += chunk_len columns, lines = TerminalSize.get_terminal_size((100, 24), terminal) time_delta = time.time() - time_start speed = '' if (time_delta > 1): - speed = str(int((n + 1) * ISP_FLASH_DATA_FRAME_SIZE / 1024.0 / time_delta)) + 'kiB/s' - printProgressBar(n+1, total_chunk, prefix = 'Programming BIN:', filename=filename, suffix = speed, length = columns - 35) + speed = str(int(write_len / 1024.0 / time_delta)) + 'kiB/s' + printProgressBar(write_len, total_len, prefix = 'Programming BIN:', filename=filename, suffix = speed, length = columns - 35) def kill(self): self._kill_process = True @@ -1119,8 +1183,10 @@ class KFlash: parser.add_argument("-t", "--terminal", help="Start a terminal after finish (Python miniterm)", default=False, action="store_true") parser.add_argument("-n", "--noansi", help="Do not use ANSI colors, recommended in Windows CMD", default=False, action="store_true") parser.add_argument("-s", "--sram", help="Download firmware to SRAM and boot", default=False, action="store_true") - parser.add_argument("-B", "--Board",required=False, type=str, help="Select dev board", choices=boards_choices) + parser.add_argument("-B", "--Board",required=False, type=str, help="Select dev board, e.g. kd233, dan, bit, goD, goE or trainer") parser.add_argument("-S", "--Slow",required=False, help="Slow download mode", default=False) + parser.add_argument("-A", "--addr",required=False, help="flash addr", type=str, default="-1") + parser.add_argument("-L", "--length",required=False, help="flash addr", type=str, default="-1") parser.add_argument("firmware", help="firmware bin path") args = parser.parse_args() else: @@ -1136,6 +1202,8 @@ class KFlash: setattr(args, "sram", False) setattr(args, "Board", None) setattr(args, "Slow", False) + setattr(args, "addr", -1) + setattr(args, "length", -1) # udpate args for none terminal call if not terminal: @@ -1145,6 +1213,9 @@ class KFlash: args.sram = sram args.Board = board args.firmware = file + args.Slow = slow_mode + args.addr = addr + args.length = length if args.Board == "maixduino" or args.Board == "bit_mic": args.Board = "goE" @@ -1200,32 +1271,32 @@ class KFlash: self.loader = MAIXLoader(port=_port, baudrate=115200) file_format = ProgramFileFormat.FMT_BINARY - # 0. Check firmware - try: - firmware_bin = open(args.firmware, 'rb') - except FileNotFoundError: - err = (ERROR_MSG,'Unable to find the firmware at ', args.firmware, BASH_TIPS['DEFAULT']) - err = tuple2str(err) - raise_exception( Exception(err) ) + # 0. Check firmware or cmd + cmds = ['erase'] + if not args.firmware in cmds: + if not os.path.exists(args.firmware): + err = (ERROR_MSG,'Unable to find the firmware at ', args.firmware, BASH_TIPS['DEFAULT']) + err = tuple2str(err) + raise_exception( Exception(err) ) - with open(args.firmware, 'rb') as f: - file_header = f.read(4) - #if file_header.startswith(bytes([0x50, 0x4B])): - if file_header.startswith(b'\x50\x4B'): - if ".kfpkg" != os.path.splitext(args.firmware)[1]: - KFlash.log(INFO_MSG, 'Find a zip file, but not with ext .kfpkg:', args.firmware, BASH_TIPS['DEFAULT']) - else: - file_format = ProgramFileFormat.FMT_KFPKG + with open(args.firmware, 'rb') as f: + file_header = f.read(4) + #if file_header.startswith(bytes([0x50, 0x4B])): + if file_header.startswith(b'\x50\x4B'): + if ".kfpkg" != os.path.splitext(args.firmware)[1]: + KFlash.log(INFO_MSG, 'Find a zip file, but not with ext .kfpkg:', args.firmware, BASH_TIPS['DEFAULT']) + else: + file_format = ProgramFileFormat.FMT_KFPKG - #if file_header.startswith(bytes([0x7F, 0x45, 0x4C, 0x46])): - if file_header.startswith(b'\x7f\x45\x4c\x46'): - file_format = ProgramFileFormat.FMT_ELF - if args.sram: - KFlash.log(INFO_MSG, 'Find an ELF file:', args.firmware, BASH_TIPS['DEFAULT']) - else: - err = (ERROR_MSG, 'This is an ELF file and cannot be programmed to flash directly:', args.firmware, BASH_TIPS['DEFAULT'] , '\r\nPlease retry:', args.firmware + '.bin', BASH_TIPS['DEFAULT']) - err = tuple2str(err) - raise_exception( Exception(err) ) + #if file_header.startswith(bytes([0x7F, 0x45, 0x4C, 0x46])): + if file_header.startswith(b'\x7f\x45\x4c\x46'): + file_format = ProgramFileFormat.FMT_ELF + if args.sram: + KFlash.log(INFO_MSG, 'Find an ELF file:', args.firmware, BASH_TIPS['DEFAULT']) + else: + err = (ERROR_MSG, 'This is an ELF file and cannot be programmed to flash directly:', args.firmware, BASH_TIPS['DEFAULT'] , '\r\nPlease retry:', args.firmware + '.bin', BASH_TIPS['DEFAULT']) + err = tuple2str(err) + raise_exception( Exception(err) ) # 1. Greeting. KFlash.log(INFO_MSG,"Trying to Enter the ISP Mode...",BASH_TIPS['DEFAULT']) @@ -1234,6 +1305,8 @@ class KFlash: while 1: self.checkKillExit() + if not self.loader._port.isOpen(): + self.loader._port.open() try: retry_count = retry_count + 1 if retry_count > 15: @@ -1282,6 +1355,8 @@ class KFlash: KFlash.log(INFO_MSG,"Automatically detected dan/bit/trainer",BASH_TIPS['DEFAULT']) break except TimeoutError: + if not self.loader._port.isOpen(): + self.loader._port.open() pass try: KFlash.log('_', end='') @@ -1292,6 +1367,8 @@ class KFlash: KFlash.log(INFO_MSG,"Automatically detected goE/kd233",BASH_TIPS['DEFAULT']) break except TimeoutError: + if not self.loader._port.isOpen(): + self.loader._port.open() pass try: KFlash.log('.', end='') @@ -1302,6 +1379,8 @@ class KFlash: KFlash.log(INFO_MSG,"Automatically detected goD",BASH_TIPS['DEFAULT']) break except TimeoutError: + if not self.loader._port.isOpen(): + self.loader._port.open() pass try: # Magic, just repeat, don't remove, it may unstable, don't know why. @@ -1313,6 +1392,8 @@ class KFlash: KFlash.log(INFO_MSG,"Automatically detected goE/kd233",BASH_TIPS['DEFAULT']) break except TimeoutError: + if not self.loader._port.isOpen(): + self.loader._port.open() pass except Exception as e: KFlash.log() @@ -1331,28 +1412,36 @@ class KFlash: # 2. download bootloader and firmware if args.sram: - if file_format == ProgramFileFormat.FMT_KFPKG: - err = (ERROR_MSG, "Unable to load kfpkg to SRAM") - err = tuple2str(err) - raise_exception( Exception(err) ) - elif file_format == ProgramFileFormat.FMT_ELF: - self.loader.load_elf_to_sram(firmware_bin) - else: - self.loader.install_flash_bootloader(firmware_bin.read()) + with open(args.firmware, 'rb') as firmware_bin: + if file_format == ProgramFileFormat.FMT_KFPKG: + err = (ERROR_MSG, "Unable to load kfpkg to SRAM") + err = tuple2str(err) + raise_exception( Exception(err) ) + elif file_format == ProgramFileFormat.FMT_ELF: + self.loader.load_elf_to_sram(firmware_bin) + else: + self.loader.install_flash_bootloader(firmware_bin.read()) else: # install bootloader at 0x80000000 - isp_loader = open(args.bootloader, 'rb').read() if args.bootloader else ISP_PROG + if args.bootloader: + with open(args.bootloader, 'rb') as f: + isp_loader = f.read() + else: + isp_loader = ISP_PROG self.loader.install_flash_bootloader(isp_loader) # Boot the code from SRAM self.loader.boot() - if args.sram: # Dangerous, here are dinosaur infested!!!!! # Don't touch this code unless you know what you are doing self.loader._port.baudrate = args.baudrate KFlash.log(INFO_MSG,"Boot user code from SRAM", BASH_TIPS['DEFAULT']) if(args.terminal == True): + try: + self.loader._port.close() + except Exception: + pass open_terminal(False) msg = "Burn SRAM OK" raise_exception( Exception(msg) ) @@ -1376,11 +1465,14 @@ class KFlash: if file_format == ProgramFileFormat.FMT_KFPKG: KFlash.log(INFO_MSG,"Extracting KFPKG ... ", BASH_TIPS['DEFAULT']) - firmware_bin.close() with tempfile.TemporaryDirectory() as tmpdir: try: with zipfile.ZipFile(args.firmware) as zf: zf.extractall(tmpdir) + if not os.path.exists(os.path.join(tmpdir, "flash-list.json")): + err = (ERROR_MSG,'Can not find flash-list.json in kfpkg root dir',BASH_TIPS['DEFAULT']) + err = tuple2str(err) + raise_exception( Exception(err) ) except zipfile.BadZipFile: err = (ERROR_MSG,'Unable to Decompress the kfpkg, your file might be corrupted.',BASH_TIPS['DEFAULT']) err = tuple2str(err) @@ -1396,14 +1488,36 @@ class KFlash: with open(os.path.join(tmpdir, lBinFiles["bin"]), "rb") as firmware_bin: self.loader.flash_firmware(firmware_bin.read(), None, int(lBinFiles['address'], 0), lBinFiles['sha256Prefix'], filename=lBinFiles['bin']) else: - if args.key: - aes_key = binascii.a2b_hex(args.key) - if len(aes_key) != 16: - raise_exception( ValueError('AES key must by 16 bytes') ) - - self.loader.flash_firmware(firmware_bin.read(), aes_key=aes_key) + if args.firmware == "erase": + if args.addr.lower().startswith("0x"): + addr = int(args.addr, base=16) + else: + addr = int(args.addr) + if args.length.lower() == "all": + addr = 0 + length = 0xFFFFFFEE + KFlash.log(INFO_MSG,"erase all") + else: + if args.length.lower().startswith("0x"): + length = int(args.length, base=16) + else: + length = int(args.length) + KFlash.log(INFO_MSG,"erase '0x{:x}' - '0x{:x}' ({}B, {:.02}KiB, {:.02}MiB)".format(addr, addr+length, length, length/1024.0, length/1024.0/1024.0)) + if ((addr % 4096) != 0) or ( length != 0xFFFFFFEE and (length % 4096) != 0) or addr < 0 or addr > 0x01000000 or length < 0 or ( length > 0x01000000 and length != 0xFFFFFFEE): + err = (ERROR_MSG,"erase flash addr or length error, addr should >= 0x00000000, and length should >= 4096 or 'all'") + err = tuple2str(err) + raise_exception( Exception(err) ) + self.loader.flash_erase(addr, length) else: - self.loader.flash_firmware(firmware_bin.read()) + with open(args.firmware, 'rb') as firmware_bin: + if args.key: + aes_key = binascii.a2b_hex(args.key) + if len(aes_key) != 16: + raise_exception( ValueError('AES key must by 16 bytes') ) + + self.loader.flash_firmware(firmware_bin.read(), aes_key=aes_key) + else: + self.loader.flash_firmware(firmware_bin.read()) # 3. boot if args.Board == "dan" or args.Board == "bit" or args.Board == "trainer":