524 lines
20 KiB
GDScript
524 lines
20 KiB
GDScript
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()
|