extends Control const GAMES_CATALOG_URL := "https://khixi.kagouille.fr/games_list.json" const GAMES_INSTALLATION_DIRECTORY := "user://games/" const UPDATE_TEMP_DIRECTORY := "res://temp/" const LOGS_DIRECTORY := "user://logs/" @onready var games_dropdown_menu: OptionButton = $OptionButton @onready var game_description_label: Label = $label_description @onready var game_size_label: Label = $label_size @onready var download_game_button: Button = $DownloadButton @onready var update_game_button: Button = $PatchButton @onready var update_button_quit: Button = $PatchButtonQuit @onready var launch_game_button: Button = $LaunchGameButton @onready var download_progress_bar: ProgressBar = $DownloadProgressBar @onready var download_progress_timer: Timer = $DownloadProgressTimer @onready var update_progress_bar: ProgressBar = $UpdateProgressBar @onready var update_progress_timer: Timer = $UpdateProgressTimer var available_games_catalog: Array = [] var selected_game_info: Dictionary = {} var active_http_request: HTTPRequest = null var current_download_size: int = 0 var total_download_size: int = 0 var current_update_size: int = 0 var total_update_size: int = 0 func _ready() -> void: initialize_ui() var base_path = "user://" var directories_to_create = ["games", "logs", "temp", "user"] ensure_directory_exists(base_path, directories_to_create) ensure_temp_directory_exists() download_games_catalog() func initialize_ui() -> void: download_game_button.disabled = true update_game_button.disabled = true launch_game_button.disabled = true game_description_label.text = "" game_size_label.text = "" download_game_button.text = "Nothing to download" update_game_button.text = "No update available" launch_game_button.text = "Launch Game" download_progress_bar.visible = false update_progress_bar.visible = false func ensure_directory_exists(base_path, directories_to_create: Array) -> void: var directory_access = DirAccess.open(base_path) if not directory_access: push_error("Failed to access base directory: " + base_path) return for dir_name in directories_to_create: var full_path = base_path.path_join(dir_name) if not directory_access.dir_exists(full_path): var creation_result = directory_access.make_dir_recursive(full_path) if creation_result != OK: push_error("Failed to create games directory: " + full_path) else: print("Games directory created: " + full_path) else: print("Games directory already exists: " + full_path) func ensure_temp_directory_exists() -> void: var user_dir = OS.get_executable_path().get_base_dir() var temp_dir = user_dir.path_join("temp") var directory_access = DirAccess.open(user_dir) if not directory_access: push_error("Failed to access base directory: " + user_dir) return if not directory_access.dir_exists("games"): var creation_result = directory_access.make_dir_recursive("temp") if creation_result != OK: push_error("Failed to create games directory: " + temp_dir) else: print("Games directory created: " + temp_dir) else: print("Games directory already exists: " + temp_dir) func download_games_catalog() -> void: active_http_request = HTTPRequest.new() add_child(active_http_request) active_http_request.request_completed.connect(_on_games_catalog_downloaded) var request_error := active_http_request.request(GAMES_CATALOG_URL) if request_error != OK: push_error("HTTP request error while fetching games catalog.") @warning_ignore("unused_parameter") func _on_games_catalog_downloaded(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void: if active_http_request: if response_code == HTTPClient.RESPONSE_OK: var json_parser := JSON.new() var parse_result := json_parser.parse(body.get_string_from_utf8()) if parse_result == OK: available_games_catalog = json_parser.get_data() check_app_update() # Vérifier les mises à jour de l'application populate_games_dropdown() else: push_error("Failed to parse games catalog JSON.") else: push_error("Failed to download games catalog.") active_http_request.queue_free() active_http_request = null else: push_error("HTTP request instance not found") func check_app_update() -> void: if not available_games_catalog.is_empty(): var app_update_info = available_games_catalog[0] # Le premier jeu est la mise à jour de l'application var files_to_update = [] for file_name in app_update_info["file_hashes"].keys(): var expected_hash = app_update_info["file_hashes"][file_name] var file_path = "res://" + file_name # Chemin à la racine du projet if FileAccess.file_exists(file_path): var current_hash = calculate_file_sha256(file_path) if current_hash != expected_hash: files_to_update.append(file_name) else: files_to_update.append(file_name) if not files_to_update.is_empty(): print("Application files to update: ", files_to_update) update_application_files(app_update_info, files_to_update) else: print("Application is up to date") func update_application_files(app_update_info: Dictionary, files_to_update: Array) -> void: update_progress_bar.value = 0 update_progress_bar.visible = true total_update_size = 0 current_update_size = 0 # Créer un dossier temporaire pour les nouveaux fichiers #var dir = DirAccess.open("res://") #dir.make_dir(UPDATE_TEMP_DIRECTORY) # Calculer la taille totale des fichiers à mettre à jour for file_name in files_to_update: if "file_sizes" in app_update_info and file_name in app_update_info["file_sizes"]: total_update_size += app_update_info["file_sizes"][file_name] for file_name in files_to_update: var temp_file_path = UPDATE_TEMP_DIRECTORY.path_join(file_name) await download_file_coroutine(app_update_info["patch_url"] + "/" + file_name, temp_file_path) print("All files downloaded to temporary directory") update_progress_bar.visible = false # Exécuter le script bash pour effectuer la mise à jour var output = [] var exit_code = OS.execute("sh", ["./update_script.sh", UPDATE_TEMP_DIRECTORY], output, true) if exit_code != 0: push_error("Failed to execute update script. Output: " + str(output)) else: print("Update script executed successfully") # Nettoyer le dossier temporaire #dir.remove(GAMES_TEMP_DIRECTORY) print("Application update completed") update_progress_bar.visible = false # Redémarrer l'application update_button_quit.visible = true func download_file_coroutine(url: String, file_path: String) -> void: var download_request := HTTPRequest.new() add_child(download_request) download_request.request(url) update_progress_bar.value = 0 update_progress_bar.visible = true update_progress_timer.connect("timeout", _on_file_downloaded_update_progress.bind(download_request)) update_progress_timer.start(0.1) current_update_size = 0 while download_request.get_http_client_status() == HTTPClient.STATUS_REQUESTING: await get_tree().create_timer(0.1).timeout var result = await download_request.request_completed if result[0] == OK and result[1] == 200: var file = FileAccess.open(file_path, FileAccess.WRITE) file.store_buffer(result[3]) file.close() current_update_size += result[3].size() else: push_error("Failed to download file: " + url) update_progress_timer.stop() update_progress_timer.disconnect("timeout", _on_file_downloaded_update_progress) download_request.queue_free() func populate_games_dropdown() -> void: if not available_games_catalog.is_empty(): for i in range(1, available_games_catalog.size()): # Commencez à partir de 1 pour exclure le premier élément var game = available_games_catalog[i] games_dropdown_menu.add_item(game["name"]) func _on_game_selected(index: int) -> void: var selected_game_name := games_dropdown_menu.get_item_text(index) for i in range(1, available_games_catalog.size()): # Commencez à partir de 1 pour exclure le premier élément var game = available_games_catalog[i] if game["name"] == selected_game_name: selected_game_info = game break if not selected_game_info.is_empty(): update_game_info_display() enable_download_button() check_game_status() else: clear_game_info_display() func update_game_info_display() -> void: game_description_label.text = selected_game_info["description"] game_size_label.text = "Size: " + str(selected_game_info["size"]) + " bytes" func enable_download_button() -> void: download_game_button.disabled = false download_game_button.text = "Download: " + selected_game_info["name"] func clear_game_info_display() -> void: game_description_label.text = "" game_size_label.text = "" download_game_button.disabled = true update_game_button.disabled = true download_game_button.text = "Nothing to download" update_game_button.text = "No update available" func _on_download_button_pressed() -> void: if not selected_game_info.is_empty(): var download_url: String = selected_game_info["download_url"] var download_request := HTTPRequest.new() add_child(download_request) download_request.request_completed.connect(_on_game_downloaded) # Réinitialisez et affichez la barre de progression download_progress_bar.value = 0 download_progress_bar.visible = true # Configurez le timer pour vérifier la progression download_progress_timer.connect("timeout", _check_download_progress.bind(download_request)) download_progress_timer.start(0.1) # Vérifiez toutes les 0.1 secondes current_download_size = 0 total_download_size = selected_game_info.get("size", 0) var request_error := download_request.request(download_url) if request_error != OK: push_error("HTTP request error while downloading game.") func _check_download_progress(download_request: HTTPRequest) -> void: current_download_size = download_request.get_downloaded_bytes() if total_download_size > 0: var progress = float(current_download_size) / float(total_download_size) download_progress_bar.value = progress * 100 # Convertir en pourcentage @warning_ignore("unused_parameter") func _on_game_downloaded(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void: if response_code == HTTPClient.RESPONSE_OK: var game_name := games_dropdown_menu.get_item_text(games_dropdown_menu.selected) var game_zip_path := GAMES_INSTALLATION_DIRECTORY.path_join(game_name + ".zip") var file := FileAccess.open(game_zip_path, FileAccess.WRITE) file.store_buffer(body) file.close() print("Game downloaded successfully: ", game_name) unzip_game(game_zip_path) check_game_status() else: push_error("Failed to download game.") # Arrêtez le timer et cachez la barre de progression download_progress_timer.stop() download_progress_timer.disconnect("timeout", _check_download_progress) download_progress_bar.visible = false $HTTPRequest.queue_free() static func unzip_game(zip_file_path: String) -> void: var zip_reader : ZIPReader = ZIPReader.new() if zip_reader.open(zip_file_path) == OK: var zip_directory : String = zip_file_path.get_base_dir() var extraction_path : String = zip_file_path.trim_suffix(".zip") var directory_access : DirAccess = DirAccess.open(zip_directory) if directory_access: if not directory_access.dir_exists(extraction_path): var dir_create_result = directory_access.make_dir(extraction_path) if dir_create_result != OK: push_error("Failed to create extraction directory: " + extraction_path) return for file_path in zip_reader.get_files(): var full_extraction_path : String = extraction_path.path_join(file_path) var file_dir : String = full_extraction_path.get_base_dir() # Créer les répertoires parents si nécessaire if not directory_access.dir_exists(file_dir): var make_dir_result = directory_access.make_dir_recursive(file_dir) if make_dir_result != OK: push_error("Failed to create directory: " + file_dir) continue var file_access : FileAccess = FileAccess.open(full_extraction_path, FileAccess.WRITE) if file_access: file_access.store_buffer(zip_reader.read_file(file_path)) file_access.close() else: push_error("Failed to create file: " + full_extraction_path) else: push_error("Failed to access directory: " + zip_directory) else: push_error("Failed to open ZIP file: " + zip_file_path) zip_reader.close() func update_game_files(game_name: String) -> void: var game_directory := GAMES_INSTALLATION_DIRECTORY.path_join(game_name) var game_info = selected_game_info var files_to_update = [] var files_to_delete = [] var existing_files = [] # Vérifier les fichiers existants et les comparer avec le catalogue var dir = DirAccess.open(game_directory) if dir: dir.list_dir_begin() var file_name = dir.get_next() while file_name != "": if not dir.current_is_dir(): existing_files.append(file_name) file_name = dir.get_next() dir.list_dir_end() # Vérifier les fichiers dans le catalogue for file_name in game_info["file_hashes"].keys(): var expected_hash = game_info["file_hashes"][file_name] var file_path = game_directory.path_join(file_name) if FileAccess.file_exists(file_path): var current_hash = calculate_file_sha256(file_path) if current_hash != expected_hash: files_to_update.append(file_name) existing_files.erase(file_name) else: files_to_update.append(file_name) # Les fichiers restants dans existing_files doivent être supprimés files_to_delete = existing_files for file_name in files_to_update: if "file_sizes" in game_info and file_name in game_info["file_sizes"]: total_update_size += game_info["file_sizes"][file_name] else: push_error("Size information not available for file: " + file_name) # Mettre à jour les fichiers if files_to_update.is_empty(): print("All files are up to date for game: ", game_name) else: print("Files to update: ", files_to_update) for file_name in files_to_update: var file_path = game_directory.path_join(file_name) download_file(game_info["patch_url"] + "/" + file_name, file_path) # Supprimer les fichiers obsolètes if not files_to_delete.is_empty(): print("Files to delete: ", files_to_delete) for file_name in files_to_delete: var file_path = game_directory.path_join(file_name) var dir_access = DirAccess.open(game_directory) if dir_access: dir_access.remove(file_name) print("Deleted file: ", file_path) else: push_error("Failed to access game directory for deletion: " + game_directory) print("Game files update process completed for: ", game_name) check_game_status() func calculate_file_sha256(file_path: String) -> String: var file = FileAccess.open(file_path, FileAccess.READ) if file: var hash_ctx = HashingContext.new() hash_ctx.start(HashingContext.HASH_SHA256) const chunk_size = 16384 # 16 KB chunks while not file.eof_reached(): var chunk = file.get_buffer(chunk_size) hash_ctx.update(chunk) file.close() var hash_file = hash_ctx.finish() return hash_file.hex_encode() return "" func download_file(url: String, file_path: String) -> void: var download_request := HTTPRequest.new() add_child(download_request) download_request.request_completed.connect(_on_file_downloaded.bind(file_path)) # Déconnectez d'abord le signal s'il est déjà connecté if update_progress_timer.timeout.is_connected(_on_file_downloaded_update_progress): update_progress_timer.timeout.disconnect(_on_file_downloaded_update_progress) update_progress_bar.value = 0 update_progress_bar.visible = true update_progress_timer.connect("timeout", _on_file_downloaded_update_progress.bind(download_request)) update_progress_timer.start(0.1) current_update_size = 0 var request_error := download_request.request(url) if request_error != OK: push_error("HTTP request error while downloading file: " + file_path) @warning_ignore("unused_parameter") func _on_file_downloaded(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray, file_path: String) -> void: if response_code == HTTPClient.RESPONSE_OK: var file := FileAccess.open(file_path, FileAccess.WRITE) file.store_buffer(body) file.close() print("File downloaded successfully: ", file_path) else: push_error("Failed to download file: " + file_path) update_progress_timer.stop() update_progress_timer.disconnect("timeout", _on_file_downloaded_update_progress) if current_update_size >= total_update_size: update_progress_bar.visible = false print("Update process completed") $HTTPRequest.queue_free() func check_game_status() -> void: if not selected_game_info.is_empty(): var game_name := games_dropdown_menu.get_item_text(games_dropdown_menu.selected) var game_directory := GAMES_INSTALLATION_DIRECTORY.path_join(game_name) var directory_access := DirAccess.open(GAMES_INSTALLATION_DIRECTORY) if directory_access: if directory_access.dir_exists(game_directory): download_game_button.disabled = true update_game_button.disabled = false update_game_button.text = "Update available" launch_game_button.disabled = false else: download_game_button.disabled = false update_game_button.disabled = true update_game_button.text = "Game not installed" launch_game_button.disabled = true else: download_game_button.disabled = false update_game_button.disabled = true update_game_button.text = "Error checking game status" launch_game_button.disabled = true func _on_launch_game_button_pressed() -> void: if not selected_game_info.is_empty(): var game_name: String = games_dropdown_menu.get_item_text(games_dropdown_menu.selected) var game_directory: String = GAMES_INSTALLATION_DIRECTORY.path_join(game_name) var executable_name: String = "" if selected_game_info.has("executable"): if selected_game_info["executable"] is Dictionary: executable_name = selected_game_info["executable"].get(OS.get_name().to_lower(), "") elif selected_game_info["executable"] is String: executable_name = selected_game_info["executable"] if executable_name.is_empty(): push_error("No executable specified for this game.") return var executable_path: String = game_directory.path_join(executable_name) if FileAccess.file_exists(executable_path): var output: Array = [] var exit_code: int if OS.get_name() == "Windows": exit_code = OS.execute(executable_name, [], output, false, true) elif OS.get_name() == "Linux": var file = FileAccess.open(executable_path, FileAccess.READ) if file: @warning_ignore("static_called_on_instance") var permissions = FileAccess.get_unix_permissions(executable_name) file.close() if permissions & 64 == 0: push_error("Executable lacks execution permissions: " + executable_name) OS.execute("sh", ["-c", "chmod", "+x", executable_name], [], true) exit_code = OS.execute("sh", ["-c", "./" + executable_name], output, false, true) else: push_error("Unsupported operating system") return if exit_code != OK: push_error("Failed to launch game: " + "./" + executable_name) print("Output: ", output) else: print("Game launched successfully: " + game_name) else: push_error("Game executable not found: " + executable_path) func _on_option_button_item_selected(index: int) -> void: _on_game_selected(index) func _on_update_game_button_pressed() -> void: if not selected_game_info.is_empty(): var game_name := games_dropdown_menu.get_item_text(games_dropdown_menu.selected) update_game_files(game_name) func _on_file_downloaded_update_progress(download_request) -> void: current_update_size = download_request.get_downloaded_bytes() if total_update_size > 0: var progress = float(current_update_size) / float(total_update_size) update_progress_bar.value = progress * 100 # Convertir en pourcentage print("Update process completed") func _on_quit_button_down(): get_tree().quit() func _on_patch_button_quit_pressed() -> void: var pid = OS.create_process(OS.get_executable_path(), []) if pid == -1: push_error("Failed to start new process") else: print("New process started with PID: ", pid) # Attendre un court instant avant de quitter await get_tree().create_timer(0.5).timeout get_tree().quit()