From 62f8a0bca5593b30bf65935f1fb72247bc17caca Mon Sep 17 00:00:00 2001 From: fancyfinn9 Date: Fri, 15 Nov 2024 11:07:50 +0000 Subject: [PATCH 1/3] Add .gitignore (for pycache) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore 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 From 8ff807ecaa3eda17d807f434a5291e41a9986ba6 Mon Sep 17 00:00:00 2001 From: fancyfinn9 Date: Fri, 15 Nov 2024 11:14:13 +0000 Subject: [PATCH 2/3] Add max retries setting Previous behaviour: If an error occurs while downloading, the program crashes (due to no error handling) New behaviour: If an error occurs while downloading, the program will retry the download, up to an amount specified in the settings. If it still can't download the file it will gracefully stop the download and notify the user. --- lib/worker.py | 24 ++++++++++++++--- lib/worker_launcher.py | 15 ++++++++--- ui/download_widget.py | 59 +++++++++++++++++++++++++++++------------- ui/settings_widget.py | 8 ++++++ 4 files changed, 82 insertions(+), 24 deletions(-) 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() From dbac18eaae14c4d5a6fac4b9b25ecf83cb525aa3 Mon Sep 17 00:00:00 2001 From: fancyfinn9 Date: Fri, 15 Nov 2024 11:17:58 +0000 Subject: [PATCH 3/3] Add max_retires to config.json, bump version --- config.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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