From dc8f020ee4464e7d1affef7fdf5bbd5a597ba2bf Mon Sep 17 00:00:00 2001
From: Robert Piotrowski <50244265+revoxhere@users.noreply.github.com>
Date: Sun, 20 Oct 2019 17:04:22 +0200
Subject: [PATCH 001/385] Add files via upload
---
Restarter.py | 31 +++++++++++++++++++++++++++++++
autorestart.bat | 5 +++++
2 files changed, 36 insertions(+)
create mode 100644 Restarter.py
create mode 100644 autorestart.bat
diff --git a/Restarter.py b/Restarter.py
new file mode 100644
index 00000000..910831de
--- /dev/null
+++ b/Restarter.py
@@ -0,0 +1,31 @@
+import socket, time, datetime, subprocess
+pool_address = "serveo.net"
+pool_port = "14808"
+
+
+while True:
+ soc = socket.socket()
+ try:
+ soc.connect((pool_address, int(pool_port)))
+ try:
+ soc.settimeout(1.0)
+ resp = soc.recv(1024).decode()
+ soc.settimeout(None)
+ if resp == "0.6":
+ now = datetime.datetime.now()
+ print(now.strftime("[%Y-%m-%d %H:%M:%S] ") + "Server is up and running [OK] on tcp://"+pool_address+":"+pool_port)
+ else:
+ now = datetime.datetime.now()
+ print(now.strftime("[%Y-%m-%d %H:%M:%S] ") + "> Server is running but SSH not! [ERROR] Restarting!")
+ subprocess.call([r'C:\Users\revox.lola\Desktop\Server\autorestart.bat'])
+ except:
+ now = datetime.datetime.now()
+ print(now.strftime("[%Y-%m-%d %H:%M:%S] ") + ">>>>> Server is running but SSH not! [ERROR] Restarting!")
+ subprocess.call([r'C:\Users\revox.lola\Desktop\Server\autorestart.bat'])
+ except:
+ now = datetime.datetime.now()
+ print(now.strftime("[%Y-%m-%d %H:%M:%S] ") + ">>> Server is down! [ERROR] Restarting!")
+ subprocess.call([r'C:\Users\revox.lola\Desktop\Server\autorestart.bat'])
+
+ time.sleep(10)
+
diff --git a/autorestart.bat b/autorestart.bat
new file mode 100644
index 00000000..1e77cb57
--- /dev/null
+++ b/autorestart.bat
@@ -0,0 +1,5 @@
+@echo off
+echo Closing down all SSH servers...
+TASKKILL /F /IM "ssh.exe"
+TASKKILL /F /IM "ssh.exe"
+TASKKILL /F /IM "ssh.exe"
\ No newline at end of file
From 5c37378726ac8158946e0e2e9dc76a99a2d08681 Mon Sep 17 00:00:00 2001
From: Robert Piotrowski <50244265+revoxhere@users.noreply.github.com>
Date: Sun, 20 Oct 2019 17:04:58 +0200
Subject: [PATCH 002/385] Delete Wallet.py
---
Wallet.py | 294 ------------------------------------------------------
1 file changed, 294 deletions(-)
delete mode 100644 Wallet.py
diff --git a/Wallet.py b/Wallet.py
deleted file mode 100644
index 625e9d4e..00000000
--- a/Wallet.py
+++ /dev/null
@@ -1,294 +0,0 @@
-#!/usr/bin/env python
-
-###########################################
-# Duino-Coin wallet version 0.6.1 alpha #
-# https://github.com/revoxhere/duino-coin #
-# copyright by revox 2019 #
-###########################################
-
-import time, socket, sys, os, configparser, tkinter, webbrowser
-import threading
-from tkinter import messagebox
-from tkinter import *
-from pathlib import Path
-
-#setting variables
-users = {}
-status = ""
-host = 'serveo.net' #official server ip
-port = 14808 #official server port
-s = socket.socket()
-config = configparser.ConfigParser()
-
-def Signup(): #signup definition
- global pwordE
- global pwordconfirm
- global nameE
- global roots
- window.destroy()
- roots = Tk() #register window
- roots.resizable(False, False)
- roots.title('Register')
-
- nameL = Label(roots, text='Username: ')
- pwordL = Label(roots, text='Password: ')
- pwordconfirm = Label(roots, text='Confirm Password: ')
- nameL.grid(row=1, column=0, sticky=W)
- pwordL.grid(row=2, column=0, sticky=W)
- pwordconfirm.grid(row=3, column=0, sticky=W)
-
- nameE = Entry(roots)
- pwordE = Entry(roots, show='*')
- pwordconfirm = Entry(roots, show='*')
- nameE.grid(row=1, column=1)
- pwordE.grid(row=2, column=1)
- pwordconfirm.grid(row=3, column=1)
-
- signupButton = Button(roots, text='Register account', command=FSSignup)
- signupButton.grid(columnspan=2, sticky=W)
- roots.mainloop()
- SelectScr()
-
-def FSSignup(): #signup server communication section
- username = nameE.get()
- passwordconfirm = pwordconfirm.get()
- password = pwordE.get()
- if password == passwordconfirm:
- s.send(bytes("REGI,"+username+","+password, encoding='utf8')) #send register request to server
- key = s.recv(3)
- key=key.decode()
- time.sleep(0.025)
- key = s.recv(2)
- key = key.decode()
- if key == "OK":
- messagebox.showinfo("Success!", "Successfully registered user "+username+".\nRestart your wallet and login.")
- roots.destroy()
- while True:
- time.sleep(99)
- if key == "NO":
- messagebox.showerror("Error!", "User "+username+" is already registered or you've used non-allowed characters!\nPlease try again!")
- roots.destroy()
- Signup()
- else:
- roots.destroy()
- Signup()
-
-def Login(): #login window
- window.destroy()
- global nameEL
- global pwordEL
- global rootA
-
- rootA = Tk() #login window
- rootA.resizable(False, False)
- rootA.title('Login')
-
- nameL = Label(rootA, text='Username: ')
- pwordL = Label(rootA, text='Password: ')
- nameL.grid(row=1, sticky=W)
- pwordL.grid(row=2, sticky=W)
-
- nameEL = Entry(rootA)
- pwordEL = Entry(rootA, show='*')
- nameEL.grid(row=1, column=1)
- pwordEL.grid(row=2, column=1)
-
- loginB = Button(rootA, text='Login to account', command=CheckLogin)
- loginB.grid(columnspan=2, sticky=W)
-
- rootA.mainloop()
- SelectScr()
-
-def CheckLogin(): #login server communication section
- username = nameEL.get()
- password = pwordEL.get()
- s.send(bytes("LOGI,"+username+","+password, encoding='utf8')) #send login request to server
-
- key = s.recv(3)
- time.sleep(0.025)
- key = s.recv(2)
- key = key.decode()
- if key == "OK":
- messagebox.showinfo("Success", "Successfully logged in!\nYour login data will be automatically remembered!\nPlease restart your wallet.")
- config['wallet'] = {"username": username,
- "password": password}
- with open("WalletConfig.ini", "w") as configfile:
- config.write(configfile)
- rootA.destroy()
- WalletScr()
-
- if key == "NO":
- messagebox.showerror("Error!", "Incorrect credentials!\n Please try again!")
- rootA.destroy()
- Login()
-
- Login()
-
-def SelectScr(): #first-time launch window
- global window
- window = tkinter.Tk()
- window.geometry("355x200")
- window.resizable(False, False)
- window.title("Duino-Coin wallet")
-
- label = tkinter.Label(window, text = "").pack()
- label = tkinter.Label(window, text = " Welcome to the Duino-Coin wallet!", font="-weight bold").pack()
- label = tkinter.Label(window, text = " It looks like it's your first time launching this program. ").pack()
- label = tkinter.Label(window, text = " Do you have an Duino-Coin account?").pack()
- label = tkinter.Label(window, text = "").pack()
- tkinter.Button(window, text = " Yes, login me! ", command = Login).pack()
- tkinter.Button(window, text = " No, register me!", command = Signup).pack()
- label = tkinter.Label(window, text = "").pack()
- label = tkinter.Label(window, text = " 2019 Duino-Coin developers").pack()
-
- window.mainloop()
-
-def WalletScr():
- global username
- print("Using pre-defined settings")
- config.read("WalletConfig.ini")
- username = config["wallet"]["username"]
- password = config["wallet"]["password"]
- print("Pre-defined settings:", username, password)
-
- s.send(bytes("LOGI,"+username+","+password, encoding='utf8'))
- key = s.recv(3)
- time.sleep(0.025)
- key = s.recv(2)
- key=key.decode()
- print(key)
- if key == "OK":
- print("ok")
- WalletWindow()
- if key == "NO":
- messagebox.showerror("Error!","Error in pre-defined credentials!\nRemoving them and again starting login select window")
- os.remove("WalletConfig.ini")
- SelectScr()
-
-def FSSSend():
- global receipent
- global fsssend
- global amount
- sendit = "OK"
- receipent = receipentA.get()
- amount = amountA.get()
-
- if amount.isupper() or amount.islower():
- sendit = "NO"
- print("Amount contains letters!")
- messagebox.showerror("Error!","Incorrect amount!")
- send.destroy()
- if amount == "0":
- sendit = "NO"
- print("Can't send 0!")
- messagebox.showerror("Error!","Incorrect amount!")
- send.destroy()
- if sendit =="OK":
- print("Sending ", amount, " funds from:", username, "to", receipent)
- s.send(bytes("SEND,"+username+","+receipent+","+amount, encoding='utf8')) #send sending funds request to server
- time.sleep(0.1)
- message = s.recv(1024).decode('utf8')
- message = ''.join([i for i in message if not i.isdigit()]) #data formatting
- messagebox.showinfo("Server message", message) #print server message
- WalletWindow()
- send.destroy()
- else:
- WalletWindow()
- send.destroy()
-
-def Send():
- global amountA
- global receipentA
- global send
- send = Tk() #sending funds window
- send.resizable(False, False)
- send.title('Send funds')
-
- label = tkinter.Label(send, text = "Your balance: "+balance+" DUCO")
- receipentA = Label(send, text="Receipents' username: ")
- amountA = Label(send, text='Amount: ')
- receipentA.grid(row=1, column=0, sticky=W)
- amountA.grid(row=2, column=0, sticky=W)
- label.grid(row=0, column=0, sticky=W)
-
- receipentA = Entry(send)
- amountA = Entry(send)
- receipentA.grid(row=1, column=1)
- amountA.grid(row=2, column=1)
-
-
- signupButton = Button(send, text='Send funds', command=FSSSend)
- signupButton.grid(columnspan=2, sticky=W)
- send.mainloop
-
-def Receive(): #receiving funds help dialog
- messagebox.showinfo("Receive funds", "To receive funds, instruct others to send money to your username ("+username+").")
- pass
-
-def About():
- global about
-
- about = Tk() #about window
- about.resizable(False, False)
- about.geometry("300x100")
- about.title('About')
-
- label = tkinter.Label(about, text = "Official Duino-Coin wallet", font="-weight bold").pack()
- label = tkinter.Label(about, text = "Wallet version: 0.6.1 alpha").pack()
- label = tkinter.Label(about, text = "Made by revox from Duino-Coin developers").pack()
- label = tkinter.Label(about, text = "Learn more at: github.com/revoxhere/duino-coin").pack()
-
-def Exchange():
- webbrowser.open_new_tab("https://revoxhere.github.io/duco-exchange/")
- pass
-
-def getBalance():
- global balance
- s.send(bytes("BALA", encoding='utf8'))
- time.sleep(0.025)
- balance = s.recv(6)
- balance = balance.decode('utf8')
- print("Got balance from server:", balance)
-
-
-def WalletWindow():
- getBalance()
- global wallet
-
- print("Displaying main wallet window")
- wallet = tkinter.Tk()
- wallet.geometry("320x250")
- wallet.resizable(False, False)
- wallet.title("Duino-Coin wallet")
-
- label = tkinter.Label(wallet, text = "Official Duino-Coin wallet", font="-weight bold").place(relx=.5, rely=.09, anchor="c")
- label = tkinter.Label(wallet, text = "").grid()
- label = tkinter.Label(wallet, text = "Your balance: "+balance+" (DUCO)").place(relx=.5, rely=.2, anchor="c")
-
- tkinter.Button(wallet, text = " Send funds ", command = Send).place(relx=0.355, rely=0.30)
- tkinter.Button(wallet, text = " Receive funds ", command = Receive).place(relx=0.353, rely=0.45)
- tkinter.Button(wallet, text = " Exchange DUCO ", command = Exchange).place(relx=0.35, rely=0.60)
- tkinter.Button(wallet, text = " About ", command = About).place(relx=0.35, rely=0.75)
-
- label = tkinter.Label(wallet, text = "2019 Duino-Coin developers").place(relx=0.46, rely=0.91)
-
- wallet.mainloop()
-
-def Start():
- try:
- s.connect((host, port))
- print("Connected to the server")
- if not Path("WalletConfig.ini").is_file():
- SelectScr()
- else:
- WalletScr()
- except:
- root = tkinter.Tk()
- root.withdraw()
- messagebox.showerror("Error!","Server communication failed!\nA server update is probably underway.\nPlease try again in a couple of hours.")
- sys.exit()
-
-Start()
-
-
-
From a8b659b00505860707f2feeb06823e620ab73f13 Mon Sep 17 00:00:00 2001
From: Robert Piotrowski <50244265+revoxhere@users.noreply.github.com>
Date: Sun, 20 Oct 2019 17:05:08 +0200
Subject: [PATCH 003/385] Delete Server.py
---
Server.py | 554 ------------------------------------------------------
1 file changed, 554 deletions(-)
delete mode 100644 Server.py
diff --git a/Server.py b/Server.py
deleted file mode 100644
index 973a827b..00000000
--- a/Server.py
+++ /dev/null
@@ -1,554 +0,0 @@
-#!/usr/bin/env python
-
-###########################################
-# Duino-Coin public-server version 0.6.3 #
-# https://github.com/revoxhere/duino-coin #
-# copyright by MrKris7100 & revox 2019 #
-###########################################
-# Important: this version of the server is a bit different than one used in "real" duino-coin network.
-# !!! If you want to host the pool/server yourself, you need to firstly install 'psutil' using pip install psutil !!!
-# !!! If you want to host the pool/server yourself, you need to firstly install 'PyGithub' using pip install PyGithub !!!
-
-VER = "0.6"
-
-import socket, threading, time, random, hashlib, math, datetime, re, configparser, sys, errno, os, psutil, string
-from pathlib import Path
-from github import Github
-
-def ServerLog(whattolog):
- #Getting actual date and time
- now = datetime.datetime.now()
- #Creating and opening today's log file
- logfile = open(now.strftime("logs/%Y-%m-%d.txt"), "a")
- #Time formating
- now = now.strftime("[%Y-%m-%d %H:%M:%S] ")
- #Writing message and closing file
- logfile.write(now + whattolog + "\n")
- logfile.close()
-
-def ServerLogHash(whattolog): #Separate serverlog section for mining section debugging. Not used in proper pool to reduce disk usage.
- #Getting actual date and time
- #now = datetime.datetime.now()
- #Creating and opening today's log file
- #logfile = open(now.strftime("logs/%Y-%m-%d.txt"), "a")
- #Time formating
- #now = now.strftime("[%Y-%m-%d %H:%M:%S] ")
- #Writing message and closing file
- #logfile.write(now + whattolog + "\n")
- #logfile.close()
- pass
-
-def UpdateServerInfo():
- global server_info, hashrates, threads, diff, update_count, gitrepo, gitusername
- now = datetime.datetime.now()
- now = now.strftime("%H:%M:%S")
- #Nulling pool hashrate stat
- server_info['pool_hashrate'] = 0
- #Counting registered users
- server_info['users'] = len(os.listdir('users'))
- #Addition miners hashrate and update pool's hashrate
- for hashrate in hashrates:
- server_info['pool_hashrate'] += hashrates[hashrate]["hashrate"]
- #Preparing json data for API
- data = {"pool_miners" : server_info["miners"], "pool_hashrate" : server_info["pool_hashrate"], "users" : server_info["users"], "miners" : {}}
- #Adding mining users to API's output
- for hashrate in hashrates:
- data["miners"][hashrate] = hashrates[hashrate]
- #Writing data to text API
- file = open("config/api.txt", "w")
- file.write(str("Pool hashrate: "))
- file.write(str(int((server_info['pool_hashrate']) / 1000)))
- file.write(str(" kH/s\n"))
- file.write(str("Pool workers: "))
- file.write(str(server_info["miners"]))
- file.write(str(" ("))
- file.write(str(' '.join([hashrates[x]["username"] for x in hashrates])))
- file.write(str(")\n"))
- with locker:
- blok = open("config/blocks", "r")
- bloki = blok.readline()
- file.write(str("Mined blocks: " + bloki))
- blok.close()
- file.write(str("\n"))
- file.write(str("Last block #: "))
- lastblok = open("config/lastblock", "r+")
- lastblokid = lastblok.readline().rstrip("\n\r ")[:10] + (lastblok.readline().rstrip("\n\r ")[10:] and '..')
- file.write(str(lastblokid))
- lastblok.close()
- file.write(str(" [...]"))
- file.write(str("\nCurrent difficulty: "))
- diff = math.ceil(int(bloki) / diff_incrase_per)
- file.write(str(diff))
- file.write(str("\nReward: "))
- file.write(str(reward))
- file.write(str(" DUCO/block"))
- file.write(str("\nDUCO/XMG: 9.82 (\/)"))
- file.write(str("\nLast updated: "))
- file.write(str(now))
- file.write(str(" (updated every 90s)"))
- file.close() #End of API file writing
- #Update api file on GitHub
- try: #Create new file if it doesn't exist
- repo.create_file("api.txt", "test", "test", branch="master")
- ServerLog("File didn't exist on GitHub repo, created it!")
- except:
- pass
- file_contents = Path("config/api.txt").read_text() #Get api file contents
- update_count = update_count + 1 #Increment update counter by 1
- repo = g.get_repo("revoxhere/"+gitrepo)
- contents = repo.get_contents("api.txt") #Get contents of previous file for SHA verification
- repo.update_file(contents.path, "Statistics update #"+str(update_count), str(file_contents), contents.sha, branch="master") #Post statistics file into github
- ServerLog("Updated statistics file on GitHub. Update count:"+str(update_count))
- #Wait 90 seconds and repeat
- threading.Timer(90, UpdateServerInfo).start()
-
-def randomString(stringLength=10):
- #Generating random string with specified length
- letters = string.ascii_lowercase
- return ''.join(random.choice(letters) for i in range(stringLength))
-
-class InputProc(threading.Thread):
- def run(self):
- proc = psutil.Process()
- while True:
- #Waiting for input command in server console
- cmd = input(">")
- #Parsing commands
- if cmd.find(" ") != -1:
- cmd = cmd.split(" ")
- if cmd[0] == "list":
- #Listing all registered users on screen
- if cmd[1] == "users":
- print(' '.join([os.path.splitext(x)[0] for x in os.listdir('users')]))
- #Listing all connected users on screen
- elif cmd[1] == "miners":
- print(' '.join([hashrates[x]["username"] for x in hashrates]))
- if cmd[0] == "kick":
- #Kick (disconnect) specified user
- kicklist.append(cmd[1])
- if cmd[0] == "ban":
- #Ban and kick specified user
- kicklist.append(cmd[1])
- #Just changing user's password to random string
- file = open("users/" + cmd[1] + ".txt", "w")
- file.write(randomString(32))
- file.close()
- else:
- if cmd == "kickall":
- #Kicking (disconnecting) all connected users
- kicklist.append(-1)
- if cmd == "serverinfo":
- #Displaying server's info like version, resource usage, etc.
- print("Duino-Coin server by revox & MrKris7100 from DUCO developers")
- print("Server version: " + VER)
- print("\nConnected GitHub account: "+str(gitusername)+", publishing on: "+str(gitrepo))
- print(" GitHub update count: "+str(update_count))
- print("\nServer resources usage:")
- print(" CPU usage: " + str(proc.cpu_percent()) + "%")
- print(" Memory usage: " + "{:.1f}".format(proc.memory_info()[0] / 2 ** 20) + "MB")
- print("\nConnected miners: " + str(server_info["miners"]))
- print("Server hashrate: " + str(server_info["pool_hashrate"]) + " H/s")
- print("Registered users count: " + str(server_info["users"]))
- with locker:
- file = open("config/blocks", "r")
- print("\nMined blocks count: " + file.readline())
- file.close()
- file = open("config/lastblock", "r+")
- print("Last block hash: " + file.readline())
- file.close()
- if cmd == "stop":
- #Shutting down server
- with locker:
- sys.exit()
-
-class ClientThread(threading.Thread): #separate thread for every user
- def __init__(self, ip, port, clientsock):
- threading.Thread.__init__(self)
- self.ip = ip
- self.port = port
- self.clientsock = clientsock
- try:
- #Sending server version to client
- clientsock.send(bytes(VER, encoding='utf8'))
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- err = True
- def run(self):
- err = False
- global server_info, hashrates, kicklist, thread_id, diff
- #New user connected
- username = ""
- #Getting thread id for this connection
- thread_id = str(threading.current_thread().ident)
- ServerLog("New thread (" + thread_id + ") started, connection: " + self.ip + ":" + str(self.port))
- while True:
- #Checking "kicklist" for "kickall" command
- if -1 in kicklist:
- del kicklist[:]
- break
- #Checking "kicklist" for this user "kick" command
- elif username in kicklist:
- kicklist.remove(username)
- break
- try:
- #Listening for requests from client
- data = self.clientsock.recv(1024)
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
- try:
- data = data.decode()
- data = data.split(",")
- except:
- pass
-
- #Recived register request
- if data[0] == "REGI":
- username = data[1]
- password = data[2]
- server_info['miners'] += 1
- hashrates[thread_id] = {"username" : username, "hashrate" : 0}
- ServerLog("Client "+str(username)+" has requested account registration.")
- #Checking username for unallowed characters
- if re.match(regex,username):
- #Checking if user already exists
- if not Path("users/" + username + ".txt").is_file():
- #If user dosen't exist, saving his password and setting up balance
- file = open("users/" + username + ".txt", "w")
- file.write(password)
- file.close()
- file = open("balances/" + username + ".txt", "w")
- file.write(str(new_user_balance))
- file.close
- self.clientsock.send(bytes("OK", encoding='utf8'))
- ServerLog("New user (" + username + ") registered")
- else:
- #User arleady exists
- ServerLog("Account already exists!")
- self.clientsock.send(bytes("NO", encoding='utf8'))
- break
- else:
- #User used unallowed characters, disconnecting
- ServerLog("Unallowed characters!!!")
- self.clientsock.send(bytes("NO", encoding='utf8'))
- break
-
- #Recived login request
- elif data[0] == "LOGI": #login
- username = data[1]
- password = data[2]
- server_info['miners'] += 1
- hashrates[thread_id] = {"username" : username, "hashrate" : 0}
- ServerLog("Client request logging in to account " + username)
- #Checking username for unallowed characters
- if re.match(regex,username):
- #Checking that user exists
- try:
- #User exists, reading his password
- file = open("users/" + username + ".txt", "r")
- data = file.readline()
- file.close()
- except:
- #Doesnt exist, disconnect
- ServerLog("User doesn't exist!")
- self.clientsock.send(bytes("NO", encoding='utf8'))
- break
- #Comparing saved password with recived password
- if password == data:
- #Password matches
- self.clientsock.send(bytes("OK", encoding='utf8'))
- ServerLog("Password matches, user logged")
- #Updating statistics username
- hashrates[thread_id]["username"] = username
- else:
- #Bad password, disconneting
- ServerLog("Incorrect password")
- self.clientsock.send(bytes("NO", encoding='utf8'))
- break
- else:
- #User doesn't exists
- ServerLog("User doesn't exist!")
- self.clientsock.send(bytes("NO", encoding='utf8'))
- break
-
- #Client requested new job for him
- elif username != "" and data[0] == "JOB": #main, mining section
- ServerLog("New job for user: " + username)
- #Waiting for unlocked files then locking them
- with locker:
- #Reading blocks amount
- file = open("config/blocks", "r")
- blocks = int(file.readline())
- file.close()
- #Reading lastblock's hash
- file = open("config/lastblock", "r+")
- lastblock = file.readline()
- #Calculating difficulty
- diff = math.ceil(blocks / diff_incrase_per)
- rand = random.randint(0, 100 * diff)
- #Generating next block hash
- hashing = hashlib.sha1(str(lastblock + str(rand)).encode("utf-8"))
- ServerLogHash("Sending target hash: " + hashing.hexdigest())
- try:
- #Sending target hash to miner
- self.clientsock.send(bytes(lastblock + "," + hashing.hexdigest() + "," + str(diff), encoding='utf8'))
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
- #Updating lastblock's hash
- file.seek(0)
- file.write(hashing.hexdigest())
- file.truncate()
- file.close()
- try:
- #Waiting until client solves hash
- response = self.clientsock.recv(1024).decode()
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
- #0.5.1 version compatibility
- if response.find(",") != -1:
- try:
- response = response.split(",")
- result = response[0]
- hashrates[thread_id]["hashrate"] = int(response[1])
- except:
- pass
- else:
- try:
- result = response
- hashrates[thread_id]["hashrate"] = 1000 #1kH/s if none submitted
- except:
- pass
- #Checking recived result is good hash
- if result == str(rand):
- ServerLogHash("Recived good result (" + str(result) + ")")
- #Rewarding user for good hash
- with locker: #Using locker because some users submitted problems when mining on many devices and weird things happened
- bal = open("balances/" + username + ".txt", "r")
- balance = str(float(bal.readline())).rstrip("\n\r ")
- balance = float(balance) + float(reward)
- bal = open("balances/" + username + ".txt", "w")
- bal.seek(0)
- bal.write(str(balance))
- bal.truncate()
- bal.close()
- try:
- self.clientsock.send(bytes("GOOD", encoding="utf8"))
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
- #Waiting fo unlocked files then lock them
- with locker:
- #Update amount of blocks
- blocks+= 1
- blo = open("config/blocks", "w")
- blo.seek(0)
- blo.write(str(blocks))
- blo.truncate()
- blo.close()
- else:
- #Recived hash is bad
- ServerLogHash("Recived bad result (" + str(result[0]) + ")")
- try:
- self.clientsock.send(bytes("BAD", encoding="utf8"))
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
-
- #Client requested account balance checking
- elif username != "" and data[0] == "BALA": #check balance section
- ServerLog("Client request balance check")
- file = open("balances/" + username + ".txt", "r")
- balance = file.readline()
- file.close()
- try:
- self.clientsock.send(bytes(balance, encoding='utf8'))
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
-
- #Close connection request, may be used in the future
- elif username != "" and data[0] == "CLOSE":
- try:
- ServerLog("Client requested thread (" + thread_id + ") closing")
- print("Klouz")
- #Closing socket connection
- self.clientsock.close()
- #Decrasing number of connected miners
- server_info['miners'] -= 1
- #Delete this miner from statistics
- del hashrates[thread_id]
- except:
- ServerLog("Error closing connection (" + thread_id + ")!")
-
- elif username != "" and data[0] == "SEND": #sending funds section
- sender = username
- reciver = data[2]
- amount = float(data[3])
- ServerLog("Client request transfer funds")
- #now we have all data needed to transfer money
- #firstly, get current amount of funds in bank
- try:
- file = open("balances/" + sender + ".txt", "r+")
- balance = float(file.readline())
- except:
- ServerLog("Can't checks sender's (" + sender + ") balance")
- #verify that the balance is higher or equal to transfered amount
- if amount > balance:
- try:
- self.clientsock.send(bytes("Error! Your balance is lower than amount you want to transfer!", encoding='utf8'))
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
- else: #if ok, check if recipient adress exists
- if Path("balances/" + reciver + ".txt").is_file():
- #it exists, now -amount from sender and +amount to reciver
- try:
- #remove amount from senders' balance
- balance -= amount
- file.seek(0)
- file.write(str(balance))
- file.truncate()
- file.close
- #get recipients' balance and add amount
- file = open("balances/" + reciver + ".txt", "r+")
- reciverbal = float(file.readline())
- reciverbal += amount
- file.seek(0)
- file.write(str(reciverbal))
- file.truncate()
- file.close()
- try:
- self.clientsock.send(bytes("Successfully transfered funds!!!", encoding='utf8'))
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
- ServerLog("Transferred " + str(amount) + " DUCO from " + sender + " to " + reciver)
- except:
- try:
- self.clientsock.send(bytes("Unknown error occured while sending funds.", encoding='utf8'))
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
- else: #message if recipient doesn't exist
- ServerLog("The recepient", reciver, "doesn't exist!")
- try:
- self.clientsock.send(bytes("Error! The recipient doesn't exist!", encoding='utf8'))
- except socket.error as err:
- if err.errno == errno.ECONNRESET:
- break
- try:
- ServerLog("Thread (" + thread_id + ") and connection closed")
- #Closing socket connection
- self.clientsock.close()
- #Decrasing number of connected miners
- server_info['miners'] -= 1
- #Delete this miner from statistics
- del hashrates[thread_id]
- except:
- ServerLog("Error closing connection (" + thread_id + ")!")
-
-
-regex = r'^[\w\d_()]*$'
-tcpsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-tcpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-threads = []
-kicklist = []
-server_info = {'miners' : 0, 'pool_hashrate' : 0, 'users' : 0}
-hashrates = {}
-config = configparser.ConfigParser()
-locker = threading.Lock()
-update_count = 0
-
-#Initial files and folders checking and making
-if not Path("logs").is_dir():
- os.mkdir("logs")
-if not Path("config").is_dir():
- os.mkdir("config")
-if not Path("users").is_dir():
- os.mkdir("users")
-if not Path("balances").is_dir():
- os.mkdir("balances")
-if not Path("config/lastblock").is_file():
- file = open("config/lastblock", "w")
- file.write(hashlib.sha1(str("revox.heremrkris7100").encode("utf-8")).hexdigest()) #First block
- file.close()
-if not Path("config/blocks").is_file():
- file = open("config/blocks", "w")
- file.write("1")
- file.close()
-#Initial configuration
-if not Path("config/config.ini").is_file():
- print("Initial server configuration\n")
- host = input("Enter server host adddress: ")
- port = input("Enter server port: ")
- new_user_balance = input("Enter default balance for new users: ")
- reward = input("Enter default block reward: ")
- diff_incrase_per = input("Enter how many blocks are needed for incrase difficulty: ")
- gitusername = input("Enter GitHub username to push api: ")
- gitpassword = input("Enter GitHub password to push api: ")
- gitrepo = input("Enter GitHub repository name to push api: ")
- config['server'] = {"host": host,
- "port": port,
- "new_user_bal": new_user_balance,
- "reward": reward,
- "diff_incrase_per": diff_incrase_per,
- "gitusername": gitusername,
- "gitpassword": gitpassword,
- "gitrepo": gitrepo}
- with open("config/config.ini", "w") as configfile:
- config.write(configfile)
-#Loading server config from INI if exists
-else:
- config.read("config/config.ini")
- host = config['server']['host']
- port = config['server']['port']
- new_user_balance = config['server']['new_user_bal']
- reward = config['server']['reward']
- diff_incrase_per = config['server']['diff_incrase_per']
- gitusername = config['server']['gitusername']
- gitpassword = config['server']['gitpassword']
- gitrepo = config['server']['gitrepo']
- ServerLog("Loaded server config: " + host + ", " + port + ", " + new_user_balance + ", " + reward + ", " + diff_incrase_per)
-#Converting some variables to numbers
-port = int(port)
-reward = float(reward)
-diff_incrase_per = int(diff_incrase_per)
-#Binding socket
-try:
- tcpsock.bind((host, port))
- ServerLog("TCP server started...")
-except:
- ServerLog("Error during TCP socket!")
- time.sleep(5)
- sys.exit()
-#Logging in to GitHub
-try:
- g = Github(gitusername, gitpassword)
- ServerLog("Logged in to GitHub...")
-except:
- ServerLog("Error logging in to GitHub!")
- time.sleep(5)
- sys.exit()
-#Thread for updating server info api
-UpdateServerInfo()
-#Main server Loop
-ServerLog("Listening for incoming connections...")
-while True:
- #Starting thread for input procesing
- newthread = InputProc()
- newthread.start()
- try:
- #Listening for new connections
- tcpsock.listen(16)
- (conn, (ip, port)) = tcpsock.accept()
- #Starting thread for new connection
- newthread = ClientThread(ip, port, conn)
- newthread.start()
- threads.append(newthread)
- except:
- ServerLog("Error in main loop!")
-
-for t in threads:
- t.join()
From eb69a938150e245d85fcb20c362dbb29cf83d28f Mon Sep 17 00:00:00 2001
From: Robert Piotrowski <50244265+revoxhere@users.noreply.github.com>
Date: Sun, 20 Oct 2019 17:05:23 +0200
Subject: [PATCH 004/385] Delete README.md
---
README.md | 136 ------------------------------------------------------
1 file changed, 136 deletions(-)
delete mode 100644 README.md
diff --git a/README.md b/README.md
deleted file mode 100644
index e6e4f650..00000000
--- a/README.md
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Watch demo »
-
- Join our Discord
-
- View pool statistics
-
- Explore wikis
- •
- Report bugs
-
- Exchange DUCO to other cryptocurrency
-
-
-
-
-
-There are many cryptocurrencies available. Most of them can be mined, but only using powerful CPUs, GPUs or ASICs to make some real money. There aren't many that support "normal" ways of mining. sFdwS)=fQQgA7Yuay`dzv}a&Y}>UzIY^&2+Klpqn#xzWU)B>e`S0dl zoB?u2D?3m?G(Og(t<g}NL$ccY^0?>>I&(QM_{2j{M-?~UCCd72v9+59_r^F{HMI|G4h>mFXR zYUg%P(mE5-a{U_O+c$FDFPCWJWj&Wiggymk%GqD7nNhuxP~uJHPv}`2*O35ll_|A^!a3xbCIN`jpJVee Er7g&*ZdptxrGqE>0sSpkJ$3Zh2doanzI> zxFR?0Mae=uzz{gG)5%*W%R{E%ER*(MF~TGmyqs?2oq~jzEB&ZYMz~FgmW1(CS4V$d zVC?-sKFhhH^3^|og-BWZBm3iTZW1$Kv?pF~>98Z3b|*E85^Vk(g#S+35|oDbeF%>d z*ex*-%h)LGk)GpB=i_!(@YLoNiTf~vcOZXhj+f#sD;WXubjjKJs;~drT*z>w@X@xc zOsXdo8=v|`85J#sQI8Z)?2O%~Q-mx}i<#DEv)Q#Q1Nv3ug0>8(W12{Z-i(3b|IXg& z@tl&J9fVa`SaZ6ZH&fxj4gm(Mfom(ouF*dI1li(bYdMTWVI`7cm!>(+byBJBY*+-N z;1%ORuS-O)Ok!`8`}tNA&Ty#Q2jIfvf;MO5prm^}-dCudUi?VH+*sCC|4_fX#zta8 z>{eBYe8>gu=wzy%QLTSV&CDpYe_wv*eCBgI7KA6Z2|_5VeB5;(ZsFQ@cHed*Ogr54 zI CNQ zAD)z@4=&?Qf|^)aoCx`K4za~#Pk(6hoa#iwU|9&66yCRqc52G(-^z>tJuq?Z-Kf4l zN!8%XAywy#>~mY$IUM<2gB-3>@(99mf_@G79Rvm@tB*VXIV1uAO%sX{rA65~qxhL$ zmV8R*_V5gP{t2m;(Uy=I$OH~AIWod&$+|>@e-*W5HNxO*pG~;z6Tbg(EhvR1(fLS{ z8q+Lu?xQ%Mm2q`@p*i21^c53EJAH0lMq`0k_Ncng(U61H;iK_KWoUX0C 5juKfw4@-R|GJ%Pcc0XwvQTGIOXo_wkv$#Kay2O^ls zAD-E$Bix<7K&3WH%uDR? H3l4UgCfRzC*}CR8)_) zOBcUGa_2m_U?k4NfDX33bjs%=EFa|{p%PVu?!;G44u` >E62Tk${t8V5w?I$JcrT-*SuC&Zlc}zrP@WD7*L~JI+*|CWV!vxTF z`|wjzzBqp0iPdEK;%YmtqGC5Xr(DnkNf*59-+{p+SL{FaUVa{b@@%hz(Zzo>p>@AY z8WjGzywl1{X!_ZNO23}ecnXWET-c<5dc@51Pf63zq%fVt+ZGU^^GT1Af{&J$=N{Wz zW3xqk4!4guRM(Y@HKCI6Ly-X*8zV{+pB%g?jVh-cP8}RVP *>YA8HF0u1^9+Q#|Z-Wt__G4dmzCRUGt-?D~h#7Hh0D`I^i-lKNXu(Ac}n}Tjt zu=pQYD$09aF-P=arXY*|tc)cK+>}s$%=n&MR=LoK!RF`pJgJW`g%|Flg1b_pi~ *jUgzX zFlhUtX1iZHT>i5xS&6Z8W@UFxHSu~TH1Db>zBr%?rk@)Virapt0%|T%k{UacnEy>w zKZsy~F$6WK_x`#2g^4wLLsgNBdM}zML&){hF(z3}Oi(kJ9pX)ma`qEkL894 A1Tqt|^^{~0 zoH0Bkkc-uqg8@Rk$PRSPXTbu&smchY$K22MtBGliXzkd!c9?qAHo){ llpNvE$c-b=>oc8Th& zQJq|I1yS?@PrqGWlm7*vKJqv+s>cqi0~!1xw=(I?9p~JVZ@^%Y4NbWONWxyUU+kxT z<43;hdA$5Lzt%CmALSE^yV6e&C*dYu qjC`Mj#b~W#=jB)UIA2vq{a3uVP^(ItW z4ax}O53(@3Ao_g?;T jjM531M3ca2U$Gk_9WkQh6xfO4I;9rA2#QQe=wQ( zT@DKV#*kE;L#81RZyw<7t8Dw)T=jCbM!>!n{^~d|4acBjwub$z$J7#_%p0nhE2Sdp zC9f~-{m190$Jb{?fmIluGU3TpIcx1YB}6JEY6R}cyAs4ebVQz#MU9fGz&CNTaYE13 zv0@@4uKS+KPWADNFL@@Zz^P@TWMKmi$7@dlLR>J-#PWv1L@NlMdMcE_J7-(*VizuR z0$OT`ZQ5wqy2w~c*z9O^IOVb6H{ap25v;#lI0BYGO8(ulizqLL=VMrcyt)@Q_n_FP zNLwQmXrR_tevjL}v#Udvj*L2G)0prw$H)!g{ldNG;QR0BS`q{2@EBy# & %wrv)z9exe#}%|b-x+%apW0`q)|Uj zFBIWGQEq*6w92$5$%liJ5VhHJ`*zH8<*4Y_9iQ!DHu)69A4kz6+X$yYj`R&5c-SGp zJ#(UPyHqRB*uoAa8Zzn@wMV@$HDKgJF4i^*NdW>?{TpC(>C|HARLs3?*$Zn0{vpOW z*0U9f2tlghN{0TC`KFEh3_b5WrRRS;v8-=oFy9GkR*`(e1Ei+KUp#6Hd<|x|Sc9av zhDA4(-i6EF73z>mp)+&e*z3_VUNalxNtSok1kP n+zdC3N1Q yJGwT2PVxTJ7Q$zlD>aJ^tQ&7aex=ey>miP2_+Ag;D+XinMna5-nj6qCOA z1b}IbS_e920L`o{fw00JmRg?(lG&1oLIF&FZn9j+SRT9ld?l#e&GmhKt*tOI<@q)w z<`(H<;sQ-6LdwNr;f{9emKIAX>NmJ;kS_Pt?BwywpLVrn;*yn^l(m4rRCyyJVJ_Ns z;kBP4Y>D=MTf;%+!$1MlK (lh7hwTP+HXKAaXfC(Rsorg1MIf80Y^1^tjX^KLL z+2>MGKjaV-rlwq=_s@kPz4HUdvGYrAQS<~(rP#Voix|0d{uepXSfG^8}><_Pi8Rt93#2UBMc6;#X2mbyf ztD=IAL|jDh{8_CECJG @SjxDiH{UeR3!i(Wi^6N2Mov4IdHD;VFi!AybJ0L{~@v zdbQ=hbEYR)*3;ZCpUPFi&ChqmiXneY3{fIoj1r)GVW>hZ<%+yL8+PIacbW%wHC7R- zP-Wh=h=)kktGy?L@?RJ04sjRLj(Z?uf($0Tfvw(8)_4 As2_h_BCKM;e}tG2P6>P{k-&uV783!bXMX%{^`}(*6W_T=3f*0Ez79evJ HQOkBc<34Y~9X-twTPy?Z0!AijF)#Z$`l?k*PKiA`s6r$^^$TQLX zhB_YVrpFy0{sAkYL#WYp6WmzRnCBf?i@+abPG~qtqgof=Oe@TUi@bLq&Io)p8t?v6 zs5mvjwB+>9QQ;)B79)+8zGMymIa7_5L=hW!Xza0q-dFv8RHVV3#3`F(Oo5cZ+)POV zS)f?`JuWbK+=I+II(v+ku)q_E-U;5io2d3uA&Br%W=xGKMg%Ozum?LuhD`%&(_ZA! zw`z>EIc-R{Lp=Nx>k{5Jf_VuG{KMLIxzGVrU5knc&f?tt=9)*lB9=@HaU#u4v7k3I ztiSGK @gmXeaM$1TOx|%Wk08z%jp%2g! z>BwdL%5WzWlN^F=RNQw*SAT>L8zdA8! |cF{Ft!r-}r0 z1T^=a`b@msnl-qC`rP@_B}72lCX0{*W1I}wgJed_0X@iH7yjX=kiEEG*B5D1$Q091 zd2@!CyvK#QxKdKy*i$4!VBDMFjJ`x%qzj9YeJ)gE{1|191Ozx@0+0&v*SJzkf*g?s zQNF`H-e;HaNFXxeFFr8a9q@tAzF{D^U3F2M$y~mUf)w^yu$}| `80s)q%dmW@bAnr{wH6ZW$ q@I7PC5GCyVrO3o`aV-Kpw@$P{C?Jo{d zmS6QEQ2qt2(;JfMV^Si^IhpG20wykIM}OBG9!(5cBF$$ipf|y(g0RzhRcPwJW_D)& zCFy|C96*ONZx;WRRTWqbf`yKpVq=VZG(P?v+$z4B5FI@SywH%Rnd~)v-SIL`+8;!R znq?q@znAF5Jnx~}gt)>G(iQc3Q2yr+MSgfCbeBy)S{eXZ^F7U5YB;_7q8&5YK zS8E-b%tL(9)a=#NfI%+RwU=qVm%nU8X#*+`Ot(55oW@_`4Q0r=RR_SK0Ez4m>^ALn zGV5)+&PIE3>WA|L{ 9Eq3uk$)!&>1et6oCR7^uYZ0O}vh(h+K+Ztszi3t11k~V2cI|2Pb?e#wC zRgDpsWbg_Os0hn!MJ2jW7X1rO&ov )dCXjE@I!Ow2gzJZ3w^EBsO& zX3dRsSE2?vus!BL44WGMBPB38+at7i)wi5C%Z-dK>rLw7ay_N_PX9YeF&!KLdj6(j ztSWBIuh+jxl`a{Cnez-WX}(B2M1Od@K39?2eCo63YA%PB` _=c*7)!4Sl)0M&!tNsM%>6r6VW#~0U)mB*5$~}#E*m4 zPxf6uPfO=AOh{y{^&uaNh)>qMdigmXP^N7$p;}l_W}e1L6)Xz;r2&GS`{0drDgPDn zI4Z<;b_sF&9>*q)K~(EMAHM8BetvOHFNm6=;6O;~P58qJ3{lJBdUB}maSC-$-jWV7 z{Xb*4!dg#aRX?aqQ7J-ygJ{*PP*XS{&^l>q5g!*=^?VK>V7zxe%X_2vkrDWSnV*>X z^Qr0P=112)hvlI6{p)2_`kcJX8@FVc+JMq=+%5~)ru4$0)sQl!QE_udC7j?dXs+N5 z$mVqN0L&6-=L?-+cV9O@X5$Kh-NjRPclRNi1G`a_W31EDs#(5*6Vk; z>~N$8v)LPx5PeP!PeBF}v2_r!^jum&A3l5fddF?q8)B_+D;&>ML2phwope;|RGl)V zHQ;QPsZt8vL$Gfd_5YNq!vfU9xTAmg1no42s0JiD_Z>iQt4nnii2d76=?q@j09P>c z@g+S$FABV|kx@!SQa@ cVc)WZbH2j)=f( zWNCJSsteQFxBgIZwA|Kovz@LlhlBI6I+C_L5bf>Qh#+gSW>q`;c@;F4D)foMAbdfT zOXeGKfL!=Pk(7t7a|!cwIgJz!IqGhIsB$ULpF3zwDc>pPVbeVVqsac_LOEOe(G%N+ z=bewXGG;c8DT|@%pF=IM0FG%5SHs6_MGm5IrCvhSV4B%z$PHdxO@#=sy2x+;G5)Rd zr&)>G*fIc-5a~|=wn0*a?IsLm*Q>&!qtQxEuD AmaP;@#XOX+2tD6Y3;2(ESiK zkRGCWI-;O6Phh;7CInVzOy&z$2R`UcQ`s*5_0;~JnrOaHl}m(B>(T!$)`Y~0e$PzT z$am|s@Yqivxz2Z?hNZmxL)d|q|Kx90XiDG8h1DA0*;1~cSFSj3EEf>u+OmbbzY*U~ ze}zutTV@iZ`Hov8cIP*uI~y)h;g8QSX1JSDwzjWJ$v1%m`|NtE51xw7=HA{aF_3*{ zrpaLgrX$pc!gJY0l4M$n+T3Sw0+ ;kG}Q^*Lcqmmb`{bXR51n%4T(F@Z-YrjsGjMX`qmEmYkuQ?urv zH-n`xTBE=DCAL63G?fteP1k7yfl*cy9R8PnJAw3kvHBy`fgIV`BYf5rE+@>mVM ;GGh^x z-=luX76B8K#@Ju*UWKx5R!n|@e_o>b%^k`|9w0(*ewOvw_WCx?e-d3Tg#4Von74j+ zI nE8!Ug4T zp>nkU(+G6IMH3c`8sxZ2u|B73BRv^B@o2eTU9GC3%<^!!nvzVjVI$f-@wUqW z$SyeyoHCoN8t#x{B}L;Ms#)F$0xuhAoz??{O*BiGFOwfIF=}_jae< t6`SGI(H3r#VLQZZn^4=nNB3r1ABZ_M*X_g|AGa`i= zYi08}XbepY>CWQVC3LjvCGqvVYBg0^8g>OejuzPF_Y#}qm1v3Yqn*XOdk^XSIzutF z*(Ks_dUUO`-n3s7aGzq_qQe%_xAVrZP&&7-3eWtJbOpb&v8!%%b6ij#rV_QqE)w!{ z5y+}Rc9!xWn@w5pM>N`0Ll)AIv8E!%1AzNKmWoYI`{-zi#v%wr55LvWa}s`2^d?gA zr=y?snV50DBqw}vHF`W6Wtk`Fb#-Ti!X%HTjavVnbaU~k(}-cnPm1=2Bkm7|6Kf>f z+{~u5CBk^Vau|HBdM|s~y+Ac3G-hRh95tX-vH+=!^dQNM?IQG&NB|d S+rXTi_h zg-?Ge_03}ny2~civQnmx^LYjq1SPsk`a}Xbg3&G&b~xW((+~e>k_UYN5J{J>Q!T&e z-z)Jmba1KZ3HPyc6!!12b*-+!3=QZV1);YAUVX3&$CJfP_C8(8rK#>&i+*tN4Bu zw)&N1yb1jd*)>8dFgNH5WsUFXRrQffDhOTFD}v@-Jv*Y}Rzfqu9hSO8=#!r?o_|Pa z9&gHz5@diH3blj@kj8V?`&Dpk7+%L-3$n9wRNiam@%Aam{k?XTyy <4NY{wOf)y53lLm zaiIp^k?tQj3=MYw$rC`teDRC5{aiR|4cg4n75!krBis|L39AHo#b-p-^T{kd?+-Pt zi){2D-m)h`5)cpSoAGT-KnUyp_}NjIm#6k!^$(F_N8_bi9+y8vLJauriW#5%Qv1kq z$kHY3`Q^P*0v!+<$s{BfjRz}ZDt6bA)EWOd8bWPu1>jO}_?53kU-YdBu3yk@yGZAU zm&X*U;m44l1rygb12$_ZhH9=C$I1w`wNLK$Ef8QMj|r;?Nt^t87p-%I33+$mGF_k< z(pQTWPv~_AG1Kza%LJ!jPz<)`v*XUU^an96}uMnCrSzQO~PiL$@ocB^yfe9IQW+ z^qEtnjt# QYX${ zDRU(uK!O|?1km2>kRk8XKmZB4c_oH-3C&M_Z;L)7tq{}$Y{b3BTcOv4ruoD {TR$R>CH^>jTXJehv$T$s=WFiFgY>ka2?kov z=L@Lbep0_mJd9aIs#y8wW7MC#B>WVRXJb?B&-2C6_4;9Pj)M|1P0jCMIuzF|mFFK^ zFH%B{q7h7@mxC?`zkH#(qbt#Yvq176p`a-9wYwXQPcSBXZb;97%_B0RqY4~r=;a>V zBrO`@%J3Ls_~hHkcKB@TU=497Jp?kDf#CaEk4fXLz_B|;LU^xUr+a$xAQK#;IX!WQ zE2n_a4U}9@q_}imw!LpYNni8jv~@olJUV$=fHwGj9iEOdU!Nl=Hzyz6`T2{o#m9g9 zTB+*R^$_su6z~4*lUoUjbbu*PB!w&P$rTB?5>KZCfWu>sSeFcL2erDMxN9W>hDk=V zi9(Y@sz4-o##g-Xk$OQ7^O5yO0q(o7>Jj#ETim@Zt3&`1O=dMQ;sbYn)PiDwSVLMt zSdrxLI#xC23U0}7h4M;=GQ)pR)V+V&s^So#jmdftWuOZkt&Xl$>Ght?UqjRF|H=#% z|0oGTFcTGtcOFJ8zAJZNZlyHTWk6y;8bUn_z``&AgsFbb1r2vjBt`7#K@szTLh~ z`>1)`jvpY8k&z yh{S#lMX#P#m8L?{KpIrJoeeb`89UOxWHYXT2|CMW+|s`_z) zoa@_BJSSscQ2sj?$-mnuy@f1v#cor6U6&Vq)g5s|baZ&y#M5~u%J0fAeH~i*zwMcP zq6mr3h~qNw_to!FVCm-z;Htw&^^n@xP&s%^+Vcra6TdvO?`j8aXj&>6|1u1TJT%+Q z4Q^`{TXYp+tFC)cM_;Zc(Ga>AEwJZz={<>Xr6Pkd^VviVlOPx+Q4o>Kfnn+?l!we- zqA`gZ)YrXgYW7PBDq&X?aGI9>n)QK#70-&y{H%XAIS~l6&rw3L(snxf=U``nXAlyk zyvw8oQYSUxYV~$kwdu`sgX5H?Q){%CCJ{5PKT!=XN!D&qaQ&hTrc=pBAcj7SFwvC8 zr^L<@X>59#Dvqv7gft{$CxKh;7GoM38iXe=+4}lMw?Z!O)?qx$o*BOtp|h1&}-` zx#48xpc#8+X(2dd-^r;VY(2Y~FJai)KBeLs*C!@tc1acJAkAu*$aZ06d|7RJ+%4{T zm81FtlCg0ZEY@wW3Z_T$|FmC;P@{7ol%Dis>w;>#O$yRU>d$p|R=hrQfj}MV{;a6) z%d^3u!NrL#lNy9|DU`e6bn^aJGuoHf)a)qL+7DA>?RocpnhCDAGQZS=wAZg0tb>H% zN8u$NDUcxZ0chMp*b-d7wV)*EL3BQ)sCSv*`OS#J&`^mCDXKcSa35+6&MbaWE$JoI zs&YuHdJzGsAdws0R%30fwz-AH-$+}kArXi`4YDeU2rje7-BTsIwwRX604tREnXYVj zV-K%EW?>TP=LTdfFVOuc0*yx0xE+PkG~?nsqt%Lu)RzV$f}+oz62~G%9Tl~C9cF Cp+(vlyvi^- zKkI0JL(O5u)gGb!3?hQtV_5fcGu^@v&t-x{;gKHiAWov#hTU*v@sw%+KpDUyMETbJ zx%h5>WBuTU!G)k_AIy?wEbPpBwtRWcPbT`PKOd2Z4-!Lv?{9O$*nEEyvCN*cA`fpI zN;S|@G(ulM17%AZ{f%?mvGV3_zc$V7!Lv6wCT!xR=qAgL{d*n{T~$=DsikC)5HS)} zIhz;=mguZD7yqaXl~Ti?8$JNEW1-faM1hB%AWe8zvKV@C0rNvd$4Nv_inp4>tCr z>Ww=P^_#<_9iUh^yv=ewi4cFi`l4< KjEt`hXj^7P!Un5V^*k(<6R zNw*d1vU<~P6=hTsI+6&@>eS&+8tPqq;d7qfPT}KVlmG5zXvtUdRK!A9`0EFE7YZva z{XsE09;^m9{FGHO&}nE81Qg8{1mF8WRY5z!0kMeE6FAde9LKvXuTj>oJ+l+j8dZ3S zgJJLQk{(fF{Sb-$Sc6~jy7pC>@yWT|{d`2hwG=HBg01dLt$Ss+vq=q<&}KB`nvc^I z-!^#4a-1=5mWO-IFCm=)wuF+vcMlzCXbI8HED!s&=-BA!cdb}v{R7W!thro{H=|Vg z1DHtlu@6%wd5IaTA#BAZEoa-eP5o=7D*9qimwqbi#oa(^oG!GHFJWS}UP(&FPhk*| zC%BeMXw`bBPRdN9>LMi9kAZ5CgT?{hDF=Mc#r7=S@y+>XkKdKH(rkq`iBX;h7C19% z#&y&Vl%zr) MDGOUJr+y}KCTl|>QBSH( hx9fyPG;O)*y;7ZpaJk@AH}>%>#zMi + z((-PHxIF$)4RFLlOMtXVo`}@5A!R~$L&YYP!=UhA-P^<-lVBW6EOeEsHq9P31H7xvA z&fd{nElFRMlm3duS|Cu(Y&>;<`^EJ!r65Se&Use!)U05_&DU=>nue4)4|9?_EbMhC zFclr>fDMIgmVw^Dx546eJDF6bU4C;r37WM+aDeO{Jcb8UkEK3#b Im{ zJTLLhlg#SvB7kXn_Me6fmD=2Cm#7bRh(kY<4m7jfmh5G|@R;A_a2`It&^$oso9Bq% zcpXm5^3F&OsHVEN&~Ng(t=C=WlYb}04Yg2|5FQm}C-s`lAssmEubvzF0J|&jPy7;T z&(4p9-~QrMjU~3qzicdEwIRZ)Y 4cF?_=;B5lS_^=B^La^vGL)*r+ z+m(~iWvfd&E2+wmP?1ckep5>&XLikEWX$4O9_xgUB=I%bFHP 9qtw!U& =xQu|u8tz)a?u3xv@o5^@|v_9HvsC+FQ>FvU#y$UqP zl4meL9PXI vWa0w;oOk4}3FFK!ZCQr0|SzGM ^6S>rY*Wbb zSK(=Hcpj)dlD25NaX@(Esh5x@MTC7_YP*N?QCV%GlD=GQDIjoVS34;+E$mMy9BY~8 zl~cAJiVUS-gdzW&mK+8fLX^;H-1v&GbsOa4LouGqRjy!Po6W3IQmzXp4nD#PC0$y+ z4zc(wRo(rBzyIx{S+-8VBBq`wlUO}=bAI+)QY_wz_eV>Z{kNW`J{JfSF7oHhY-SHZ zy3iUhNq2o~^q~AgJ3aaNBg?l3K*G_rrOWyUlO*k&w9i+3I@w&qPT_~>CO z5-;O>Hwx`8G@Ty+ThH10hNc-c-YX$}M_&{BOTK5!(V*iVrknA)psm}tm(XPjk0XI_ zqQj*W-~ UO{$d)UH9^y&x_>yfD`xTd$X6}q0qVbk `(N0{iwk0_CXFZ#IgQYe>JxsPX e;*Z z|G|Q*#eHpV?8N`z^b$oHG(P gp%4*4qAMS+ zxM;RP-1Mp79&H}4{${AfYE(S&b$;f%_rKr9Olf@6hc%=<*PI^m{0I*uv+81=SAPf5 zAI?Sl&iLLMtw(@4QAwhQX`eT^4C{-uL$e6+xG2NpZ~2Wf@wfaVX1%T(!{#JT#wK(s z>;^oynMbLB!f)Nbg7T@_muffE?VG7^ITS1h-XrdoGe(oX`O@cDRrN5pp2zU>Gl7A| zsKiIlnhM&@0wEnkDjI;}dtO_X&r>{H;?wwCa;NPQ+;X60YbzTupZPZ0wZ+GDWqU!W zNBXs1ktLGU_Y>syM$0AmT$l_Oz^q2G58c2dKIBbQX-g)2oY-5NIbJJpn9aSb4J^+? zSbCG_F};W>kFiGn3r}%St{B7-AfqUJ=1~|rF*Ey@Qf-t)^k(zTODj3KvNXg9ZrlJ& zKhWRD5(n;xQKfbd6F!w|EDN_)jLe$DE89xWqkJywYPYPbIzaLJ;Qq$QXMnK_b~;h< z^XE^8T2B6`?y Gerx-Ryw<4Q|O8D!=`sA=JysQ@&B8=YXH9iqlFT_|)s%FdGX z@ixtg^r&fYC(emvU>H7z8C9n_As>A@KCwP7Ac@cHeZy(&rj?Dv+<$AQV}j#2M{F^n z>59}q6KIF_6m~mzaBb!sAKy9iOEH)#2DFkq>K}ZGa!CD^WP&F^SWXy))Zgh^tWNTj z9*C}IJ2)Xu{=t#A>e`VxmuK4YVJ%^JSb+|pVgR@36;PiEJda39!nzXq^C$$Y4GQu3 z)P@XsLZMM`u@s{2uaK{QVn0=Pzj5~-VAdQd_Hh2YOqu?&(*-tP`0)MlSHwiVmPk8} zRYy1)KrS79UoES@Kw6u9ic;@QwEM&?L)4D`w?DuA0S w z0@Vev-6JSapw9-cB^c)rk^Hk 6mm1*%je^K(372txZx) _tx50n>sKv>?YA<>>gnv@boXMc|&nWItND1x=KvzF&p>y@o18 zMiwjL3m8aZH}ePVdtD1el+8BEUk$$KaAG{|iTpF!Q`Jz#LQBT{8GVM;&hhpu`3^0Q zG_bFlV8QiyR;eq+pJTJ{WX}f{@M Zv!=8Hg6XWl(YV(&i>vRW) zyD9IWRj#XCO2KbxOqPF4k3EFWz+c!}`nF<6oreNDH-_Q*G5JCIt9k}4L+B$Kw0lAq z+YnZ9WF9Hm$16vV3+GXVvR+rSk}fp(y9!+HOlRNu>M`8o(1T<1eJP+~Q_*S@x{p zIQ@C^=En~k>rXRmdl-+`l8MwD?k+-w<(33t7Zfd7uj8EXzu)t!11LIDs(wXvm!}Kt zLhj8N6!>mq%hk|?9A2yFrJwd;Oz{{;ufZ1q*z?3J|5|fKOoStp8@+B~^s08B@O&;0 zFRJN;{xUEdMQK-a!Y7@s+^9Hl$RTp%;T%J6;xAc}ZG!Z{oi8~3i X~mYj3@yyWp Ykn<@R;!j?P%?xexl_q)nviz zjwlpBmfAN DKDVH=dOUEa; uE&(htpIUk$!wUiqgy=(@ER_0OE&bYCK#Wb^` zNp3ddq}u}ZhNV)uk^n?f_Ag?-WB;okArveYtc)36$b9vqO*feSg7P22vePV{2?F&v zy>r)uWaQ+gqp6`0)my~^5lnDi37}^k_=MQcU*dq#_LeQ_BkFF@T$~lc_bb!vvy-he z(WL=JeyYSRyL;MDa<#R6bPcVby_w*yq{QdBwlvg;hzM&>3Fanyr-a8hATcW;kH2_( z27}g2KjDB7lg9cn?IZf=3(0)V0LP0ot43UY;@doTYiXV15KMB*aiwfa&hVQ|PlMJO zFRhoVKfUMd$)A8(O$+U@uLod2_h-S}I`5aJ{I#m ruk`r zfDP3yIH5TQDgcZB8kURzjCRIjK{J7{$q+B(CYbN$wwaz&{NzZh30ITi_{i{T9aJdm zH3kLASB$VX_5>!F=0*a&$c3O(B4K1P#Q;!{$|Rd8Y$v<#yW^{qZmWq~)KRdR*^C=( ztj3h0Pen4CnYN@74yoP#jWgGRuKhf})s!CJ8I?DGhv9tWGy2fCNQV6hTw_${{^Lpu z2Vf{@C5Q9fnR;S@ewOx7;9N#u>K&l3qHuxl7vAjhc+r1g |F<9 zQU1El+?26!t9aQ#zeUMo#|vP%6*p=~tADHLUUp|lFR F~&*XXbVIS{0olt zo!wv@Gnel-3i$H%Q;gfGIDy3v@}Ieb>1C!mWV0{wb4NF8&PlJmo$vS7w(V@`a( WP!MI|Me0J!AI-F3uBWNwQ%z-?w z9rdpL;o}=MFOR8YLJ+NajQFzJJB{M-4 3G?n)(25J9XJ`xc+auHWi%3k<+j`q+ z*HLpYD7KmewLeI_GrefTp8KGb5HC=DS^rAG8QT5!i2vtO!bjOkYehpn$4~X&KlReb z1Icx2o0}yEn30r1{Bc+>N9EE6j$KP;mb-g4j^ak!Z(L@RaZrcGWkkiv4CLF)w{(D{ zSP8KF?snFR|5+O4!P rN8Xb8aJrf39H*$}B&HbyO*OFY-}ON!_BVwKTNLn| zn3lW)Wk@d{;OHw96-}Z6R1QKfINx`0>#bI*Ds-8)`1Dx0d~WARzTYo$y+KzO-zc~K zOgd~%qYV =KWj%?0{=K7`L>)~;Cw+Ko7MM;{Q z&uQV|(-WN9Z)AHUzy|Eb%l%R}^U_|hCkVDB*%g$5_oF=yIJT4$fTvV$88Qmc7(EDI z@0Y4g22 {?+<%wn#ZswR23OQx~PnIdFsKH_UN5~-5Y&|H1|^$4m^_7 z_)(yK76iZrs~?pUS9$4MU#< e+RbSU{WuB7m7P4;ECoyw^p?X z w!{TS_r(YIhn$s2fooV-1c$`8}iMJ**SdNW&XaTT{0%vJBh6*k3n{P zKeOGdEr$h{6Uia=cJ8wOYy1dZz_d?|xuXrjzG!^yDRCgktoC8_L!KOBPu+sjZS(-w zDIcq8IH@h`|1!OnPigK`LI~7cdJ|3riil;kJF05wbP2?{ak90wD4w4y79^d=|Jd=%Kq4RI6!L3yM>%RA@swKY2OdJ~DhJ>tGB=9o)|f{b>|MFShEpm6I?8-#v}J zv)2JS_~egXChGse_%idtc(aq>A`#S@F>KynY8t#B>E#YV-DL`+;k-y0F2b!gb0bCY zT;avSSqZm39e;;%l-Lk#vVJP^tvP^Tln;Fa>6ooaHC?{s9wO1F18_m*-WZnbSvfj5 zOuk>@ * zJ|zXe*)Y+es=$7{D2bTxz}VjRum1O$FTJG*BsikbV#6MwxU4V?IXg_zZkN#seCD-# z*Q14sir7~fh!sXc-=5xoDe9YA{Z~Xv^o;7
GgR67fFm8Vx_?bMb zE1e {Fx^1JWrunP?M)lh}^JA<)`yI9~J$#4pwSfKQicrkzBcuvvgL`N4J zUt>$leDU1#3l$!o?~P@pyN*i~? )<}7BF=I1zqG+P3J#qXrqUu*L_!1l);>$rQ{+mo(La0ziwgr zd;A&2lC99!Yvp~=YJ`U)8JyHJxA!A7i`yGBa&<%}vUeH>-tU0kL1^WsjTFHBD2h ?VwW@AFX0t3Nu z?*HarRgsxuQ9>kG%ViZ}-aCf>GRFObM)ct(LCeb6hOgKkAL1fFSGHJ3zj*Hyg=cwi z*2q{KGxO&n?!d{vlcZYMv4}HQ#o9K*D6_inH_kC@G3y1%3c$XGgm|^XjXCK9>gMsq zG={tjkoY=yHinyf85f@D#il^^xdQBWDwi_+-7jNbZl33ASqog^nL0nes)fDW<}PiE z{8`d|D3%*Z&IRzuoR`|{K_IBgG^n}oL#`x8Vs+(T%6al;A-9KXg+B^n`&|~M0d5rz zt1}@%u+u7j3d)%+^8(_!JDlEInwx=xy4YXZFHir>ugj8rawTs0qO`KZ;902rs&OTY zyXlP9;*;X1UUm2y?*mwF0g-o65=A+;{}=cZ!qUywg&wwZyeQHA_~6#|#~g!~7ynh` zLdsWLyzP3SPmBmSljZM!avl{Az{LdR8@z;YS6DbndiNvu>03NfQv0g+nPc!FzK!?Y zRBchD7dh6Xv;{ieqv2;%Qc{)5AJZmkZQ1Qd!>#DZUH7{IstyhgM&!I7c;U&r8qRSs zhUH3^!qqwSRO(Xj5eFo`j2#6^O9N1RsY@m43vb4P%VVgjarg2$->CO@HXTM;-QQ zhszguNq_5!`NFvz#E%ifNI$*Y!h;gthmgWc;wTCLN@Xb}3GeaBTdlc&xcM4p7SMGy z+qUACu^TWyg8Bo#1uwkqIDmJI82m_yf7g@(KniUJ_!bOlTE8y>SAbnuy(@RYxo_#U% z7qlEoXxe?c^^d&G#E#ZKy!)_Lbs2K5+|tXXT#)m9If{q?@GG(tHN&9e{Ze}D9dP|S zuJ$Vb@i6ssK~}*1!n8BGEf#jF(tOGKy?!ZhT+u_^1MI05qm0IIOK7?c)Djk)3SZqk z^eI}B+p<4};1`+{z$F2X9u!nzzLF5QKh0l~=TQ G9m#5)zKV)+G6O23 zVBR)#8;16>;!{gQxs6CF3l|xB{=L4e@JM%MF|^zBo2G)j?b3Xdq02D!wwS>2vGCm6 zdn2E+3E0)qb9X@J$GzHI3fhol0|PwPV3jSfc~|k;<-i1jFjhij)(|` ccn- P>w6KHmy*fmBc~9VEEQ2=*=+F{Dgm)a? zWWzOM3ck2903q-W)8H(LF%%m(;(;_sus8CHDoJB>>AdrPoJz?3Q-GA{$4uRLA@|aZ zamIMixQRGt@Z1gsdRTC(;=|y58&y70F8F=VC$3NOLf1%ryYUTA2nB(`HP$DK(zb%( z3^M1jdc=_&0c;%UHuFPY#xO&cQe+VDS?NBtno~v>Xno w(_Tqd`mM`fF=VW8l1 {2>;R{r}*9srH-^!c{bc73?6eT(N+B~}iU(IaYtmkdG_ZnRO zsb2lm@v8}_`FN*g*>U@WOP-4v3M3b+b2SDTHx`WVu&cW)uH5fOWUbV>#Rqc-ovp2s zcmFfV8oOru)qa7*c @_vE0TTyj5lQ^5453qN{IS{kG8opWoji zPPX3{Z{LUZaQzhGj(iRJG*8FU1F23!bj8Aho}=DrDw&0rU{1F^&iTBBjg>gJ9(@l9 z33>7@h)q-EdTnG{xBD;{Eb9NI4lU*%ov0n=R;Rj`h`R^~(nI*G%wFDjxs> `FoL2DELOLfQ864lS1HqxPUh@X pCsf` 9M_O!sU+v5*DfO(ek zudMiNwWD&YI(VtY &bOr3Hdi|8+5?w_k)0C}wKm6aphOSRS zx}|{$Ik4+;*f?vTJc8}pLbBc4podZM?#q;5m<^ffp5_a^&5gn3PeCDuwO^<}j}{aa zKf@$Ta~om^un^J6^1h0+S#EdKo1-^-n4om>S$qDY-|q0s3^r!O)P3btzT3=DbQU9v zV2ygdA-r&;k<1QtpiXALNb!W5LcBbkt8*{^%WD@(x=mEo5fKKp@CJRM(I=ik?O~ ;aoRpL}Z#-D8jGseHn8{$Zjy|M8#lgxR&+ zli0F> J`J5Q0wgaZClkngJkU z2AJFJOSz)(ekVa_!Xf4$l#TjCagcekZ0VUY@vmD??|;F;Bll*@=w9dI5?rQbV=uqt z@q>dWEUCYPwB&nsFIG$1C|YwR!0Tw$IP Le z0*I!Nn?NaJC8xRJ#nZ^>_PO=+XAiS;yH!PR)9P999)gG1U4C4qV(2-RDEQYMULIdB zbN!TCiEHp1jJ}TcYVNBcqpqM^*vsQMedkuE6~5{r7j!NxS~CA(s2$`E#t~iZZox14 zd*!oy({DBx@aQu<<$ojue@Lj8&t|of+P>u}S5^TaVFuC${R< z{0-gd<+HQ%@z}@YTg1_8rdOx?szP0E#d_(hXV-zKTRv*xvFS44%a+O8Z+xxduwe10 zQRU|6$Ft^yTQxXW4;j=&wcU>oP4KmX-4J)1b;H 1xIADpax^}$CnZM`G&W%2_FC(l3WI6|h3r ^MP>j$qFr+=qn9*}Fuhs6cxDUfq*&Hpeue@=K@40`E2 zd;a13cyctDPIT9gzlM?jinW!GN@Ic$5u^xJoMpjPdvFdBB{D}Rq$odJlz#j8NcFhf zOwCeZ(thf-Vtwqo-FOI8R1t$;BW{P|0dZ)+v#ij%UGv(_e4N?yZqm}x{Y#PZ3ZkpS zh%i0??!%(+`OcRi?i6J6czX7{Yh>a$*X3ORWv@1->At0O$$uI|&*Vz}e6H4!@WY0O z3okR6LVZs^DW@XtZLz)BLcuR_kJ0Afmw-;A_4%Et41lz~#uV+2AbCp-pjdAnO@HUg z7yyvoCj~cnugt0kY?hFg?!6?j-O%)CD-WgsIK$I#Gk-q1c3K#!mquRncR%mjRWExl zwei|OjQvj>CWoq8Juk<8sLshq! dE187vswRMST-RFgn1=M-V>~W>sy`YsMbwJ?9g2*WTm@d+Gh2 zkL2qxa)%2D5kWIt1=kXi%yd+*=DKzrVQ5y}xBb|ij iRxx3|CZqe1^}sI~j?N#1^#~Te-#9}{ zpc-WBH8ed|l-r}1zWBv;3zz@8LY@Xwi^bC>9o3Cp@3Veb>QO#n)7?l(lw`&iA@-~w zK;Fh_0Y3XGcN?QQZ_b|QiC^klETo4%8N0?nATKw{)S^dWXRWT^Sz@z7Y$_X!V)Bpe zLwuJr|H=(HeVV0yc$v>g1z<}pM$IqRR)5gEUVb+ S#qRPKuaeg*uZEkLE;q7e1-VIxy5{O^_-5DZJt<4iEFz3rkR7ifi z+ePW`NBw-*eCm6k*Bu-*N%CTPzd9$*b00QWmCqixxpe+!_< f7fiI&30|24}?5L!{ zJS5l6t5cPY;x8)?cHMV>+s#gnj>_dX5+!~43(3=5%v$^XsSu7lMGNqI_6)PaMieE0 zo>u1iyY>6TiR;Lhr(OhX*wBlZ!0ff~zt6X(yZ)0=T-gGZjBejo=*{WUQK-_ =P~kcjNhHE{g*gCZ2T|vS{b6v zuS=WX{)oKq?tU7#sy4nIc_D}TO|5;}-n6;g%n@I_OJXNtFLzxqHFiN3t=-wC1=8`} zpRi`Kk(&kBta0mg@0h^?NX%$DZ=Lq~t6CqPl(|KMQNKgA{SG(Xgp=uU>w=DTbe*n> zD=CA0Pw!8HyS7&4mA#a=d;ddI2e-1BRjs^TDnuJ+q&4h?|Hb>ct%Mpk)M^PM+PT8L zNx##>Mg65a_<2pd+n>W;-mBK;#{K@<$VJ7p)%{Br93rkO#NUej-5K85hPTBiDEuvT zXH%+Md+k2n;^!{nt;{cJ1){p4yzl!|RoA<3_^AyazBs~hn6)*?cJGZF!2Hn!phA$; zm0!Y`ZhMn4@p94K{h}~A_p(<7gBR5_T{&!U2UE@6E_rInXs9cq)%mMZd}s`3Xmc_7 zdhG|~F{D!Nd;Ce~_Pu5w@r$Cf=k4F#rO3|-23%s&rrx9S(_s}y+gq&Ay$KVfEE&DBDpXqHh<{&Dr-7g9zP~T74)DwbM zW7a%G689l2xW_uk)(Sxt-RYyVZCOqB!EbIcsXdnv3zqTUQ*6`l0wIWIZDOqoMDt6K zkWPgEE(iM6I^i`vNFQS9iHHZaBAT8~Y@R9iS#E@VsSY^u>9%(~I-@$1&QqxKYxUgK zt9A)ETZntRw;`Jy!7V%FUyxW?!`t=hT`ktt4SqlaGd9RIq731*_wCX*gTcjw;H8^k zWbyW BYMIZ=IjZ%B%h)oujK8kis*%lj~_EwbB^A&4yA4uQk*Q%=0CJh^aRYV$O>P^+=?J zTcTcwEuP1 ycJCD5J?e4LfdOqd8F*< n2 z3O=Gq+7L7qzc_kSQ~(r~&JdO7zZ&hT7Z`SLg-ixrH65aJ$Fs^snLN(^-MH;=*}2cE zGIR$y%n)xn+EMM0`X%f|rS{PQ+i3d$jt@CbYdhpi6q=#vRT1^`tGI2u^SIe6lSe}{ zzh7zLnrDORokF5Vv*ecbjEVtSJ?jqt3zhU=adBxl!jz~ztsN{=ys3MxqSvEusQnK! zt1AL+&0ghD{_g`kd{~P$s*W>jcCSU|j{OTJdb6Nf;rhNZpYo23{N$#H5txnsz7UEq zcUX%PWffqn_8Qz1lKkL$%*%0 Q)%oO -HEr$&AGkEOS6r!N zIxLAg=xzhSlAa>eRVfV$|9g`iLVVBcbF#L+1P2a->xTdPd-0Y&494lvKyEMwD&Rj> zGqwghY(C0=*{fcuugr(}__}X=UiEN!R(L9|C;m7&zzDAj2=K0_2%Qs`2*Zraq4@(r zp-(2ptHm{nL*Aa=-VWP@CI@jVtiVCCi&n!wu2W~?a&`yb=mAf k$2*i5@4X|Wld=HQGlTc u%8@^FZ30zB!@5zB6ujIfy zAAVOe0=DDG4oC%=MYs`$TIPu`BYFQpH8D5>M|@6UG>@vvdGVS|yRGazENgtw$L=cH z`sb^4*=2&)Ahc!1;l8Q6Kj}Cw+GuC_{*5rV19A)xn!V_!mbuwL(Ra0F!Y{A3WEZ}T z@>6q>{ALzV)xtg3ofhCEi?lcoslHIuWXn*YgIu>5a~WCl(nzhRg8ZI!>M-J!0I`pa z-5 Ze`C%Hbi_bsqPWdD-U`9Qh^ojU0zXh4MYg|3|4st#QzqD3A7q&RBbn!N#2km6Y zBh+V?jmj3CQKX={{JiowbsZGanx>8bFoKLp9w^8a(d2%ilw)>RVaPQf#y|Kdxa)Jn z-XSZ*$P3UugmcNYu@SmdEAF2~i!jI_si;n?WRWC)hbl-irSY2FN^;VYSWtd){bufo z-qRiy!@ZKM@IbgB$5fLf1qZ=+QoI1T8R$R%8W&{|`bL7~r8}AZ-Ra@6;`31D&C?4# z?!Q$bWUl*?+y1T^>%>q9U~Md>=9+&)E706KuxQW`#W}m_szDu4xqJ20X^2PD<616p z!c8TNU1 X`(=P2z<$lH>P=VZ{<@X7AL8HTCT%(aVvJ1g+S(vqQ&0*4R4m-Xazr2 zQ!g+5TYI@xIMavS^&(U2OuVHRSYZFQJ6hO@%#l#mm)iJii$bJ>X}I0PGMOfx2B4p# zbKDkmtx6M21t=KgtoK7=*Yf(}YFEv>N>p2I9REh2F)WvGS8M=-u3YALsOx9+s63-P zb(%kPRNTpvBYGOKKCE^utX?L*h?x_vxqrDb*)8BJUk=a#d&TK^>z - !}lf8L((bw}tqt z$}f{G-^}Ot5;oR!m3bmTqw^&fA5#zT}->G)QR)qjhRrrNoAQGOEFAk 2j`c?BR>RC0^^#6Wg6H zP8jLv&`2YWF(b=dFVmA0$D@FPbvLH4$zy2TGyMD`<2MRaBorW_hpq-W8_13O3-+7O z&widL*5vP$Z^6N$_dRD|0#2#$_P<{UH*JJbci#O>^zXWwZp>(Ol&?hZ;U3P=GacdT zyw ?`45Bvhz|G>R;V8s3W6P|%tb*!(j_ zbu40Zhw!pNOb@-rUELdGhiXYq5t93d=*oy8&ggcKXuC@5frxaxQv=7B_(x4I4iLq; zTlt&y>Ny d#+#J6E|!?7UN+~>?v(ZPXa!e?UZq&u zu=~yhw9)#VUas5Jjfo|e?~x9@=*ef_%3n;j#|d)&A#z3}o`>52f`SZF^X7y>=4u)G zT`kJ=C}Hh)Z>ldypoW+@4rATO=tu%lUmv6FFLy&|&M@K#OtpPhrzv`T*5|#d#FPCvOw&P@9zJHGxu;k5DVM1Jv zRaI3DiT)#Q#P^sPTk%9xGlPDN&ZQ*XGl?|v1@l}WX}=Af9tHY_nS^^yZ$R5ja(nUz z8G$Z>^)>+&1CnMniiJTq0w)N$7OY=n%H9@D`m_Q{rpuAUaN_M*v*+VxALHKXz cG8zzuO2ovCpJCR;I6pOXJ9$EC1T Xo$9pNu)vLJNnJ2*T^9&iJFb5(8o{J7tv?e*{@{aLJXXlRM1=*kCORDA%@wg zdJr60C6*z0Z;U*|IgcE%Sz2m;yQs@MMON4{t1$-|)mlc7B*-DkYU ~^g{pSVlmS;U8W4}_ `(<`pdb+xL85!N4Zb9L;dLzv-Cscub;)8B>TrnJ zFyY!?O0!OeOwrU(2aJELpgnK@233F_wxD1z0Av>+I`B3X3sD>rE-04py^D*G`n$NE zFH$@U+%U%b*5D3->yM&trX(15rK2*0yLls9012mtJHl##73S+bkc@$GtI&Srz5H~8 z_ZDT^!$2PP?b`L{g4>Y5s~Ma4I(JZi*S`zJX+5qBV>+~BGSP1Iq_ygV_%WZxGtCD7 zUL?3(`spkv;EioREf6XIdBWQFa0FT)65Gm1mSN3pr@t= z8SA}C7J1<(e~lw-Jp;x?*B}?@W!GPq9-oR5-^=0%7zcivW+4mu9|7QXEb?nkZ4W9( z>9f{?2wWNSWSSX$#7`f2I6dfafT3;4THpgZcmrM6?cpu#j`YYxTipJs*F5>lKW^In zIFyI49HV%6AXx%ggVZs=^e+?QAL5Wq(}+D=rl1o7|I?Y|pOTKL!y(<4JS>|O`>bVC z+FD<*L%OQJN{J^5h%t?eC-s>9#uQ_=m3`u2hW90@LHa}&n>%IerJIbJ7bpVDn~i+e z8=!))Imm}{1S}?{04PgNC NTIp3@U7}GLvG>-Yj4V3`qB!sl-a+6}Bb0VTj4C zSGQUB@L&)|?dn6KA^Yj78f+OD?%1B5o+L&3kik3@iaL(Dy;bV_nMxrBu`mdfEgf;l z-|ooW^QY*v#Q9`j#1F#SZc>+IUXQjQ RV6MT(SV~8tgMlwVVZ7EPhX**TOPmw;(8sI~ ztX#z~AwMTGNNCtXV`=X$1QF+KV`y=EdYjE%Y+$+nWDt63fdEb+UGk?WTu{K|_K{yg zTu&pJ@T5sOP>#w#ft&jZgaT#vtYJ0pa%!{NRez2A^|olR3|;~czGx_%`*ngHo*@sZ zD*N1u5Ezvt&Uq?|sU%O)*VS(Q@MAolGP{jdL+Dj#ic@6A0MkSqTk2avb$lmwlqZot zr{ze;qZx&qdXb4zq>FBzgzO4vY$!0cbRDCl!lNL+Wo)|v@VO5rnlV9IZy8aq7Q62P z8_G1BDV`{VvU;gT1;@1tQ46japm?&${Uz*ULpmp#G}6&4sx3v@yVimq5c4)3l`Kf1 z5B1yhI~ 7<468ow#M)b7+hfDjj}$o&x~KYRSqqR@B=1FZ1Sy|+3$ z?N=f>gS)OK nqaPxDIOnhaa$N& 5B1z)Z`Rx*whofavj zURkY7$4BEMp*#}ohYFEwqrp6-F;p$W$O@2fjH{3?&Re!CNSz9n&F7(N-i30se2Iwr zP{QB%Y;_V1{d6O_Bx*z)zm4z+xCh?i+G|mwLzTg0g5gkcZ(~R<2F+bL7U`4;odjak zpMwTTUU0e~&*}vziUBaYlh|%Es!B48BJM;ictw3!@AVm=6QbRnlmPP}$eei^TVNad zP8b~M|HXQ;s$Rch4u&lay;}CW%2S_pgi+^JydLG>YC0l+msdYuPd4{MxyKpe8E %#Kmrx5Rg5blh%8ja~|%g=2~DYEx_sG_m^mv7Ne&j&tZhl zxM7H sl )AESgg}*c@v~a$%b8 z$^wB22MByvw+o`sse0Y>3D-9Bcj?jRjJA9; h-HcCE z4w|MS