diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08ea62b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +lib/__pycache__/ +ui/__pycache__/ +config.json \ No newline at end of file diff --git a/config.json b/config.json index c7d4234..3db028a 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,8 @@ { "workers_count": 4, + "max_retries": 0, "output_path": "output", - "major": 7, - "build": 288, - "minor": 38 + "major": 8, + "build": 256, + "minor": 37 } \ No newline at end of file diff --git a/lib/worker.py b/lib/worker.py index e4e83d5..ca99fbf 100644 --- a/lib/worker.py +++ b/lib/worker.py @@ -1,4 +1,6 @@ import os +import time +import sys from urllib.request import urlopen from PyQt5.QtCore import QThread, pyqtSignal @@ -10,10 +12,12 @@ class DownloadWorker(QThread): file_downloaded = pyqtSignal(bool) + max_retries_reached = pyqtSignal(bool) def __init__(self, download_queue, masterhash, assets_url, - decompress_data, output_dir): + decompress_data, output_dir, + max_retries = 0): self.stopped = False self.is_running = True @@ -23,6 +27,7 @@ def __init__(self, download_queue, self.output_dir = output_dir self.download_queue = download_queue self.decompress_data = decompress_data + self.max_retries = max_retries QThread.__init__(self) @@ -33,8 +38,21 @@ def run(self): if filename is not None: file_url = join_path(self.assets_url, self.masterhash, filename) - file_data = urlopen(file_url) - + self.retries = 0 + while True: + try: + file_data = urlopen(file_url) + break + except Exception as e: + print(f"An error occured while fetching {file_url}\n{e}") + if self.retries < self.max_retries: + self.retries += 1 + print("Retrying in 5 seconds...") + time.sleep(5) + else: + self.max_retries_reached.emit(True) + return self.stop() + os.makedirs(os.path.dirname(join_path(self.output_dir, filename)), exist_ok=True) with open(join_path(self.output_dir, filename), 'wb') as f: diff --git a/lib/worker_launcher.py b/lib/worker_launcher.py index 17df63f..bb9e708 100644 --- a/lib/worker_launcher.py +++ b/lib/worker_launcher.py @@ -1,13 +1,13 @@ from PyQt5.QtCore import QThread, pyqtSignal -from lib.utils import join_path +from lib.utils import join_path, build_alert_box from lib.worker import DownloadWorker class WorkerLauncher(QThread): file_downloaded = pyqtSignal(bool) - download_finished = pyqtSignal() + download_finished = pyqtSignal(bool) def __init__(self, download_widget): self.download_widget = download_widget @@ -18,17 +18,20 @@ def run(self): self.thread_list = [] self.download_widget.workers_count = self.download_widget.parent.settings_widget.workers_spinbox.value() + self.download_widget.max_retries = self.download_widget.parent.settings_widget.retries_spinbox.value() for _ in range(self.download_widget.workers_count): thread = DownloadWorker(self.download_widget.download_queue, self.download_widget.fingerprint['sha'], self.download_widget.assets_host, self.download_widget.enable_compression_checkbox.isChecked(), join_path(self.download_widget.parent.settings_widget.folder_path_input.text(), - self.download_widget.fingerprint['sha']) + self.download_widget.fingerprint['sha']), + self.download_widget.max_retries ) self.thread_list.append(thread) thread.file_downloaded.connect(self.emit_file_downloaded) + thread.max_retries_reached.connect(self.max_retries_reached) thread.start() self.download_widget.download_queue.join() @@ -39,6 +42,12 @@ def run(self): def emit_file_downloaded(self, update_status): self.file_downloaded.emit(update_status) + def max_retries_reached(self, boolean): + if boolean == True: + self.stop() + print("Max retries reached") + self.download_finished.emit(False) + def stop(self): for thread in self.thread_list: thread.stop() diff --git a/ui/download_widget.py b/ui/download_widget.py index ddd084f..22934e9 100644 --- a/ui/download_widget.py +++ b/ui/download_widget.py @@ -222,6 +222,11 @@ def on_info_fetched(self, login_failed): download_method = self.download_method_combo_box.currentText() + if login_failed == False: + self.parent.hide_loading() + self.parent.status_bar_label.setText('Connection error: couldn\'t connect to Supercell servers') + return build_alert_box('Connection error', 'An error occured when connecting to Supercell\'s servers. Please check the console and/or your internet connection!') + login_failed_error_code = login_failed.read_vint() if login_failed_error_code == 7: @@ -241,6 +246,8 @@ def on_info_fetched(self, login_failed): self.assets_host = login_failed.read_string() else: + self.parent.hide_loading() + self.parent.status_bar_label.setText('Couldn\'t find any host to download assets !') return build_alert_box('Download error', 'Couldn\'t find any host to download assets !') login_failed.read_string() @@ -261,9 +268,13 @@ def on_info_fetched(self, login_failed): return elif login_failed_error_code == 10: + self.parent.hide_loading() + self.parent.status_bar_label.setText('Server is in maintenance, cannot fetch current info') return build_alert_box('Error', 'Server is in maintenance, cannot fetch current info') else: + self.parent.hide_loading() + self.parent.status_bar_label.setText('Wrong login failed code: {}'.format(login_failed_error_code)) return build_alert_box('Error', 'Wrong login failed code: {}'.format(login_failed_error_code)) if download_method == 'Latest Patch': @@ -310,20 +321,24 @@ def request_login_failed(self): client_hello = (10100).to_bytes(2, 'big') + len(client_hello_writer.buffer).to_bytes(3, 'big') + bytes(2) + client_hello_writer.buffer - s = socket.create_connection(('game.clashroyaleapp.com', 9339)) - s.send(client_hello) + try: + s = socket.create_connection(('game.clashroyaleapp.com', 9339)) + s.send(client_hello) - header = s.recv(7) - message_length = int.from_bytes(header[2:5], 'big') + header = s.recv(7) + message_length = int.from_bytes(header[2:5], 'big') - login_failed = b'' + login_failed = b'' - while message_length: - data = s.recv(message_length) - message_length -= len(data) - login_failed += data + while message_length: + data = s.recv(message_length) + message_length -= len(data) + login_failed += data - return Reader(login_failed) + return Reader(login_failed) + except Exception as e: + print(e) + return False def update_client_hello_version(self): self.start_button.setEnabled(False) @@ -354,7 +369,7 @@ def stop_download(self): self.worker_launcher.stop() self.worker_launcher.quit() - self.on_donwload_finish() + self.on_download_finish() def on_masterhash_changed(self, masterhash): if not is_masterhash_valid(masterhash): @@ -369,6 +384,7 @@ def start_download(self, wanted_extensions): self.downloaded_files = 0 self.download_start_time = datetime.utcnow() self.download_queue = Queue() + self.downloading = True overwrite_existing_file = False @@ -396,7 +412,7 @@ def start_download(self, wanted_extensions): self.worker_launcher = WorkerLauncher(self) self.worker_launcher.file_downloaded.connect(self.update_download_count) - self.worker_launcher.download_finished.connect(self.on_donwload_finish) + self.worker_launcher.download_finished.connect(self.on_download_finish) self.worker_launcher.start() @@ -409,26 +425,33 @@ def update_download_count(self, update_status): self.downloaded_files, self.total_files)) - def on_donwload_finish(self): + def on_download_finish(self, success = True): self.parent.hide_loading() self.download_method_combo_box.setEnabled(True) if self.downloaded_files: elapsed_time = (datetime.utcnow() - self.download_start_time).seconds - - self.parent.status_bar_label.setText('''Download finished ! {} files downloaded in {}min {}s'''.format(self.downloaded_files, - *divmod(elapsed_time, 60))) - + + if success == True: + self.parent.status_bar_label.setText('''Download finished ! {} files downloaded in {}min {}s'''.format(self.downloaded_files, + *divmod(elapsed_time, 60))) + else: + self.parent.status_bar_label.setText('''Download failed ! {} files downloaded in {}min {}s'''.format(self.downloaded_files, + *divmod(elapsed_time, 60))) + if self.downloading == True: + build_alert_box("Max retries reached", "Check your internet connection, and/or increase the max retries in the Settings.") + else: self.parent.status_bar_label.setText('No files were downloaded !') + self.downloading = False self.progress_bar.reset() self.stop_button.setEnabled(False) self.start_button.setEnabled(True) def display_bruteforce_info(self): self.parent.status_bar_label.setText('Bruteforce started ! Trying with major {}, build {}, minor {}'.format(self.major, self.build, self.minor)) - + class InfoFetcherThread(QThread): info_fetched = pyqtSignal(object) diff --git a/ui/settings_widget.py b/ui/settings_widget.py index 98a86f3..a0ad8eb 100644 --- a/ui/settings_widget.py +++ b/ui/settings_widget.py @@ -46,6 +46,11 @@ def init_ui(self): self.workers_spinbox.setRange(1, 10) self.workers_spinbox.setValue(max(min(self.config['workers_count'], 10), 1)) + self.retries_spinbox = QSpinBox() + + self.retries_spinbox.setRange(1, 10) + self.retries_spinbox.setValue(max(self.config['max_retries'], 0)) + self.save_settings_button = QPushButton('Save settings', self) self.save_settings_button.setIcon(QIcon('ui/assets/save.png')) self.save_settings_button.setIconSize(QSize(17, 17)) @@ -55,6 +60,8 @@ def init_ui(self): self.main_layout.addWidget(self.browse_folder_widget) self.main_layout.addWidget(QLabel('Workers count (up to 10):')) self.main_layout.addWidget(self.workers_spinbox) + self.main_layout.addWidget(QLabel('Max retries:')) + self.main_layout.addWidget(self.retries_spinbox) self.main_layout.addWidget(self.save_settings_button) self.setLayout(self.main_layout) @@ -66,5 +73,6 @@ def browse_folder(self): def save_settings(self): self.config['workers_count'] = self.workers_spinbox.value() + self.config['max_retries'] = self.retries_spinbox.value() self.parent.save_config()