mirror of
https://github.com/RetroGameSets/RGSX.git
synced 2026-03-19 16:26:00 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c7fa0484f | ||
|
|
814861e9ee |
@@ -687,6 +687,7 @@ async def main():
|
||||
"history_error_details",
|
||||
"history_confirm_delete",
|
||||
"history_extract_archive",
|
||||
"text_file_viewer", # Visualiseur de fichiers texte
|
||||
# Menus filtrage avancé
|
||||
"filter_menu_choice",
|
||||
"filter_advanced",
|
||||
@@ -1115,6 +1116,9 @@ async def main():
|
||||
elif config.menu_state == "history_error_details":
|
||||
from display import draw_history_error_details
|
||||
draw_history_error_details(screen)
|
||||
elif config.menu_state == "text_file_viewer":
|
||||
from display import draw_text_file_viewer
|
||||
draw_text_file_viewer(screen)
|
||||
elif config.menu_state == "history_confirm_delete":
|
||||
from display import draw_history_confirm_delete
|
||||
draw_history_confirm_delete(screen)
|
||||
|
||||
@@ -13,7 +13,7 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "2.3.2.8"
|
||||
app_version = "2.3.2.9"
|
||||
|
||||
|
||||
def get_application_root():
|
||||
|
||||
@@ -59,6 +59,7 @@ VALID_STATES = [
|
||||
"history_error_details", # détails de l'erreur
|
||||
"history_confirm_delete", # confirmation suppression jeu
|
||||
"history_extract_archive", # extraction d'archive
|
||||
"text_file_viewer", # visualiseur de fichiers texte
|
||||
# Nouveaux menus filtrage avancé
|
||||
"filter_menu_choice", # menu de choix entre recherche et filtrage avancé
|
||||
"filter_search", # recherche par nom (existant, mais renommé)
|
||||
@@ -1008,6 +1009,8 @@ def handle_controls(event, sources, joystick, screen):
|
||||
ext = os.path.splitext(actual_filename)[1].lower()
|
||||
if ext in ['.zip', '.rar']:
|
||||
options.append("extract_archive")
|
||||
elif ext == '.txt':
|
||||
options.append("open_file")
|
||||
elif status in ["Erreur", "Error", "Canceled"]:
|
||||
options.append("error_info")
|
||||
options.append("retry")
|
||||
@@ -1050,6 +1053,30 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Affichage du dossier de téléchargement pour {game_name}")
|
||||
|
||||
elif selected_option == "open_file":
|
||||
# Ouvrir le fichier texte
|
||||
if actual_path and os.path.exists(actual_path):
|
||||
try:
|
||||
with open(actual_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
content = f.read()
|
||||
config.text_file_content = content
|
||||
config.text_file_name = actual_filename
|
||||
config.text_file_scroll_offset = 0
|
||||
config.previous_menu_state = "history_game_options"
|
||||
config.menu_state = "text_file_viewer"
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Ouverture du fichier texte: {actual_filename}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'ouverture du fichier texte: {e}")
|
||||
config.menu_state = "error"
|
||||
config.error_message = f"Erreur lors de l'ouverture du fichier: {str(e)}"
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
logger.error(f"Fichier texte introuvable: {actual_path}")
|
||||
config.menu_state = "error"
|
||||
config.error_message = "Fichier introuvable"
|
||||
config.needs_redraw = True
|
||||
|
||||
elif selected_option == "extract_archive":
|
||||
# L'option n'apparaît que si le fichier existe, pas besoin de re-vérifier
|
||||
config.previous_menu_state = "history_game_options"
|
||||
@@ -1201,6 +1228,128 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
|
||||
# Affichage détails erreur
|
||||
# Visualiseur de fichiers texte
|
||||
elif config.menu_state == "text_file_viewer":
|
||||
content = getattr(config, 'text_file_content', '')
|
||||
if content:
|
||||
lines = content.split('\n')
|
||||
line_height = config.small_font.get_height() + 2
|
||||
|
||||
# Calculer le nombre de lignes visibles (approximation)
|
||||
controls_y = config.screen_height - int(config.screen_height * 0.037)
|
||||
margin = 40
|
||||
header_height = 60
|
||||
content_area_height = controls_y - 2 * margin - 10 - header_height - 20
|
||||
visible_lines = int(content_area_height / line_height)
|
||||
|
||||
scroll_offset = getattr(config, 'text_file_scroll_offset', 0)
|
||||
max_scroll = max(0, len(lines) - visible_lines)
|
||||
|
||||
if is_input_matched(event, "up"):
|
||||
config.text_file_scroll_offset = max(0, scroll_offset - 1)
|
||||
update_key_state("up", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||
event.value)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "down"):
|
||||
config.text_file_scroll_offset = min(max_scroll, scroll_offset + 1)
|
||||
update_key_state("down", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||
event.value)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "page_up"):
|
||||
config.text_file_scroll_offset = max(0, scroll_offset - visible_lines)
|
||||
update_key_state("page_up", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||
event.value)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "page_down"):
|
||||
config.text_file_scroll_offset = min(max_scroll, scroll_offset + visible_lines)
|
||||
update_key_state("page_down", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||
event.value)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "cancel") or is_input_matched(event, "confirm"):
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
# Si pas de contenu, retourner au menu précédent
|
||||
if is_input_matched(event, "cancel") or is_input_matched(event, "confirm"):
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
|
||||
# Visualiseur de fichiers texte
|
||||
elif config.menu_state == "text_file_viewer":
|
||||
content = getattr(config, 'text_file_content', '')
|
||||
if content:
|
||||
from utils import wrap_text
|
||||
|
||||
# Calculer les dimensions
|
||||
controls_y = config.screen_height - int(config.screen_height * 0.037)
|
||||
margin = 40
|
||||
header_height = 60
|
||||
rect_width = config.screen_width - 2 * margin
|
||||
content_area_height = controls_y - 2 * margin - 10 - header_height - 20
|
||||
max_width = rect_width - 60
|
||||
|
||||
# Diviser le contenu en lignes et appliquer le word wrap
|
||||
original_lines = content.split('\n')
|
||||
wrapped_lines = []
|
||||
|
||||
for original_line in original_lines:
|
||||
if original_line.strip(): # Si la ligne n'est pas vide
|
||||
wrapped = wrap_text(original_line, config.small_font, max_width)
|
||||
wrapped_lines.extend(wrapped)
|
||||
else: # Ligne vide
|
||||
wrapped_lines.append('')
|
||||
|
||||
line_height = config.small_font.get_height() + 2
|
||||
visible_lines = int(content_area_height / line_height)
|
||||
|
||||
scroll_offset = getattr(config, 'text_file_scroll_offset', 0)
|
||||
max_scroll = max(0, len(wrapped_lines) - visible_lines)
|
||||
|
||||
if is_input_matched(event, "up"):
|
||||
config.text_file_scroll_offset = max(0, scroll_offset - 1)
|
||||
update_key_state("up", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||
event.value)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "down"):
|
||||
config.text_file_scroll_offset = min(max_scroll, scroll_offset + 1)
|
||||
update_key_state("down", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||
event.value)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "page_up"):
|
||||
config.text_file_scroll_offset = max(0, scroll_offset - visible_lines)
|
||||
update_key_state("page_up", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||
event.value)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "page_down"):
|
||||
config.text_file_scroll_offset = min(max_scroll, scroll_offset + visible_lines)
|
||||
update_key_state("page_down", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||
event.value)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "cancel") or is_input_matched(event, "confirm"):
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
# Si pas de contenu, retourner au menu précédent
|
||||
if is_input_matched(event, "cancel") or is_input_matched(event, "confirm"):
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
|
||||
elif config.menu_state == "history_error_details":
|
||||
if is_input_matched(event, "confirm") or is_input_matched(event, "cancel"):
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
|
||||
@@ -189,32 +189,38 @@ THEME_COLORS = {
|
||||
# Néon image grille des systèmes
|
||||
"neon": (0, 134, 179), # bleu
|
||||
# Dégradé sombre pour le fond
|
||||
"background_top": (30, 40, 50),
|
||||
"background_bottom": (60, 80, 100), # noir vers bleu foncé
|
||||
"background_top": (20, 25, 35),
|
||||
"background_bottom": (45, 55, 75), # noir vers bleu foncé
|
||||
# Fond des cadres
|
||||
"button_idle": (50, 50, 70, 150), # Bleu sombre métal
|
||||
"button_idle": (45, 50, 65, 180), # Bleu sombre métal avec plus d'opacité
|
||||
# Fond des boutons sélectionnés
|
||||
"button_selected": (70, 70, 100, 200), # Bleu plus clair
|
||||
"button_selected": (70, 80, 110, 220), # Bleu plus clair
|
||||
# Fond des boutons hover dans les popups ou menu
|
||||
"button_hover": (255, 0, 255, 220), # Rose
|
||||
"button_hover": (255, 0, 255, 240), # Rose vif
|
||||
# Générique
|
||||
"text": (255, 255, 255), # blanc
|
||||
# Texte sélectionné (alias pour compatibilité)
|
||||
"text_selected": (0, 255, 0), # utilise le même vert que fond_lignes
|
||||
# Erreur
|
||||
"error_text": (255, 0, 0), # rouge
|
||||
"error_text": (255, 60, 60), # rouge vif
|
||||
# Succès
|
||||
"success_text": (0, 255, 0), # vert
|
||||
"success_text": (0, 255, 150), # vert cyan
|
||||
# Avertissement
|
||||
"warning_text": (255, 100, 0), # orange
|
||||
"warning_text": (255, 150, 0), # orange vif
|
||||
# Titres
|
||||
"title_text": (200, 200, 200), # gris clair
|
||||
"title_text": (220, 220, 230), # gris très clair
|
||||
# Bordures
|
||||
"border": (150, 150, 150), # Bordures grises subtiles
|
||||
"border_selected": (0, 255, 0), # Bordure verte pour sélection
|
||||
"border": (100, 120, 150), # Bordures bleutées
|
||||
"border_selected": (0, 255, 150), # Bordure verte cyan pour sélection
|
||||
# Couleurs pour filtres
|
||||
"green": (0, 255, 0), # vert
|
||||
"red": (255, 0, 0), # rouge
|
||||
# Nouvelles couleurs pour effets modernes
|
||||
"shadow": (0, 0, 0, 100), # Ombre portée
|
||||
"glow": (100, 180, 255, 40), # Effet glow bleu doux
|
||||
"highlight": (255, 255, 255, 20), # Reflet subtil
|
||||
"accent_gradient_start": (80, 120, 200), # Début dégradé accent
|
||||
"accent_gradient_end": (120, 80, 200), # Fin dégradé accent
|
||||
}
|
||||
|
||||
# Général, résolution, overlay
|
||||
@@ -228,34 +234,104 @@ def init_display():
|
||||
screen = pygame.display.set_mode((screen_width, screen_height))
|
||||
config.screen_width = screen_width
|
||||
config.screen_height = screen_height
|
||||
# Initialisation de OVERLAY
|
||||
# Initialisation de OVERLAY avec effet glassmorphism
|
||||
OVERLAY = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA)
|
||||
OVERLAY.fill((0, 0, 0, 150)) # Transparence augmentée
|
||||
OVERLAY.fill((5, 10, 20, 160)) # Bleu très foncé semi-transparent pour effet verre
|
||||
logger.debug(f"Écran initialisé avec résolution : {screen_width}x{screen_height}")
|
||||
return screen
|
||||
|
||||
# Fond d'écran dégradé
|
||||
def draw_gradient(screen, top_color, bottom_color):
|
||||
"""Dessine un fond dégradé vertical avec des couleurs vibrantes."""
|
||||
"""Dessine un fond dégradé vertical avec des couleurs vibrantes et texture de grain."""
|
||||
height = screen.get_height()
|
||||
width = screen.get_width()
|
||||
top_color = pygame.Color(*top_color)
|
||||
bottom_color = pygame.Color(*bottom_color)
|
||||
|
||||
# Dégradé principal
|
||||
for y in range(height):
|
||||
ratio = y / height
|
||||
color = top_color.lerp(bottom_color, ratio)
|
||||
pygame.draw.line(screen, color, (0, y), (screen.get_width(), y))
|
||||
pygame.draw.line(screen, color, (0, y), (width, y))
|
||||
|
||||
# Ajouter une texture de grain subtile pour plus de profondeur
|
||||
grain_surface = pygame.Surface((width, height), pygame.SRCALPHA)
|
||||
import random
|
||||
random.seed(42) # Seed fixe pour cohérence
|
||||
for _ in range(width * height // 200): # Réduire la densité pour performance
|
||||
x = random.randint(0, width - 1)
|
||||
y = random.randint(0, height - 1)
|
||||
alpha = random.randint(5, 20)
|
||||
grain_surface.set_at((x, y), (255, 255, 255, alpha))
|
||||
screen.blit(grain_surface, (0, 0))
|
||||
|
||||
|
||||
def draw_shadow(surface, rect, offset=6, alpha=120):
|
||||
"""Dessine une ombre portée pour un rectangle."""
|
||||
shadow = pygame.Surface((rect.width + offset, rect.height + offset), pygame.SRCALPHA)
|
||||
pygame.draw.rect(shadow, (0, 0, 0, alpha), (0, 0, rect.width + offset, rect.height + offset), border_radius=15)
|
||||
return shadow
|
||||
|
||||
|
||||
def draw_glow_effect(screen, rect, color, intensity=80, size=10):
|
||||
"""Dessine un effet de glow autour d'un rectangle."""
|
||||
glow = pygame.Surface((rect.width + size * 2, rect.height + size * 2), pygame.SRCALPHA)
|
||||
for i in range(size):
|
||||
alpha = int(intensity * (1 - i / size))
|
||||
pygame.draw.rect(glow, (*color[:3], alpha),
|
||||
(i, i, rect.width + (size - i) * 2, rect.height + (size - i) * 2),
|
||||
border_radius=15)
|
||||
screen.blit(glow, (rect.x - size, rect.y - size))
|
||||
|
||||
# Nouvelle fonction pour dessiner un bouton stylisé
|
||||
def draw_stylized_button(screen, text, x, y, width, height, selected=False):
|
||||
"""Dessine un bouton moderne avec effet de survol et bordure arrondie."""
|
||||
button_surface = pygame.Surface((width, height), pygame.SRCALPHA)
|
||||
"""Dessine un bouton moderne avec effet de survol, ombre et bordure arrondie."""
|
||||
# Ombre portée subtile
|
||||
shadow_surf = pygame.Surface((width + 6, height + 6), pygame.SRCALPHA)
|
||||
pygame.draw.rect(shadow_surf, THEME_COLORS["shadow"], (3, 3, width, height), border_radius=12)
|
||||
screen.blit(shadow_surf, (x - 3, y - 3))
|
||||
|
||||
button_color = THEME_COLORS["button_hover"] if selected else THEME_COLORS["button_idle"]
|
||||
pygame.draw.rect(button_surface, button_color, (0, 0, width, height), border_radius=12)
|
||||
pygame.draw.rect(button_surface, THEME_COLORS["border"], (0, 0, width, height), 2, border_radius=12)
|
||||
|
||||
button_surface = pygame.Surface((width, height), pygame.SRCALPHA)
|
||||
|
||||
# Fond avec dégradé subtil pour bouton sélectionné
|
||||
if selected:
|
||||
glow_surface = pygame.Surface((width + 10, height + 10), pygame.SRCALPHA)
|
||||
pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (5, 5, width, height), border_radius=12)
|
||||
screen.blit(glow_surface, (x - 5, y - 5))
|
||||
# Créer le dégradé
|
||||
for i in range(height):
|
||||
ratio = i / height
|
||||
brightness = 1 + 0.2 * ratio
|
||||
r = min(255, int(button_color[0] * brightness))
|
||||
g = min(255, int(button_color[1] * brightness))
|
||||
b = min(255, int(button_color[2] * brightness))
|
||||
alpha = button_color[3] if len(button_color) > 3 else 255
|
||||
rect = pygame.Rect(0, i, width, 1)
|
||||
pygame.draw.rect(button_surface, (r, g, b, alpha), rect)
|
||||
|
||||
# Appliquer les coins arrondis avec un masque
|
||||
mask_surface = pygame.Surface((width, height), pygame.SRCALPHA)
|
||||
pygame.draw.rect(mask_surface, (255, 255, 255, 255), (0, 0, width, height), border_radius=12)
|
||||
button_surface.blit(mask_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)
|
||||
else:
|
||||
pygame.draw.rect(button_surface, button_color, (0, 0, width, height), border_radius=12)
|
||||
|
||||
# Reflet en haut
|
||||
highlight = pygame.Surface((width - 4, height // 3), pygame.SRCALPHA)
|
||||
highlight.fill(THEME_COLORS["highlight"])
|
||||
button_surface.blit(highlight, (2, 2))
|
||||
|
||||
# Bordure
|
||||
pygame.draw.rect(button_surface, THEME_COLORS["border"], (0, 0, width, height), 2, border_radius=12)
|
||||
|
||||
if selected:
|
||||
# Effet glow doux pour sélection
|
||||
glow_surface = pygame.Surface((width + 16, height + 16), pygame.SRCALPHA)
|
||||
for i in range(6):
|
||||
alpha = int(40 * (1 - i / 6))
|
||||
pygame.draw.rect(glow_surface, (*THEME_COLORS["glow"][:3], alpha),
|
||||
(i, i, width + 16 - i*2, height + 16 - i*2), border_radius=15)
|
||||
screen.blit(glow_surface, (x - 8, y - 8))
|
||||
|
||||
screen.blit(button_surface, (x, y))
|
||||
|
||||
# Vérifier si le texte dépasse la largeur disponible
|
||||
@@ -636,14 +712,35 @@ def draw_platform_grid(screen):
|
||||
|
||||
# Effet de pulsation subtil pour le titre - calculé une seule fois par frame
|
||||
current_time = pygame.time.get_ticks()
|
||||
pulse_factor = 0.05 * (1 + math.sin(current_time / 500))
|
||||
title_glow = pygame.Surface((title_rect_inflated.width + 10, title_rect_inflated.height + 10), pygame.SRCALPHA)
|
||||
pygame.draw.rect(title_glow, THEME_COLORS["neon"] + (int(40 * pulse_factor),),
|
||||
title_glow.get_rect(), border_radius=14)
|
||||
screen.blit(title_glow, (title_rect_inflated.left - 5, title_rect_inflated.top - 5))
|
||||
pulse_factor = 0.08 * (1 + math.sin(current_time / 400))
|
||||
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12)
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||||
# Ombre portée pour le titre
|
||||
shadow_surf = pygame.Surface((title_rect_inflated.width + 12, title_rect_inflated.height + 12), pygame.SRCALPHA)
|
||||
pygame.draw.rect(shadow_surf, (0, 0, 0, 140), (6, 6, title_rect_inflated.width, title_rect_inflated.height), border_radius=16)
|
||||
screen.blit(shadow_surf, (title_rect_inflated.left - 6, title_rect_inflated.top - 6))
|
||||
|
||||
# Glow multicouche pour le titre
|
||||
for i in range(2):
|
||||
glow_size = title_rect_inflated.inflate(15 + i * 8, 15 + i * 8)
|
||||
title_glow = pygame.Surface((glow_size.width, glow_size.height), pygame.SRCALPHA)
|
||||
alpha = int((30 + 20 * pulse_factor) * (1 - i / 2))
|
||||
pygame.draw.rect(title_glow, (*THEME_COLORS["neon"][:3], alpha),
|
||||
title_glow.get_rect(), border_radius=16 + i * 2)
|
||||
screen.blit(title_glow, (title_rect_inflated.left - 8 - i * 4, title_rect_inflated.top - 8 - i * 4)) # Fond du titre avec dégradé
|
||||
title_bg = pygame.Surface((title_rect_inflated.width, title_rect_inflated.height), pygame.SRCALPHA)
|
||||
for i in range(title_rect_inflated.height):
|
||||
ratio = i / title_rect_inflated.height
|
||||
alpha = int(THEME_COLORS["button_idle"][3] * (1 + ratio * 0.1))
|
||||
pygame.draw.line(title_bg, (*THEME_COLORS["button_idle"][:3], alpha),
|
||||
(0, i), (title_rect_inflated.width, i))
|
||||
screen.blit(title_bg, title_rect_inflated.topleft)
|
||||
|
||||
# Reflet en haut du titre
|
||||
highlight = pygame.Surface((title_rect_inflated.width - 8, title_rect_inflated.height // 3), pygame.SRCALPHA)
|
||||
highlight.fill((255, 255, 255, 25))
|
||||
screen.blit(highlight, (title_rect_inflated.left + 4, title_rect_inflated.top + 4))
|
||||
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=14)
|
||||
screen.blit(title_surface, title_rect)
|
||||
|
||||
# Configuration de la grille - calculée une seule fois
|
||||
@@ -689,9 +786,11 @@ def draw_platform_grid(screen):
|
||||
if total_pages > 1:
|
||||
page_indicator_text = _("platform_page").format(config.current_page + 1, total_pages)
|
||||
page_indicator = config.small_font.render(page_indicator_text, True, THEME_COLORS["text"])
|
||||
# Positionner au-dessus du footer (réserver ~80px pour le footer)
|
||||
footer_reserved_height = 80
|
||||
page_y = config.screen_height - footer_reserved_height - page_indicator.get_height() - 10
|
||||
# Position fixe : 5px au-dessus du footer
|
||||
# Le footer commence à screen_height - rect_height - 5 (voir draw_controls)
|
||||
# On estime la hauteur du footer à environ 50-60px selon le contenu
|
||||
# Pour être sûr, on positionne à screen_height - 60px (hauteur footer) - 5px (marge) - hauteur du texte
|
||||
page_y = config.screen_height - 65 - page_indicator.get_height()
|
||||
page_rect = page_indicator.get_rect(center=(config.screen_width // 2, page_y))
|
||||
screen.blit(page_indicator, page_rect)
|
||||
|
||||
@@ -860,6 +959,17 @@ def draw_game_list(screen):
|
||||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20))
|
||||
title_rect_inflated = title_rect.inflate(60, 30)
|
||||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||||
|
||||
# Ombre pour le titre de recherche
|
||||
shadow = pygame.Surface((title_rect_inflated.width + 10, title_rect_inflated.height + 10), pygame.SRCALPHA)
|
||||
pygame.draw.rect(shadow, (0, 0, 0, 120), (5, 5, title_rect_inflated.width, title_rect_inflated.height), border_radius=14)
|
||||
screen.blit(shadow, (title_rect_inflated.left - 5, title_rect_inflated.top - 5))
|
||||
|
||||
# Glow pour recherche active
|
||||
glow = pygame.Surface((title_rect_inflated.width + 20, title_rect_inflated.height + 20), pygame.SRCALPHA)
|
||||
pygame.draw.rect(glow, (*THEME_COLORS["glow"][:3], 60), glow.get_rect(), border_radius=16)
|
||||
screen.blit(glow, (title_rect_inflated.left - 10, title_rect_inflated.top - 10))
|
||||
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12)
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||||
screen.blit(title_surface, title_rect)
|
||||
@@ -884,11 +994,30 @@ def draw_game_list(screen):
|
||||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, title_surface.get_height() // 2 + 20))
|
||||
title_rect_inflated = title_rect.inflate(60, 30)
|
||||
title_rect_inflated.topleft = ((config.screen_width - title_rect_inflated.width) // 2, 10)
|
||||
|
||||
# Ombre et glow pour titre normal
|
||||
shadow = pygame.Surface((title_rect_inflated.width + 10, title_rect_inflated.height + 10), pygame.SRCALPHA)
|
||||
pygame.draw.rect(shadow, (0, 0, 0, 120), (5, 5, title_rect_inflated.width, title_rect_inflated.height), border_radius=14)
|
||||
screen.blit(shadow, (title_rect_inflated.left - 5, title_rect_inflated.top - 5))
|
||||
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=12)
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=12)
|
||||
screen.blit(title_surface, title_rect)
|
||||
|
||||
# Ombre portée pour le cadre principal
|
||||
shadow_rect = pygame.Rect(rect_x + 6, rect_y + 6, rect_width, rect_height)
|
||||
shadow_surf = pygame.Surface((rect_width + 8, rect_height + 8), pygame.SRCALPHA)
|
||||
pygame.draw.rect(shadow_surf, (0, 0, 0, 100), (4, 4, rect_width, rect_height), border_radius=14)
|
||||
screen.blit(shadow_surf, (rect_x - 4, rect_y - 4))
|
||||
|
||||
# Fond du cadre avec légère transparence glassmorphism
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||||
|
||||
# Reflet en haut du cadre
|
||||
highlight = pygame.Surface((rect_width - 8, 40), pygame.SRCALPHA)
|
||||
highlight.fill((255, 255, 255, 15))
|
||||
screen.blit(highlight, (rect_x + 4, rect_y + 4))
|
||||
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||||
|
||||
# Largeur colonne taille (15%) mini 120px, reste pour nom
|
||||
@@ -947,10 +1076,29 @@ def draw_game_list(screen):
|
||||
size_rect.midright = (rect_x + rect_width - 20, row_center_y)
|
||||
if i == config.current_game:
|
||||
glow_width = rect_width - 40
|
||||
glow_height = name_rect.height + 10
|
||||
glow_surface = pygame.Surface((glow_width, glow_height), pygame.SRCALPHA)
|
||||
pygame.draw.rect(glow_surface, THEME_COLORS["fond_lignes"] + (50,), (0, 0, glow_width, glow_height), border_radius=8)
|
||||
screen.blit(glow_surface, (rect_x + 20, row_center_y - glow_height // 2))
|
||||
glow_height = name_rect.height + 12
|
||||
|
||||
# Effet de glow plus doux pour la sélection
|
||||
glow_surface = pygame.Surface((glow_width + 6, glow_height + 6), pygame.SRCALPHA)
|
||||
alpha = 50
|
||||
pygame.draw.rect(glow_surface, (*THEME_COLORS["fond_lignes"][:3], alpha),
|
||||
(3, 3, glow_width, glow_height),
|
||||
border_radius=8)
|
||||
screen.blit(glow_surface, (rect_x + 17, row_center_y - glow_height // 2 - 3))
|
||||
|
||||
# Fond principal de la sélection avec dégradé subtil
|
||||
selection_bg = pygame.Surface((glow_width, glow_height), pygame.SRCALPHA)
|
||||
for j in range(glow_height):
|
||||
ratio = j / glow_height
|
||||
alpha = int(60 + 20 * ratio)
|
||||
pygame.draw.line(selection_bg, (*THEME_COLORS["fond_lignes"][:3], alpha),
|
||||
(0, j), (glow_width, j))
|
||||
screen.blit(selection_bg, (rect_x + 20, row_center_y - glow_height // 2))
|
||||
|
||||
# Bordure lumineuse plus subtile
|
||||
border_rect = pygame.Rect(rect_x + 20, row_center_y - glow_height // 2, glow_width, glow_height)
|
||||
pygame.draw.rect(screen, (*THEME_COLORS["fond_lignes"][:3], 120), border_rect, width=1, border_radius=8)
|
||||
|
||||
screen.blit(name_surface, name_rect)
|
||||
screen.blit(size_surface, size_rect)
|
||||
|
||||
@@ -1777,9 +1925,9 @@ def draw_language_menu(screen):
|
||||
# Instructions (placer juste au-dessus du footer sans chevauchement)
|
||||
instruction_text = _("language_select_instruction")
|
||||
instruction_surface = config.small_font.render(instruction_text, True, THEME_COLORS["text"])
|
||||
footer_reserved = 72 # hauteur approximative footer (barre bas) + marge
|
||||
bottom_margin = 12
|
||||
instruction_y = config.screen_height - footer_reserved - bottom_margin
|
||||
# Position fixe : 5px au-dessus du footer
|
||||
footer_height = 70 # Hauteur estimée du footer
|
||||
instruction_y = config.screen_height - footer_height - instruction_surface.get_height() - 5
|
||||
# Empêcher un chevauchement avec les derniers boutons si espace réduit
|
||||
last_button_bottom = start_y + (len(available_languages) - 1) * (button_height + button_spacing) + button_height
|
||||
min_gap = 16
|
||||
@@ -1799,9 +1947,10 @@ def draw_menu_instruction(screen, instruction_text, last_button_bottom=None):
|
||||
return
|
||||
try:
|
||||
instruction_surface = config.small_font.render(instruction_text, True, THEME_COLORS["text"])
|
||||
footer_reserved = 72
|
||||
bottom_margin = 12
|
||||
instruction_y = config.screen_height - footer_reserved - bottom_margin
|
||||
# Position fixe : 5px au-dessus du footer
|
||||
footer_height = 70 # Hauteur estimée du footer
|
||||
instruction_y = config.screen_height - footer_height - instruction_surface.get_height() - 5
|
||||
# Empêcher chevauchement avec le dernier bouton
|
||||
min_gap = 16
|
||||
if last_button_bottom is not None and instruction_y - last_button_bottom < min_gap:
|
||||
instruction_y = last_button_bottom + min_gap
|
||||
@@ -1876,10 +2025,13 @@ def draw_display_menu(screen):
|
||||
selected=(i == selected)
|
||||
)
|
||||
|
||||
# Aide en bas de l'écran
|
||||
# Aide en bas de l'écran - Position fixe au-dessus du footer
|
||||
instruction_text = _("language_select_instruction")
|
||||
instruction_surface = config.small_font.render(instruction_text, True, THEME_COLORS["text"])
|
||||
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, config.screen_height - 50))
|
||||
# Calculer la position en fonction de la hauteur du footer (environ 60-70px) + marge de 5px
|
||||
footer_height = 70 # Hauteur estimée du footer
|
||||
instruction_y = config.screen_height - footer_height - instruction_surface.get_height() - 5
|
||||
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, instruction_y))
|
||||
screen.blit(instruction_surface, instruction_rect)
|
||||
|
||||
def draw_pause_menu(screen, selected_option):
|
||||
@@ -2975,6 +3127,9 @@ def draw_history_game_options(screen):
|
||||
if ext in ['.zip', '.rar']:
|
||||
options.append("extract_archive")
|
||||
option_labels.append(_("history_option_extract_archive"))
|
||||
elif ext == '.txt':
|
||||
options.append("open_file")
|
||||
option_labels.append(_("history_option_open_file"))
|
||||
elif status in ["Erreur", "Error", "Canceled"]:
|
||||
options.append("error_info")
|
||||
option_labels.append(_("history_option_error_info"))
|
||||
@@ -3250,6 +3405,96 @@ def draw_history_extract_archive(screen):
|
||||
draw_stylized_button(screen, _("button_OK"), rect_x + (rect_width - button_width) // 2, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=True)
|
||||
|
||||
|
||||
def draw_text_file_viewer(screen):
|
||||
"""Affiche le contenu d'un fichier texte avec défilement."""
|
||||
screen.blit(OVERLAY, (0, 0))
|
||||
|
||||
# Récupérer les données du fichier texte
|
||||
content = getattr(config, 'text_file_content', '')
|
||||
filename = getattr(config, 'text_file_name', 'Unknown')
|
||||
scroll_offset = getattr(config, 'text_file_scroll_offset', 0)
|
||||
|
||||
# Dimensions
|
||||
margin = 40
|
||||
header_height = 60
|
||||
controls_y = config.screen_height - int(config.screen_height * 0.037)
|
||||
bottom_margin = 10
|
||||
|
||||
rect_width = config.screen_width - 2 * margin
|
||||
rect_height = controls_y - 2 * margin - bottom_margin
|
||||
rect_x = margin
|
||||
rect_y = margin
|
||||
|
||||
content_area_y = rect_y + header_height
|
||||
content_area_height = rect_height - header_height - 20
|
||||
|
||||
# Fond principal
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||||
|
||||
# Titre/nom du fichier
|
||||
title_text = f"{filename}"
|
||||
title_surface = config.font.render(title_text, True, THEME_COLORS["text"])
|
||||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, rect_y + 30))
|
||||
screen.blit(title_surface, title_rect)
|
||||
|
||||
# Séparateur
|
||||
pygame.draw.line(screen, THEME_COLORS["border"], (rect_x + 20, content_area_y - 10), (rect_x + rect_width - 20, content_area_y - 10), 2)
|
||||
|
||||
# Contenu du fichier
|
||||
if content:
|
||||
# Diviser le contenu en lignes et appliquer le word wrap
|
||||
original_lines = content.split('\n')
|
||||
wrapped_lines = []
|
||||
max_width = rect_width - 60
|
||||
|
||||
# Appliquer wrap_text à chaque ligne originale
|
||||
for original_line in original_lines:
|
||||
if original_line.strip(): # Si la ligne n'est pas vide
|
||||
wrapped = wrap_text(original_line, config.small_font, max_width)
|
||||
wrapped_lines.extend(wrapped)
|
||||
else: # Ligne vide
|
||||
wrapped_lines.append('')
|
||||
|
||||
line_height = config.small_font.get_height() + 2
|
||||
|
||||
# Calculer le nombre de lignes visibles
|
||||
visible_lines = int(content_area_height / line_height)
|
||||
|
||||
# Appliquer le scroll
|
||||
start_line = scroll_offset
|
||||
end_line = min(start_line + visible_lines, len(wrapped_lines))
|
||||
|
||||
# Afficher les lignes visibles
|
||||
for i, line_index in enumerate(range(start_line, end_line)):
|
||||
if line_index < len(wrapped_lines):
|
||||
line = wrapped_lines[line_index]
|
||||
line_surface = config.small_font.render(line, True, THEME_COLORS["text"])
|
||||
line_rect = line_surface.get_rect(left=rect_x + 30, top=content_area_y + i * line_height)
|
||||
screen.blit(line_surface, line_rect)
|
||||
|
||||
# Scrollbar si nécessaire
|
||||
if len(wrapped_lines) > visible_lines:
|
||||
scrollbar_height = int((visible_lines / len(wrapped_lines)) * content_area_height)
|
||||
scrollbar_y = content_area_y + int((scroll_offset / len(wrapped_lines)) * content_area_height)
|
||||
scrollbar_x = rect_x + rect_width - 15
|
||||
|
||||
# Fond de la scrollbar
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], (scrollbar_x, content_area_y, 8, content_area_height), border_radius=4)
|
||||
# Barre de défilement
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_hover"], (scrollbar_x, scrollbar_y, 8, scrollbar_height), border_radius=4)
|
||||
|
||||
# Indicateur de position
|
||||
position_text = f"{scroll_offset + 1}-{end_line}/{len(wrapped_lines)}"
|
||||
position_surface = config.small_font.render(position_text, True, THEME_COLORS["title_text"])
|
||||
position_rect = position_surface.get_rect(right=rect_x + rect_width - 30, bottom=rect_y + rect_height - 10)
|
||||
screen.blit(position_surface, position_rect)
|
||||
else:
|
||||
# Aucun contenu
|
||||
no_content_text = "Empty file"
|
||||
no_content_surface = config.font.render(no_content_text, True, THEME_COLORS["title_text"])
|
||||
no_content_rect = no_content_surface.get_rect(center=(config.screen_width // 2, content_area_y + content_area_height // 2))
|
||||
screen.blit(no_content_surface, no_content_rect)
|
||||
|
||||
|
||||
def draw_scraper_screen(screen):
|
||||
|
||||
@@ -242,7 +242,8 @@
|
||||
"history_game_options_title": "Spiel Optionen",
|
||||
"history_option_download_folder": "Datei lokalisieren",
|
||||
"history_option_extract_archive": "Archiv extrahieren",
|
||||
"history_option_scraper": "Metadaten scrapen",
|
||||
"history_option_open_file": "Datei öffnen",
|
||||
"history_option_scraper": "Metadaten abrufen",
|
||||
"history_option_delete_game": "Spiel löschen",
|
||||
"history_option_error_info": "Fehlerdetails",
|
||||
"history_option_retry": "Download wiederholen",
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
"history_game_options_title": "Game Options",
|
||||
"history_option_download_folder": "Locate file",
|
||||
"history_option_extract_archive": "Extract archive",
|
||||
"history_option_open_file": "Open file",
|
||||
"history_option_scraper": "Scrape metadata",
|
||||
"history_option_delete_game": "Delete game",
|
||||
"history_option_error_info": "Error details",
|
||||
|
||||
@@ -244,7 +244,8 @@
|
||||
"history_game_options_title": "Opciones del juego",
|
||||
"history_option_download_folder": "Localizar archivo",
|
||||
"history_option_extract_archive": "Extraer archivo",
|
||||
"history_option_scraper": "Scraper metadatos",
|
||||
"history_option_open_file": "Abrir archivo",
|
||||
"history_option_scraper": "Obtener metadatos",
|
||||
"history_option_delete_game": "Eliminar juego",
|
||||
"history_option_error_info": "Detalles del error",
|
||||
"history_option_retry": "Reintentar descarga",
|
||||
|
||||
@@ -244,10 +244,11 @@
|
||||
"history_game_options_title": "Options du jeu",
|
||||
"history_option_download_folder": "Localiser le fichier",
|
||||
"history_option_extract_archive": "Extraire l'archive",
|
||||
"history_option_scraper": "Scraper métadonnées",
|
||||
"history_option_open_file": "Ouvrir le fichier",
|
||||
"history_option_scraper": "Récupérer métadonnées",
|
||||
"history_option_delete_game": "Supprimer le jeu",
|
||||
"history_option_error_info": "Détails de l'erreur",
|
||||
"history_option_retry": "Réessayer le téléchargement",
|
||||
"history_option_retry": "Retenter le téléchargement",
|
||||
"history_option_back": "Retour",
|
||||
"history_folder_path_label": "Chemin de destination :",
|
||||
"history_scraper_not_implemented": "Scraper pas encore implémenté",
|
||||
|
||||
@@ -241,6 +241,7 @@
|
||||
"history_game_options_title": "Opzioni gioco",
|
||||
"history_option_download_folder": "Localizza file",
|
||||
"history_option_extract_archive": "Estrai archivio",
|
||||
"history_option_open_file": "Apri file",
|
||||
"history_option_scraper": "Scraper metadati",
|
||||
"history_option_delete_game": "Elimina gioco",
|
||||
"history_option_error_info": "Dettagli errore",
|
||||
|
||||
@@ -243,7 +243,8 @@
|
||||
"history_game_options_title": "Opções do jogo",
|
||||
"history_option_download_folder": "Localizar arquivo",
|
||||
"history_option_extract_archive": "Extrair arquivo",
|
||||
"history_option_scraper": "Scraper metadados",
|
||||
"history_option_open_file": "Abrir arquivo",
|
||||
"history_option_scraper": "Obter metadados",
|
||||
"history_option_delete_game": "Excluir jogo",
|
||||
"history_option_error_info": "Detalhes do erro",
|
||||
"history_option_retry": "Tentar novamente",
|
||||
|
||||
@@ -1294,6 +1294,18 @@ def _handle_special_platforms(dest_dir, archive_path, before_dirs, iso_before=No
|
||||
if not success:
|
||||
return False, error_msg
|
||||
|
||||
# PSVita: extraction dans ux0/app + création fichier .psvita
|
||||
psvita_dir_normal = os.path.join(config.ROMS_FOLDER, "psvita")
|
||||
psvita_dir_symlink = os.path.join(config.ROMS_FOLDER, "psvita", "psvita")
|
||||
is_psvita = (dest_dir == psvita_dir_normal or dest_dir == psvita_dir_symlink)
|
||||
|
||||
if is_psvita:
|
||||
expected_base = os.path.splitext(os.path.basename(archive_path))[0]
|
||||
items_before = before_items if before_items is not None else before_dirs
|
||||
success, error_msg = handle_psvita(dest_dir, items_before, extracted_basename=expected_base)
|
||||
if not success:
|
||||
return False, error_msg
|
||||
|
||||
return True, None
|
||||
|
||||
def extract_zip(zip_path, dest_dir, url):
|
||||
@@ -1959,6 +1971,136 @@ def handle_scummvm(dest_dir, before_items, extracted_basename=None):
|
||||
return False, error_msg
|
||||
|
||||
|
||||
def handle_psvita(dest_dir, before_items, extracted_basename=None):
|
||||
"""Gère l'organisation spécifique des jeux PSVita extraits.
|
||||
|
||||
Structure attendue:
|
||||
- Archive RAR extraite → Dossier "Nom du jeu"/
|
||||
- Dans ce dossier → Fichier "IDJeu.zip" (ex: PCSE00890.zip)
|
||||
- Ce ZIP contient → Dossier "IDJeu" (ex: PCSE00890/)
|
||||
|
||||
Actions:
|
||||
1. Créer fichier "Nom du jeu [IDJeu].psvita" dans dest_dir
|
||||
2. Extraire IDJeu.zip dans config.SAVE_FOLDER/psvita/ux0/app/
|
||||
3. Supprimer le dossier temporaire "Nom du jeu"/
|
||||
|
||||
Args:
|
||||
dest_dir: Dossier de destination (psvita ou psvita/psvita)
|
||||
before_items: Set des éléments présents avant extraction
|
||||
extracted_basename: Nom de base de l'archive extraite (sans extension)
|
||||
"""
|
||||
logger.debug(f"Traitement spécifique PSVita dans: {dest_dir}")
|
||||
time.sleep(2) # Petite latence post-extraction
|
||||
|
||||
try:
|
||||
after_items = set(os.listdir(dest_dir))
|
||||
except Exception:
|
||||
after_items = set()
|
||||
|
||||
ignore_names = {"psvita", "images", "videos", "manuals", "media"}
|
||||
# Filtrer les nouveaux éléments (fichiers ou dossiers)
|
||||
new_items = [item for item in (after_items - before_items)
|
||||
if item not in ignore_names and not item.endswith('.psvita')]
|
||||
|
||||
if not new_items:
|
||||
logger.warning("PSVita: Aucun nouveau dossier détecté après extraction")
|
||||
return True, None
|
||||
|
||||
if not extracted_basename:
|
||||
extracted_basename = new_items[0] if new_items else "game"
|
||||
|
||||
# Chercher le dossier du jeu (normalement il n'y en a qu'un)
|
||||
game_folder = None
|
||||
for item in new_items:
|
||||
item_path = os.path.join(dest_dir, item)
|
||||
if os.path.isdir(item_path):
|
||||
game_folder = item
|
||||
game_folder_path = item_path
|
||||
break
|
||||
|
||||
if not game_folder:
|
||||
logger.error("PSVita: Aucun dossier de jeu trouvé après extraction")
|
||||
return False, "PSVita: Aucun dossier de jeu trouvé"
|
||||
|
||||
logger.debug(f"PSVita: Dossier de jeu trouvé: {game_folder}")
|
||||
|
||||
# Chercher le fichier ZIP à l'intérieur (IDJeu.zip)
|
||||
try:
|
||||
contents = os.listdir(game_folder_path)
|
||||
zip_files = [f for f in contents if f.lower().endswith('.zip')]
|
||||
|
||||
if not zip_files:
|
||||
logger.error(f"PSVita: Aucun fichier ZIP trouvé dans {game_folder}")
|
||||
return False, f"PSVita: Aucun ZIP trouvé dans {game_folder}"
|
||||
|
||||
# Prendre le premier ZIP trouvé
|
||||
zip_filename = zip_files[0]
|
||||
zip_path = os.path.join(game_folder_path, zip_filename)
|
||||
|
||||
# Extraire l'ID du jeu (nom du ZIP sans extension)
|
||||
game_id = os.path.splitext(zip_filename)[0]
|
||||
logger.debug(f"PSVita: ZIP trouvé: {zip_filename}, ID du jeu: {game_id}")
|
||||
|
||||
# 1. Créer le fichier .psvita dans dest_dir
|
||||
psvita_filename = f"{game_folder} [{game_id}].psvita"
|
||||
psvita_file_path = os.path.join(dest_dir, psvita_filename)
|
||||
|
||||
try:
|
||||
# Créer un fichier vide .psvita
|
||||
with open(psvita_file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(f"# PSVita Game\n")
|
||||
f.write(f"# Game: {game_folder}\n")
|
||||
f.write(f"# ID: {game_id}\n")
|
||||
logger.info(f"PSVita: Fichier .psvita créé: {psvita_filename}")
|
||||
except Exception as e:
|
||||
logger.error(f"PSVita: Erreur création fichier .psvita: {e}")
|
||||
return False, f"Erreur création {psvita_filename}: {e}"
|
||||
|
||||
# 2. Extraire le ZIP dans le dossier parent de config.SAVE_FOLDER/psvita/ux0/app/
|
||||
save_parent2 = os.path.dirname(config.SAVE_FOLDER)
|
||||
save_parent = os.path.dirname(save_parent2)
|
||||
ux0_app_dir = os.path.join(save_parent, "psvita", "ux0", "app")
|
||||
os.makedirs(ux0_app_dir, exist_ok=True)
|
||||
|
||||
logger.debug(f"PSVita: Extraction de {zip_filename} dans {ux0_app_dir}")
|
||||
|
||||
try:
|
||||
import zipfile
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(ux0_app_dir)
|
||||
logger.info(f"PSVita: ZIP extrait avec succès dans {ux0_app_dir}")
|
||||
|
||||
# Vérifier que le dossier game_id existe bien
|
||||
game_id_path = os.path.join(ux0_app_dir, game_id)
|
||||
if not os.path.exists(game_id_path):
|
||||
logger.warning(f"PSVita: Le dossier {game_id} n'a pas été trouvé dans l'extraction")
|
||||
else:
|
||||
logger.info(f"PSVita: Dossier {game_id} confirmé dans ux0/app/")
|
||||
|
||||
except zipfile.BadZipFile as e:
|
||||
logger.error(f"PSVita: Fichier ZIP corrompu: {e}")
|
||||
return False, f"ZIP corrompu: {zip_filename}"
|
||||
except Exception as e:
|
||||
logger.error(f"PSVita: Erreur extraction ZIP: {e}")
|
||||
return False, f"Erreur extraction {zip_filename}: {e}"
|
||||
|
||||
# 3. Supprimer le dossier temporaire du jeu
|
||||
try:
|
||||
import shutil
|
||||
shutil.rmtree(game_folder_path)
|
||||
logger.info(f"PSVita: Dossier temporaire supprimé: {game_folder}")
|
||||
except Exception as e:
|
||||
logger.warning(f"PSVita: Impossible de supprimer {game_folder}: {e}")
|
||||
# Ne pas échouer pour ça, le jeu est quand même installé
|
||||
|
||||
logger.info(f"PSVita: Traitement terminé avec succès - {psvita_filename} créé, {game_id} installé dans ux0/app/")
|
||||
return True, None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"PSVita: Erreur générale: {e}", exc_info=True)
|
||||
return False, f"Erreur PSVita: {str(e)}"
|
||||
|
||||
|
||||
def handle_xbox(dest_dir, iso_files, url=None):
|
||||
"""Gère la conversion des fichiers Xbox extraits et met à jour l'UI (Converting)."""
|
||||
logger.debug(f"Traitement spécifique Xbox dans: {dest_dir}")
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2.3.2.8"
|
||||
"version": "2.3.2.9"
|
||||
}
|
||||
Reference in New Issue
Block a user