1
0
forked from Mirrors/RGSX

Compare commits

..

43 Commits

Author SHA1 Message Date
skymike03
7fbf936af6 v2.3.1.0
- correct bug for games with an " ' " apostrophe inside name not download
- filtrer platforms in web that are filtered too on main app
2025-11-01 21:09:25 +01:00
skymike03
5fa606b3de v2.3.0.9
- Clear history now keep current downloading or extracting
- converting xbox iso if symlink activated works now
- sanitize some display status
2025-10-25 17:18:47 +02:00
skymike03
179d10facd update retrobat launch script and add python to windows folder 2025-10-24 23:34:15 +02:00
skymike03
06c06d0223 v2.3.0.8
- add RGSX_WEB service for batocera only
- add an option to enable/disable RGSX Web Service at boot (in settings menu) . With that, you can have rgsx web server enabled without launching rgsx main appp.
2025-10-24 23:17:27 +02:00
skymike03
18e5f6d637 update release name 2025-10-23 17:30:56 +02:00
skymike03
d2a52c5a2e update push new test 2025-10-22 02:35:46 +02:00
skymike03
9df7a35e85 v2.3.0.7 changes for updates and release system simplify 2025-10-22 02:26:22 +02:00
skymike03
26f1f58d7a Merge branch 'main' of https://github.com/RetroGameSets/RGSX 2025-10-22 02:14:37 +02:00
skymike03
a9cdba3aa6 v2.3.0.7
- minor updates to install script
2025-10-22 02:14:35 +02:00
RGS
1a118247d9 Update Windows installation instructions in release.yml 2025-10-22 02:11:28 +02:00
RGS
5497727e2e Update release workflow to rename Retrobat package to use full name 2025-10-22 02:09:55 +02:00
skymike03
56a6d6f17b v2.3.0.6 2025-10-21 00:09:40 +02:00
skymike03
3e769e4b18 add minor updates on release 2025-10-21 00:05:33 +02:00
skymike03
3023a45402 v2.3.0.5
- add retrobat release test
2025-10-21 00:01:51 +02:00
skymike03
f1b2fad0d4 v2.3.0.4.1
-test update
2025-10-20 23:43:04 +02:00
skymike03
80844bda79 v2.3.0.4
- update links changed
2025-10-20 23:26:34 +02:00
skymike03
b19d4ff60a v2.3.0.3
- now rgsx download updates to github to avoid problems
2025-10-20 23:00:41 +02:00
skymike03
f6f8436022 add release test 2025-10-20 22:53:00 +02:00
skymike03
edb331d053 v2.3.0.2
- show toast notification when download completd but not open history
- move toast notification on top right corner
2025-10-20 21:07:08 +02:00
skymike03
059c38d8d6 v2.3.0.1
- Add game info scraper test (does not save information) : Push "confirm/download" button for 3sec to see picture/info of  a game
- correct ps3 decrypt for some games with special characters inside name
2025-10-20 19:02:28 +02:00
skymike03
acac05ea26 v2.3.0.0
- Add download queue function. You can add as many games you want, donwload queue starts and one game is download at time, and when finished, next game processed
- Add controls help on bottom screen
- Add toast notifcations for downloads when click download, to not open history and stay on gamelist
- Update 1fichier free mode handling if wait time is greater than 60 seconds (after already downloaded a game)
- Already Downloaded games are now in green and marked with [>] symbol
- Update translations
2025-10-19 23:58:48 +02:00
skymike03
6e59e954ec v2.2.4.8
- update ps3 extact with symlink activated
- add option to delete game if download error / canceled and game is not deleted automatically
2025-10-17 22:17:10 +02:00
skymike03
dac8114a0b v2.2.4.8
- Repair bug not extracting ps3 iso after download and decrypt and converting to .ps3 folder
2025-10-17 20:53:53 +02:00
skymike03
b46455aff2 v2.2.4.7 2025-10-16 23:28:40 +02:00
skymike03
893573b560 v2.2.4.7
- kills web server before launch new instance
2025-10-16 23:26:37 +02:00
skymike03
8618220f66 v2.2.4.6
- adding new download queue to web interface
- adding missing translations
2025-10-16 22:51:15 +02:00
skymike03
2fc881e6ab v2.2.4.5
- correct xbox convert bug
2025-10-16 21:00:03 +02:00
skymike03
a81139b50d v2.2.4.5
- correct xbox convert bug
2025-10-16 20:59:45 +02:00
skymike03
dd447e0bea mobile search amerlioration css 2025-10-14 23:58:42 +02:00
skymike03
b8b6277ba0 v2.2.4.4
-add system information to web interface and logs
-add new search feature on web interface home page can search game and download directly
- correct some bugs extacting bios
2025-10-14 23:22:28 +02:00
skymike03
34eafcd981 v2.2.4.3
- Add automatic extract/converted for web interface
- correct permissions bugs in windows for bios folders
2025-10-13 22:49:20 +02:00
skymike03
31c8f6a63e v2.2.4.2
- add 1fichier free mode handling (test) with wait time, like you download on 1fichier website. Don't need an api key anymore if you don"t have a subscription.
If you have any api key it will use in priority
2025-10-12 00:24:45 +02:00
skymike03
2caf0f80c7 v2.2.4.1
- add SUPPORT button on web interface to zip all necessary log files to send on discord
2025-10-11 15:47:42 +02:00
skymike03
b73c1e7a80 add rgsx_web.py 2025-10-08 23:37:20 +02:00
skymike03
b0087c9d58 v2.2.4.0
- Add web interface  for RGSX. You can barely do all things directly on computer or your smartphone on same network :)
- Enhanced  language support with new network error messages.
- Updated network.py to improve download error handling, including detailed error messages for various HTTP errors.
- Removed unnecessary web progress updates;
- Implemented real-time download status updates in the web interface.
- Added custom ROMs folder setting in rgsx_settings.py.
2025-10-08 23:34:18 +02:00
skymike03
8e60a44ccc v2.2.3.1
-add support for generic xbox controller no name
- add support for decrypting ps3 redump game on the fly after download, extract zip, for linux and windows and extract iso after in a  .ps3 folder
2025-10-06 13:32:46 +02:00
skymike03
75cbe5c259 v2.2.3.0
- add new menu in history, check if a game already exist, select a game to have infos about download folder, errors, retry download if error, infos about errors, extract an archive directly...
- correct bug with rar files
- joystick hot plug and play, if removed , keyboard take control, and vice versa
2025-10-05 03:51:15 +02:00
skymike03
669aae02b0 add dos game handling (extract, rename .pc) 2025-10-02 22:30:00 +02:00
skymike03
acd0ce337e correct language bug on forein languages 2025-10-02 18:58:24 +02:00
skymike03
0183916667 v2.2.2.8 don't extract psvita zip 2025-10-01 22:37:59 +02:00
skymike03
997de8bf36 gitignore update 2025-10-01 22:14:04 +02:00
retrogamesets
22a36df654 add dragonrise arcade joystick 2025-10-01 19:31:18 +02:00
retrogamesets
ac4c34089e v2.2.2.7 bis
- Removed unnecessary debug logging
- Simplified the extraction process in utils.py
- Enhanced extraction functions to handle special cases for Xbox and PS3 platforms more effectively.
- Improve the update of game list XML for retrobat
2025-09-21 23:33:04 +02:00
35 changed files with 9978 additions and 1801 deletions

133
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,133 @@
# Pour déclencher ce workflow : git tag v1.X.X && git push origin v1.X.X
# OU en une seule ligne : git tag v1.X.X && git push origin --tags
name: Release on tag
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
create_release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build RGSX Release Package
shell: bash
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME}"
mkdir -p dist
echo "Building RGSX package from ports/RGSX/ directory…"
cd ports/RGSX
zip -r "../../dist/RGSX_update_latest.zip" . \
-x "logs/*" \
"logs/**" \
"images/*" \
"images/**" \
"games/*" \
"games/**" \
"scripts/*" \
"scripts/**" \
"__pycache__/*" \
"__pycache__/**" \
"*.pyc" \
"sources.json" \
"*.log"
cd ../..
cp -f "dist/RGSX_update_latest.zip" "dist/RGSX_latest.zip"
echo "✓ RGSX package created successfully"
echo ""
echo "Building RGSX Full package (includes ports/ and windows/ directories)…"
zip -r "dist/RGSX_full_latest.zip" ports windows \
-x "ports/RGSX/logs/*" \
"ports/RGSX/logs/**" \
"ports/RGSX/images/*" \
"ports/RGSX/images/**" \
"ports/RGSX/games/*" \
"ports/RGSX/games/**" \
"ports/RGSX/scripts/*" \
"ports/RGSX/scripts/**" \
"ports/RGSX/__pycache__/*" \
"ports/RGSX/__pycache__/**" \
"ports/RGSX/*.pyc" \
"ports/RGSX/sources.json" \
"ports/RGSX/*.log" \
"windows/logs/*" \
"windows/*.xml" \
"ports/*.xml" \
"*.xml" \
"*.pyc" \
"*.xml" \
"*.log"
echo "✓ All packages created successfully"
ls -lh dist/
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: RGSX ${{ github.ref_name }}
generate_release_notes: true
draft: false
prerelease: false
body: |
# 📦 RGSX Release ${{ github.ref_name }}
## 📥 Automatic Installation (Only for batocera Knulli)
### ON PC :
1. Open File Manager (F1) then "Applications" and launch xterm
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
3. Launch RGSX from "Ports" menu
### ON RASPBERRY/ARM SBC / HANDHELD :
1. Connect your device with SSH on a computer/smartphone connected to same network (ssh root@IPADDRESS , pass:linux)
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
3. Launch RGSX from "Ports" menu
## 📥 Manual Installation
### Batocera/Knulli
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
2. Extract only PORTS folder in `/userdata/roms/`
3. Launch RGSX from the Ports menu
### Retrobat/Full Installation on Windows
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
2. Extract all folders in your Retrobat\roms folder
3. Launch RGSX from system "Windows"
## 📥 Manual Update (you shouldn't need to do this as RGSX updates automatically on each start)
#### Batocera/Knulli
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
2. Extract only PORTS folder in `/userdata/roms/`
3. Launch RGSX from the Ports menu
#### Retrobat
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
2. Extract all folders in your Retrobat\roms folder
3. Launch RGSX from system "Windows"
### 📖 Documentation
[README.md](https://github.com/${{ github.repository }}/blob/main/README.md)
files: |
dist/RGSX_update_latest.zip
dist/RGSX_full_latest.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

7
.gitignore vendored
View File

@@ -6,11 +6,14 @@ ports/RGSX/__pycache__/
ports/RGSX/sources.json
ports/gamelist.xml
windows/gamelist.xml
windows/logs/
ports/gamelist.xml
prboom/
*.log
*.rar
*.zip
.vscode/
ports/RGSX.bat
.venv/
*.py
audit_i18n.py
prune_i18n.py
Info.txt

View File

@@ -1,71 +0,0 @@
# Migration vers rgsx_settings.json
## Résumé des changements
Ce commit unifie les fichiers de configuration suivants en un seul fichier `rgsx_settings.json` :
- `accessibility.json``rgsx_settings.json` (clé: `accessibility`)
- `language.json``rgsx_settings.json` (clé: `language`)
- `music_config.json``rgsx_settings.json` (clé: `music_enabled`)
- `symlink_settings.json``rgsx_settings.json` (clé: `symlink`)
## Structure du nouveau fichier rgsx_settings.json
```json
{
"language": "fr",
"music_enabled": true,
"accessibility": {
"font_scale": 1.0
},
"symlink": {
"enabled": false,
"target_directory": ""
}
}
```
## Nouveau module rgsx_settings.py
Un nouveau module `rgsx_settings.py` a été créé pour centraliser la gestion des paramètres :
### Fonctions principales :
- `load_rgsx_settings()` : Charge tous les paramètres depuis rgsx_settings.json
- `save_rgsx_settings(settings)` : Sauvegarde tous les paramètres
- `migrate_old_settings()` : Migre automatiquement les anciens fichiers
## Fichiers modifiés
### Nouveau fichier : rgsx_settings.py
- Module dédié à la gestion des paramètres RGSX
- Contient toute la logique de chargement, sauvegarde et migration
- Documentation complète des fonctions
### config.py
- Ajout de `RGSX_SETTINGS_PATH`
- Import des fonctions depuis `rgsx_settings.py`
- Conservation temporaire des anciens chemins pour la migration
- Suppression des fonctions de gestion des paramètres (déplacées vers rgsx_settings.py)
### accessibility.py
- Import des fonctions depuis `rgsx_settings.py`
- Utilisation directe des fonctions importées au lieu de `config.`
### utils.py
- Import des fonctions depuis `rgsx_settings.py`
- Fonctions `load_music_config()` et `save_music_config()` mises à jour
### symlink_settings.py
- Import des fonctions depuis `rgsx_settings.py`
- Fonctions `load_symlink_settings()` et `save_symlink_settings()` mises à jour
- Compatibilité maintenue avec l'ancien format (`use_symlink_path`)
## Migration automatique
Le système détecte automatiquement les anciens fichiers et les migre vers le nouveau format :
1. Au premier lancement, `load_rgsx_settings()` vérifie si `rgsx_settings.json` existe
2. Si absent, il tente de migrer les données depuis les anciens fichiers
3. Les valeurs par défaut sont utilisées si aucun ancien fichier n'est trouvé
4. Le nouveau fichier unifié est créé automatiquement
5. Les anciens fichiers sont automatiquement supprimés après migration réussie

View File

@@ -1,77 +0,0 @@
# Symlink Option Feature
## Overview
This feature adds a simple toggle option to append the platform folder name to the download path, creating a symlink-friendly structure for external storage.
## How It Works
When the symlink option is **disabled** (default):
- Super Nintendo ROMs download to: `../roms/snes/`
- PlayStation 2 ROMs download to: `../roms/ps2/`
When the symlink option is **enabled**:
- Super Nintendo ROMs download to: `../roms/snes/snes/`
- PlayStation 2 ROMs download to: `../roms/ps2/ps2/`
This allows users to create symlinks from the platform folder to external storage locations.
## Usage
1. Open the pause menu (P key or Start button)
2. Navigate to "Symlink Option" (second to last option, before Quit)
3. Press Enter to toggle the option on/off
4. The menu will show the current status: "Symlink option enabled" or "Symlink option disabled"
## Implementation Details
### Files Added
- `symlink_settings.py` - Core functionality for managing the symlink option
### Files Modified
- `display.py` - Added symlink option to pause menu with dynamic status display
- `controls.py` - Added handling for symlink option toggle
- `network.py` - Modified download functions to use symlink paths when enabled
- Language files - Added translation strings for all supported languages
### Configuration
The symlink setting is stored in `symlink_settings.json` in the save folder:
```json
{
"use_symlink_path": false
}
```
### API Functions
- `get_symlink_option()` - Get current symlink option status
- `set_symlink_option(enabled)` - Enable/disable the symlink option
- `apply_symlink_path(base_path, platform_folder)` - Apply symlink path modification
## Example Use Case
1. Enable the symlink option
2. **Optional**: Create a symlink: `ln -s /external/storage/snes ../roms/snes/snes`
- If you don't create the symlink, the nested directories will be created automatically when you download ROMs
3. Download ROMs - the nested directories (like `../roms/snes/snes/`) will be created automatically if they don't exist
4. Now Super Nintendo ROMs will download to the external storage via the symlink (if created) or to the local nested directory
## Features
- **Simple Toggle**: Easy on/off switch in the pause menu
- **Persistent Settings**: Option is remembered between sessions
- **Multi-language Support**: Full internationalization
- **Backward Compatible**: Disabled by default, doesn't affect existing setups
- **Platform Agnostic**: Works with all platforms automatically
- **Automatic Directory Creation**: Nested directories are created automatically if they don't exist
## Technical Notes
- The option is disabled by default
- Settings are stored in JSON format
- Path modification is applied at download time
- Works with both regular downloads and 1fichier downloads
- No impact on existing ROMs or folder structure
- Missing directories are automatically created using `os.makedirs(dest_dir, exist_ok=True)`

View File

@@ -1,4 +1,4 @@
#!/bin/bash
# Supprimer SDL_VIDEODRIVER=fbcon pour laisser SDL choisir le pilote
# export SDL_VIDEODRIVER=fbcon
/usr/bin/python3 /userdata/roms/ports/RGSX
#!/usr/bin/env python3
SCRIPT_DIR=$(dirname "$(realpath "$0")")
python3 "$SCRIPT_DIR/__main__.py"

View File

@@ -15,6 +15,7 @@ import queue
import datetime
import subprocess
import sys
import threading
import config
from display import (
@@ -24,7 +25,7 @@ from display import (
draw_display_menu,
draw_history_list, draw_clear_history_dialog, draw_cancel_download_dialog,
draw_confirm_dialog, draw_reload_games_data_dialog, draw_popup, draw_gradient,
THEME_COLORS
draw_toast, show_toast, THEME_COLORS
)
from language import _
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates, cancel_all_downloads
@@ -32,10 +33,10 @@ from controls import handle_controls, validate_menu_state, process_key_repeats,
from controls_mapper import map_controls, draw_controls_mapping, get_actions
from controls import load_controls_config
from utils import (
load_sources, check_extension_before_download, extract_zip_data,
load_sources, check_extension_before_download, extract_data,
play_random_music, load_music_config, load_api_keys
)
from history import load_history, save_history
from history import load_history, save_history, load_downloaded_games
from config import OTA_data_ZIP
from rgsx_settings import get_sources_mode, get_custom_sources_url, get_sources_zip_url
from accessibility import load_accessibility_settings
@@ -60,18 +61,16 @@ logger = logging.getLogger(__name__)
# Ensure API key files (1Fichier, AllDebrid, RealDebrid) exist at startup so user can fill them before any download
try: # pragma: no cover
load_api_keys(False)
logger.debug("API key files ensured at startup")
except Exception as _e:
logger.warning(f"Cannot prepare API key files early: {_e}")
# Mise à jour de la gamelist Windows avant toute initialisation graphique (évite les conflits avec ES)
def _run_windows_gamelist_update():
try:
if platform.system() != "Windows":
if config.OPERATING_SYSTEM != "Windows":
return
script_path = os.path.join(config.APP_FOLDER, "update_gamelist_windows.py")
if not os.path.exists(script_path):
return
logger.info("Lancement de update_gamelist_windows.py depuis __main__ (pré-init)")
exe = sys.executable or "python"
# Exécuter rapidement avec capture sortie pour journaliser tout message utile
result = subprocess.run(
@@ -90,9 +89,7 @@ def _run_windows_gamelist_update():
_run_windows_gamelist_update()
# Pré-boot: Désactivé — pas de test Internet ni de mise à jour avant l'init
try:
logger.debug("Pré-boot: vérification des mises à jour désactivée")
config.update_checked = False
except Exception:
pass
@@ -142,7 +139,7 @@ logger.debug(f"Mode sources initial: {config.sources_mode}, URL custom: {config.
def detect_system_info():
"""Détecte les informations système (OS, architecture) via des commandes appropriées."""
try:
if platform.system() == "Windows":
if config.OPERATING_SYSTEM == "Windows":
# Commande pour Windows
result = subprocess.run(["wmic", "os", "get", "caption"], capture_output=True, text=True)
if result.returncode == 0:
@@ -166,7 +163,8 @@ config.init_font()
# Mise à jour de la résolution dans config
config.screen_width, config.screen_height = pygame.display.get_surface().get_size()
logger.debug(f"Résolution d'écran : {config.screen_width}x{config.screen_height}")
logger.debug(f"Resolution d'ecran : {config.screen_width}x{config.screen_height}")
print(f"Resolution ecran validee: {config.screen_width}x{config.screen_height}")
# Afficher un premier écran de chargement immédiatement pour éviter un écran noir
try:
@@ -179,11 +177,11 @@ try:
pygame.display.flip()
pygame.event.pump()
except Exception as e:
logger.debug(f"Impossible d'afficher l'écran de chargement initial: {e}")
logger.debug(f"Impossible d'afficher l'ecran de chargement initial: {e}")
# Détection des joysticks après init_display (plus stable sur Batocera)
try:
if platform.system() != "Windows":
if config.OPERATING_SYSTEM != "Windows":
time.sleep(0.05) # petite latence pour stabiliser SDL sur certains builds
count = pygame.joystick.get_count()
except Exception:
@@ -216,8 +214,8 @@ else:
# Des joysticks sont présents: activer le mode joystick et mémoriser le nom pour l'auto-préréglage
config.joystick = True
config.keyboard = False
print("Joystick détecté:", ", ".join(joystick_names))
logger.debug(f"Joysticks détectés: {joystick_names}")
print("Joystick detecte:", ", ".join(joystick_names))
logger.debug(f"Joysticks detectes: {joystick_names}")
@@ -228,7 +226,6 @@ config.selected_platform = 0
# Charger la configuration musique AVANT d'initialiser le mixer pour respecter le paramètre music_enabled
try:
load_music_config()
logger.debug(f"Configuration musique chargée: music_enabled={getattr(config, 'music_enabled', True)}")
except Exception as e:
logger.warning(f"Impossible de charger la configuration musique avant init mixer: {e}")
@@ -239,8 +236,6 @@ if getattr(config, 'music_enabled', True):
pygame.mixer.init()
except Exception as e:
logger.warning(f"Échec init mixer: {e}")
else:
logger.debug("Musique désactivée, on saute l'initialisation du mixer")
# Dossier musique Batocera
music_folder = os.path.join(config.APP_FOLDER, "assets", "music")
@@ -265,6 +260,10 @@ config.current_music = current_music # Met à jour la musique en cours dans con
config.history = load_history()
logger.debug(f"Historique de téléchargement : {len(config.history)} entrées")
# Chargement des jeux téléchargés
config.downloaded_games = load_downloaded_games()
logger.debug(f"Jeux téléchargés : {sum(len(v) for v in config.downloaded_games.values())} jeux")
# Vérification et chargement de la configuration des contrôles (après mises à jour et détection manette)
config.controls_config = load_controls_config()
@@ -320,10 +319,102 @@ if pygame.joystick.get_count() > 0:
logger.warning(f"Échec initialisation gamepad: {e}")
# ===== GESTION DU SERVEUR WEB =====
web_server_process = None
def start_web_server():
"""Démarre le serveur web en arrière-plan dans un processus séparé."""
global web_server_process
try:
web_server_script = os.path.join(config.APP_FOLDER, "rgsx_web.py")
logger.info(f"Tentative de démarrage du serveur web...")
logger.info(f"Script: {web_server_script}")
logger.info(f"Fichier existe: {os.path.exists(web_server_script)}")
if not os.path.exists(web_server_script):
logger.warning(f"Script serveur web introuvable: {web_server_script}")
return False
exe = sys.executable or "python"
logger.info(f"Exécutable Python: {exe}")
logger.info(f"Répertoire de travail: {config.APP_FOLDER}")
logger.info(f"Système: {config.OPERATING_SYSTEM}")
# Créer un fichier de log pour les erreurs du serveur web
web_server_log = os.path.join(config.log_dir, "rgsx_web_startup.log")
# Démarrer le processus en arrière-plan sans fenêtre console sur Windows
if config.OPERATING_SYSTEM == "Windows":
# Utiliser DETACHED_PROCESS pour cacher la console sur Windows
CREATE_NO_WINDOW = 0x08000000
logger.info(f"🚀 Lancement du serveur web (mode Windows CREATE_NO_WINDOW)...")
# Rediriger stdout/stderr vers un fichier de log pour capturer les erreurs
with open(web_server_log, 'w', encoding='utf-8') as log_file:
web_server_process = subprocess.Popen(
[exe, web_server_script],
stdout=log_file,
stderr=subprocess.STDOUT,
cwd=config.APP_FOLDER,
creationflags=CREATE_NO_WINDOW
)
else:
logger.info(f"🚀 Lancement du serveur web (mode Linux/Unix)...")
with open(web_server_log, 'w', encoding='utf-8') as log_file:
web_server_process = subprocess.Popen(
[exe, web_server_script],
stdout=log_file,
stderr=subprocess.STDOUT,
cwd=config.APP_FOLDER
)
logger.info(f"✅ Serveur web démarré (PID: {web_server_process.pid})")
logger.info(f"🌐 Serveur accessible sur http://localhost:5000")
logger.info(f"📝 Logs de démarrage: {web_server_log}")
# Attendre un peu pour voir si le processus crash immédiatement
import time
time.sleep(0.5)
if web_server_process.poll() is not None:
logger.error(f"❌ Le serveur web s'est arrêté immédiatement (code: {web_server_process.returncode})")
logger.error(f"📝 Vérifiez les logs: {web_server_log}")
return False
return True
except Exception as e:
logger.error(f"❌ Erreur lors du démarrage du serveur web: {e}")
logger.exception("Détails de l'exception:")
return False
def stop_web_server():
"""Arrête proprement le serveur web."""
global web_server_process
if web_server_process is not None:
try:
logger.info("Arrêt du serveur web...")
web_server_process.terminate()
# Attendre jusqu'à 5 secondes que le processus se termine
try:
web_server_process.wait(timeout=5)
logger.info("Serveur web arrêté proprement")
except subprocess.TimeoutExpired:
logger.warning("Serveur web ne répond pas, forçage de l'arrêt...")
web_server_process.kill()
web_server_process.wait()
logger.info("Serveur web forcé à l'arrêt")
web_server_process = None
except Exception as e:
logger.error(f"Erreur lors de l'arrêt du serveur web: {e}")
# Boucle principale
async def main():
global current_music, music_files, music_folder
global current_music, music_files, music_folder, joystick
logger.debug("Début main")
# Démarrer le serveur web en arrière-plan
start_web_server()
running = True
loading_step = "none"
sources = []
@@ -335,7 +426,7 @@ async def main():
while running:
clock.tick(30) # Limite à 60 FPS
clock.tick(60) # Limite à 60 FPS pour une meilleure réactivité
if config.update_triggered:
logger.debug("Mise à jour déclenchée, arrêt de la boucle principale")
break
@@ -387,6 +478,73 @@ async def main():
# Gestion de la répétition automatique des actions
process_key_repeats(sources, joystick, screen)
# Gestion de l'appui long sur confirm dans le menu game pour ouvrir le scraper
if (config.menu_state == "game" and
config.confirm_press_start_time > 0 and
not config.confirm_long_press_triggered):
press_duration = current_time - config.confirm_press_start_time
if press_duration >= config.confirm_long_press_threshold:
# Appui long détecté, ouvrir le scraper
games = config.filtered_games if config.filter_active or config.search_mode else config.games
if games:
game_name = games[config.current_game][0]
platform = config.platforms[config.current_platform]["name"] if isinstance(config.platforms[config.current_platform], dict) else config.platforms[config.current_platform]
config.previous_menu_state = "game"
config.menu_state = "scraper"
config.scraper_game_name = game_name
config.scraper_platform_name = platform
config.scraper_loading = True
config.scraper_error_message = ""
config.scraper_image_surface = None
config.scraper_image_url = ""
config.scraper_description = ""
config.scraper_genre = ""
config.scraper_release_date = ""
config.scraper_game_page_url = ""
config.needs_redraw = True
config.confirm_long_press_triggered = True # Éviter de déclencher plusieurs fois
logger.debug(f"Appui long détecté ({press_duration}ms), ouverture du scraper pour {game_name}")
# Lancer la recherche des métadonnées dans un thread séparé
def scrape_async():
from scraper import get_game_metadata, download_image_to_surface
logger.info(f"Scraping métadonnées pour {game_name} sur {platform}")
metadata = get_game_metadata(game_name, platform)
# Vérifier si on a une erreur
if "error" in metadata:
config.scraper_error_message = metadata["error"]
config.scraper_loading = False
config.needs_redraw = True
logger.error(f"Erreur de scraping: {metadata['error']}")
return
# Mettre à jour les métadonnées textuelles
config.scraper_description = metadata.get("description", "")
config.scraper_genre = metadata.get("genre", "")
config.scraper_release_date = metadata.get("release_date", "")
config.scraper_game_page_url = metadata.get("game_page_url", "")
# Télécharger l'image si disponible
image_url = metadata.get("image_url")
if image_url:
logger.info(f"Téléchargement de l'image: {image_url}")
image_surface = download_image_to_surface(image_url)
if image_surface:
config.scraper_image_surface = image_surface
config.scraper_image_url = image_url
else:
logger.warning("Échec du téléchargement de l'image")
config.scraper_loading = False
config.needs_redraw = True
logger.info("Scraping terminé")
import threading
thread = threading.Thread(target=scrape_async, daemon=True)
thread.start()
# Gestion des événements
events = pygame.event.get()
for event in events:
@@ -402,6 +560,67 @@ async def main():
logger.debug("Événement QUIT détecté, passage à confirm_exit")
continue
# Gestion de la reconnexion/déconnexion de manettes (Bluetooth)
if event.type == pygame.JOYDEVICEADDED:
try:
device_index = event.device_index
new_joystick = pygame.joystick.Joystick(device_index)
new_joystick.init()
# Si c'est la première manette, on l'utilise
if joystick is None:
joystick = new_joystick
logger.info(f"Manette connectée et activée: {new_joystick.get_name()} (index {device_index})")
# Basculer sur les contrôles joystick
config.joystick = True
config.keyboard = False
config.controller_device_name = new_joystick.get_name()
# Recharger la configuration des contrôles pour le joystick
config.controls_config = load_controls_config()
logger.info(f"Contrôles joystick chargés pour {new_joystick.get_name()}")
else:
logger.info(f"Manette connectée: {new_joystick.get_name()} (index {device_index})")
config.needs_redraw = True
except Exception as e:
logger.error(f"Erreur lors de la connexion de la manette: {e}")
continue
if event.type == pygame.JOYDEVICEREMOVED:
try:
# Pour JOYDEVICEREMOVED, utiliser instance_id pas device_index
instance_id = event.instance_id
logger.info(f"Manette déconnectée (instance_id {instance_id})")
# Si c'était notre manette active, essayer de trouver une autre
if joystick is not None and joystick.get_instance_id() == instance_id:
joystick = None
logger.info("Aucune manette active, basculement automatique sur clavier")
# Chercher une autre manette disponible
if pygame.joystick.get_count() > 0:
try:
joystick = pygame.joystick.Joystick(0)
joystick.init()
logger.info(f"Basculement vers la manette: {joystick.get_name()}")
except Exception as e:
logger.warning(f"Impossible de basculer vers une autre manette: {e}")
logger.info("Utilisation du clavier")
# Basculer sur les contrôles clavier
config.joystick = False
config.keyboard = True
# Recharger la configuration des contrôles pour le clavier
config.controls_config = load_controls_config()
logger.info("Contrôles clavier chargés")
else:
logger.info("Utilisation du clavier")
# Basculer sur les contrôles clavier
config.joystick = False
config.keyboard = True
# Recharger la configuration des contrôles pour le clavier
config.controls_config = load_controls_config()
logger.info("Contrôles clavier chargés")
config.needs_redraw = True
except Exception as e:
logger.error(f"Erreur lors de la déconnexion de la manette: {e}")
continue
start_config = config.controls_config.get("start", {})
if start_config and (
(event.type == pygame.KEYDOWN and start_config.get("type") == "key" and event.key == start_config.get("key")) or
@@ -438,6 +657,14 @@ async def main():
"controls_help",
"confirm_cancel_download",
"reload_games_data",
# Menus historique
"history_game_options",
"history_show_folder",
"history_scraper_info",
"scraper", # Ajout du scraper pour gérer les contrôles
"history_error_details",
"history_confirm_delete",
"history_extract_archive",
}
if config.menu_state in SIMPLE_HANDLE_STATES:
action = handle_controls(event, sources, joystick, screen)
@@ -473,17 +700,19 @@ async def main():
continue
if config.menu_state == "extension_warning":
logger.debug(f"[EXTENSION_WARNING] Processing extension_warning, previous_menu_state={config.previous_menu_state}, pending_download={bool(config.pending_download)}")
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
if action == "confirm":
logger.debug(f"[EXTENSION_WARNING] Confirm pressed, selection={config.extension_confirm_selection}")
if config.pending_download and config.extension_confirm_selection == 0: # Oui
url, platform_name, game_name, is_zip_non_supported = config.pending_download
logger.debug(f"Téléchargement confirmé après avertissement: {game_name} pour {platform_name} depuis {url}")
logger.debug(f"[EXTENSION_WARNING] Téléchargement confirmé après avertissement: {game_name} pour {platform_name}")
task_id = str(pygame.time.get_ticks())
config.history.append({
"platform": platform_name,
"game_name": game_name,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"url": url,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -494,15 +723,24 @@ async def main():
asyncio.create_task(download_rom(url, platform_name, game_name, is_zip_non_supported, task_id)),
url, game_name, platform_name
)
config.menu_state = "history"
old_state = config.menu_state
config.menu_state = config.previous_menu_state if config.previous_menu_state else "game"
logger.debug(f"[EXTENSION_WARNING] Menu state changed: {old_state} -> {config.menu_state}")
config.pending_download = None
config.extension_confirm_selection = 0 # Réinitialiser la sélection
config.needs_redraw = True
logger.debug(f"Téléchargement démarré pour {game_name}, task_id={task_id}")
# Afficher toast de téléchargement en cours
config.toast_message = f"Downloading: {game_name}..."
config.toast_start_time = pygame.time.get_ticks()
config.toast_duration = 3000 # 3 secondes
logger.debug(f"[EXTENSION_WARNING] Download started for {game_name}, task_id={task_id}, menu_state={config.menu_state}, needs_redraw={config.needs_redraw}")
elif config.extension_confirm_selection == 1: # Non
logger.debug(f"[EXTENSION_WARNING] Download rejected by user")
config.menu_state = config.previous_menu_state
config.pending_download = None
config.extension_confirm_selection = 0 # Réinitialiser la sélection
config.needs_redraw = True
logger.debug("Téléchargement annulé, retour à l'état précédent")
logger.debug(f"[EXTENSION_WARNING] Returning to {config.menu_state}")
continue
if config.menu_state in ["platform", "game", "error", "confirm_exit", "history"]:
@@ -523,17 +761,6 @@ async def main():
platform_name = config.platforms[config.current_platform]
if url:
logger.debug(f"Vérification pour {game_name}, URL: {url}")
# Ajouter une entrée temporaire à l'historique
config.history.append({
"platform": platform_name,
"game_name": game_name,
"status": "downloading",
"progress": 0,
"message": _("download_initializing"),
"url": url,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
config.current_history_item = len(config.history) - 1 # Sélectionner l'entrée en cours
if is_1fichier_url(url):
# Utilisation helpers centralisés (utils)
try:
@@ -542,29 +769,36 @@ async def main():
except Exception as e:
logger.error(f"Impossible de charger les clés via helpers: {e}")
keys_info = {'1fichier': getattr(config,'API_KEY_1FICHIER',''), 'alldebrid': getattr(config,'API_KEY_ALLDEBRID',''), 'realdebrid': getattr(config,'API_KEY_REALDEBRID','')}
# SUPPRIMÉ: Vérification clés API obligatoires
# Maintenant on a le mode gratuit en fallback automatique
# if missing_all_provider_keys():
# config.previous_menu_state = config.menu_state
# config.menu_state = "error"
# try:
# config.error_message = _("error_api_key").format(build_provider_paths_string())
# except Exception:
# config.error_message = "Please enter API key (1fichier or AllDebrid or RealDebrid)"
# # Mise à jour historique
# config.history[-1]["status"] = "Erreur"
# config.history[-1]["progress"] = 0
# config.history[-1]["message"] = "API NOT FOUND"
# save_history(config.history)
# config.needs_redraw = True
# logger.error("Aucune clé fournisseur (1fichier/AllDebrid/RealDebrid) disponible")
# config.pending_download = None
# continue
# Avertissement si pas de clé (utilisation mode gratuit)
if missing_all_provider_keys():
config.previous_menu_state = config.menu_state
config.menu_state = "error"
try:
config.error_message = _("error_api_key").format(build_provider_paths_string())
except Exception:
config.error_message = "Please enter API key (1fichier or AllDebrid or RealDebrid)"
# Mise à jour historique
config.history[-1]["status"] = "Erreur"
config.history[-1]["progress"] = 0
config.history[-1]["message"] = "API NOT FOUND"
save_history(config.history)
config.needs_redraw = True
logger.error("Aucune clé fournisseur (1fichier/AllDebrid/RealDebrid) disponible")
config.pending_download = None
continue
logger.warning("Aucune clé API - Mode gratuit 1fichier sera utilisé (attente requise)")
pending = check_extension_before_download(url, platform_name, game_name)
if not pending:
config.menu_state = "error"
config.error_message = _("error_invalid_download_data") if _ else "Invalid download data"
config.needs_redraw = True
logger.error(f"check_extension_before_download a échoué pour {game_name}")
config.history.pop()
else:
from utils import is_extension_supported, load_extensions_json, sanitize_filename
from rgsx_settings import get_allow_unknown_extensions
@@ -581,19 +815,29 @@ async def main():
config.extension_confirm_selection = 0
config.needs_redraw = True
logger.debug(f"Extension non reconnue pour lien 1fichier, passage à extension_warning pour {game_name}")
config.history.pop()
else:
config.previous_menu_state = config.menu_state
logger.debug(f"Previous menu state défini: {config.previous_menu_state}")
# Ajouter une entrée à l'historique maintenant que le téléchargement démarre vraiment
config.history.append({
"platform": platform_name,
"game_name": game_name,
"status": "Downloading",
"progress": 0,
"message": _("download_in_progress") if _ else "Download in progress",
"url": url,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
config.current_history_item = len(config.history) - 1
save_history(config.history)
# Lancer le téléchargement dans une tâche asynchrone
task_id = str(pygame.time.get_ticks())
config.download_tasks[task_id] = (
asyncio.create_task(download_from_1fichier(url, platform_name, game_name, zip_ok)),
asyncio.create_task(download_from_1fichier(url, platform_name, game_name, zip_ok, task_id)),
url, game_name, platform_name
)
config.menu_state = "history" # Passer à l'historique
config.needs_redraw = True
logger.debug(f"Téléchargement 1fichier démarré pour {game_name}, passage à l'historique")
logger.debug(f"Téléchargement 1fichier démarré pour {game_name}, tâche lancée")
else:
pending = check_extension_before_download(url, platform_name, game_name)
if not pending:
@@ -601,7 +845,6 @@ async def main():
config.error_message = _("error_invalid_download_data") if _ else "Invalid download data"
config.needs_redraw = True
logger.error(f"check_extension_before_download a échoué pour {game_name}")
config.history.pop()
else:
from utils import is_extension_supported, load_extensions_json, sanitize_filename
from rgsx_settings import get_allow_unknown_extensions
@@ -618,19 +861,29 @@ async def main():
config.extension_confirm_selection = 0
config.needs_redraw = True
logger.debug(f"Extension non reconnue, passage à extension_warning pour {game_name}")
config.history.pop()
else:
config.previous_menu_state = config.menu_state
logger.debug(f"Previous menu state défini: {config.previous_menu_state}")
# Ajouter une entrée à l'historique maintenant que le téléchargement démarre vraiment
config.history.append({
"platform": platform_name,
"game_name": game_name,
"status": "Downloading",
"progress": 0,
"message": _("download_in_progress") if _ else "Download in progress",
"url": url,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
config.current_history_item = len(config.history) - 1
save_history(config.history)
# Lancer le téléchargement dans une tâche asynchrone
task_id = str(pygame.time.get_ticks())
config.download_tasks[task_id] = (
asyncio.create_task(download_rom(url, platform_name, game_name, zip_ok)),
asyncio.create_task(download_rom(url, platform_name, game_name, zip_ok, task_id)),
url, game_name, platform_name
)
config.menu_state = "history" # Passer à l'historique
config.needs_redraw = True
logger.debug(f"Téléchargement démarré pour {game_name}, passage à l'historique")
logger.debug(f"Téléchargement démarré pour {game_name}, tâche lancée")
elif action in ("clear_history", "delete_history") and config.menu_state == "history":
# Ouvrir le dialogue de confirmation
@@ -645,17 +898,29 @@ async def main():
# Gestion des téléchargements
if config.download_tasks:
for task_id, (task, url, game_name, platform_name) in list(config.download_tasks.items()):
#logger.debug(f"[DOWNLOAD_CHECK] Checking task {task_id}: done={task.done()}, game={game_name}")
if task.done():
logger.debug(f"[DOWNLOAD_COMPLETE] Task {task_id} is done, processing result for {game_name}")
try:
success, message = await task
logger.debug(f"[DOWNLOAD_RESULT] Task {task_id} returned: success={success}, message={message[:100]}")
if "http" in message:
message = message.split("https://")[0].strip()
logger.debug(f"[HISTORY_SEARCH] Searching in {len(config.history)} history entries for url={url[:50]}...")
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
#logger.debug(f"[HISTORY_ENTRY] Checking: url_match={entry['url'] == url}, status={entry['status']}, game={entry.get('game_name')}")
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
#logger.debug(f"[HISTORY_MATCH] Found matching entry for {game_name}, updating status")
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
save_history(config.history)
# Marquer le jeu comme téléchargé si succès
if success:
logger.debug(f"[MARKING_DOWNLOAD] Marking game as downloaded: platform={platform_name}, game={game_name}")
from history import mark_game_as_downloaded
file_size = entry.get("size", "N/A")
mark_game_as_downloaded(platform_name, game_name, file_size)
config.needs_redraw = True
logger.debug(f"Téléchargement terminé: {game_name}, succès={success}, message={message}, task_id={task_id}")
break
@@ -663,7 +928,12 @@ async def main():
config.download_result_error = not success
config.download_progress.clear()
config.pending_download = None
config.menu_state = "history"
# Afficher un toast au lieu de changer de page
if success:
toast_msg = f"[OK] {game_name}\n{_('download_completed') if _ else 'Download completed'}"
else:
toast_msg = f"[ERROR] {game_name}\n{_('download_failed') if _ else 'Download failed'}"
show_toast(toast_msg, 3000)
config.needs_redraw = True
del config.download_tasks[task_id]
except Exception as e:
@@ -671,7 +941,7 @@ async def main():
if "http" in message:
message = message.split("https://")[0].strip()
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["status"] = "Erreur"
entry["progress"] = 0
entry["message"] = message
@@ -683,7 +953,9 @@ async def main():
config.download_result_error = True
config.download_progress.clear()
config.pending_download = None
config.menu_state = "history"
# Afficher un toast au lieu de changer de page
toast_msg = f"[ERROR] {game_name}\n{_('download_failed') if _ else 'Download failed'}"
show_toast(toast_msg, 3000)
config.needs_redraw = True
del config.download_tasks[task_id]
else:
@@ -698,37 +970,49 @@ async def main():
continue
if isinstance(data[1], bool): # Fin du téléchargement
success, message = data[1], data[2]
logger.debug(f"[DOWNLOAD_TASK] Download task done - success={success}, message={message}, task_id={task_id}")
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
save_history(config.history)
# Marquer le jeu comme téléchargé si succès
if success:
from history import mark_game_as_downloaded
file_size = entry.get("size", "N/A")
mark_game_as_downloaded(platform_name, game_name, file_size)
config.needs_redraw = True
logger.debug(f"Final update in history: status={entry['status']}, progress={entry['progress']}%, message={message}, task_id={task_id}")
break
config.download_result_message = message
config.download_result_error = not success
config.download_progress.clear()
config.pending_download = None
# Afficher un toast au lieu de changer de page
if success:
toast_msg = f"[OK] {game_name}\n{_('download_completed') if _ else 'Download completed'}"
else:
toast_msg = f"[ERROR] {game_name}\n{_('download_failed') if _ else 'Download failed'}"
show_toast(toast_msg, 3000)
config.needs_redraw = True
logger.debug(f"[DOWNLOAD_TASK] Toast displayed after completion, task_id={task_id}")
del config.download_tasks[task_id]
else:
downloaded, total_size = data[1], data[2]
progress = (downloaded / total_size * 100) if total_size > 0 else 0
for entry in config.history:
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["progress"] = progress
entry["status"] = "Téléchargement"
config.needs_redraw = True
# logger.debug(f"Progress updated in history: {progress:.1f}% for {game_name}, task_id={task_id}")
logger.debug(f"Progress updated in history: {progress:.1f}% for {game_name}, task_id={task_id}")
break
config.download_result_message = message
config.download_result_error = True
config.download_result_start_time = pygame.time.get_ticks()
config.menu_state = "download_result"
config.download_progress.clear()
config.pending_download = None
config.needs_redraw = True
del config.download_tasks[task_id]
# Affichage
if config.needs_redraw:
#logger.debug(f"[RENDER_LOOP] Frame render - menu_state={config.menu_state}, needs_redraw={config.needs_redraw}")
draw_gradient(screen, THEME_COLORS["background_top"], THEME_COLORS["background_bottom"])
@@ -744,6 +1028,7 @@ async def main():
elif config.menu_state == "platform":
draw_platform_grid(screen)
elif config.menu_state == "game":
#logger.debug(f"[RENDER_GAME] Rendering game state - search_mode={config.search_mode}, filtered_games={len(config.filtered_games) if config.filtered_games else 0}, current_game={config.current_game}")
if not config.search_mode:
draw_game_list(screen)
if config.search_mode:
@@ -756,6 +1041,7 @@ async def main():
elif config.menu_state == "confirm_exit":
draw_confirm_dialog(screen)
elif config.menu_state == "extension_warning":
logger.debug(f"[RENDER_EXT_WARNING] Drawing extension warning dialog")
draw_extension_warning(screen)
elif config.menu_state == "pause_menu":
draw_pause_menu(screen, config.selected_option)
@@ -782,8 +1068,32 @@ async def main():
elif config.menu_state == "history":
draw_history_list(screen)
# logger.debug("Screen updated with draw_history_list")
elif config.menu_state == "history_game_options":
from display import draw_history_game_options
draw_history_game_options(screen)
elif config.menu_state == "history_show_folder":
from display import draw_history_show_folder
draw_history_show_folder(screen)
elif config.menu_state == "scraper":
from display import draw_scraper_screen
draw_scraper_screen(screen)
elif config.menu_state == "history_scraper_info":
from display import draw_history_scraper_info
draw_history_scraper_info(screen)
elif config.menu_state == "history_error_details":
from display import draw_history_error_details
draw_history_error_details(screen)
elif config.menu_state == "history_confirm_delete":
from display import draw_history_confirm_delete
draw_history_confirm_delete(screen)
elif config.menu_state == "history_extract_archive":
from display import draw_history_extract_archive
draw_history_extract_archive(screen)
elif config.menu_state == "confirm_clear_history":
draw_clear_history_dialog(screen)
elif config.menu_state == "support_dialog":
from display import draw_support_dialog
draw_support_dialog(screen)
elif config.menu_state == "confirm_cancel_download":
draw_cancel_download_dialog(screen)
elif config.menu_state == "reload_games_data":
@@ -809,6 +1119,9 @@ async def main():
if config.popup_timer > 0 and config.popup_message and config.menu_state not in ["update_result", "restart_popup"]:
draw_popup(screen)
# Toast notification (dans le coin inférieur droit)
draw_toast(screen)
pygame.display.flip()
config.needs_redraw = False
@@ -931,7 +1244,7 @@ async def main():
config.needs_redraw = True
dest_dir = config.SAVE_FOLDER
try:
success, message = extract_zip_data(local_zip, dest_dir, local_zip)
success, message = extract_data(local_zip, dest_dir, local_zip)
if success:
logger.debug(f"Extraction locale réussie : {message}")
config.loading_progress = 70.0
@@ -979,7 +1292,7 @@ async def main():
config.loading_progress = 60.0
config.needs_redraw = True
dest_dir = config.SAVE_FOLDER
success, message = extract_zip_data(zip_path, dest_dir, sources_zip_url)
success, message = extract_data(zip_path, dest_dir, sources_zip_url)
if success:
logger.debug(f"Extraction réussie : {message}")
config.loading_progress = 70.0
@@ -1056,16 +1369,35 @@ async def main():
except Exception as e:
logger.debug(f"Erreur lors de l'annulation globale des téléchargements: {e}")
if platform.system() == "Windows":
# Arrêter le serveur web
stop_web_server()
if config.OPERATING_SYSTEM == "Windows":
logger.debug(f"Mise à jour liste des jeux ignorée sur {config.OPERATING_SYSTEM}")
try:
result = subprocess.run(["taskkill", "/f", "/im", "emulatorLauncher.exe"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
result2 = subprocess.run(["taskkill", "/f", "/im", "python.exe"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if getattr(result, "returncode", 1) == 0:
logger.debug("Quitté avec succès: emulatorLauncher.exe")
print(f"Arret Emulatorlauncher ok")
else:
logger.debug("Erreur lors de la tentative d'arrêt d'emulatorLauncher.exe")
print(f"Arret Emulatorlauncher ko")
if getattr(result2, "returncode", 1) == 0:
logger.debug("Quitté avec succès: Python.Exe")
print(f"Arret Python ok")
else:
logger.debug("Erreur lors de la tentative d'arrêt de Python.exe ")
print(f"Arret Python ko")
except FileNotFoundError:
logger.debug("taskkill introuvable, saut de l'étape d'arrêt d'emulatorLauncher.exe")
else:
# Exécuter la mise à jour de la liste des jeux d'EmulationStation UNIQUEMENT sur Batocera
resp = requests.get("http://127.0.0.1:1234/reloadgames", timeout=2)
content = (resp.text or "").strip()
logger.debug(f"Résultat mise à jour liste des jeux: HTTP {resp.status_code} - {content}")
try:
result2 = subprocess.run(["batocera-es-swissknife", "--emukill"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if getattr(result2, "returncode", 1) == 0:
@@ -1077,7 +1409,7 @@ async def main():
pygame.quit()
logger.debug("Application terminée")
if platform.system() == "Emscripten":
if config.OPERATING_SYSTEM == "Emscripten":
asyncio.ensure_future(main())
else:
if __name__ == "__main__":

View File

@@ -0,0 +1,87 @@
{
"device": "Generic X-Box pad",
"up": {
"type": "hat",
"value": [
0,
1
],
"display": "\u2191"
},
"down": {
"type": "hat",
"value": [
0,
-1
],
"display": "\u2193"
},
"left": {
"type": "hat",
"value": [
-1,
0
],
"display": "\u2190"
},
"right": {
"type": "hat",
"value": [
1,
0
],
"display": "\u2192"
},
"confirm": {
"type": "button",
"button": 0,
"display": "A"
},
"cancel": {
"type": "button",
"button": 1,
"display": "B"
},
"history": {
"type": "button",
"button": 3,
"display": "Y"
},
"clear_history": {
"type": "button",
"button": 2,
"display": "X"
},
"start": {
"type": "button",
"button": 7,
"display": "Start"
},
"filter": {
"type": "button",
"button": 6,
"display": "Select"
},
"delete": {
"type": "button",
"button": 4,
"display": "LB"
},
"space": {
"type": "button",
"button": 5,
"display": "RB"
},
"page_up": {
"type": "axis",
"axis": 2,
"direction": 1,
"display": "LT"
},
"page_down": {
"type": "axis",
"axis": 5,
"direction": 1,
"display": "RT"
}
}

View File

@@ -0,0 +1,77 @@
{
"device": "DragonRise Inc. Generic USB Joystick ",
"up": {
"type": "axis",
"axis": 0,
"direction": -1,
"display": "\u2191"
},
"down": {
"type": "axis",
"axis": 0,
"direction": 1,
"display": "\u2193"
},
"left": {
"type": "axis",
"axis": 1,
"direction": 1,
"display": "\u2190"
},
"right": {
"type": "axis",
"axis": 1,
"direction": -1,
"display": "\u2192"
},
"confirm": {
"type": "button",
"button": 0,
"display": "A"
},
"cancel": {
"type": "button",
"button": 1,
"display": "B"
},
"history": {
"type": "button",
"button": 4,
"display": "Y"
},
"clear_history": {
"type": "button",
"button": 3,
"display": "X"
},
"start": {
"type": "button",
"button": 9,
"display": "Start"
},
"filter": {
"type": "button",
"button": 8,
"display": "Select"
},
"delete": {
"type": "button",
"button": 5,
"display": "LB"
},
"space": {
"type": "button",
"button": 2,
"display": "RB"
},
"page_up": {
"type": "button",
"button": 5,
"display": "LT"
},
"page_down": {
"type": "button",
"button": 2,
"display": "RT"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.

BIN
ports/RGSX/assets/progs/7zz Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,103 @@
#!/bin/bash
# BATOCERA SERVICE
# name: RGSX Web Service for Batocera
# description: Automatic launch Web interface service for RGSX
# author: RetroGameSets / ninao.xyz
# depends: python3
# version: 1.3
SCRIPT="/userdata/roms/ports/RGSX/rgsx_web.py"
PYTHON="/usr/bin/python3"
PIDFILE="/var/run/rgsx_web.pid"
LOGFILE="/userdata/roms/ports/RGSX/LOGS/rgsx_web_service.log"
SERVICE_NAME="rgsx_web"
# Fonction utilitaire : vérifie si le service est activé dans batocera-settings
is_enabled() {
local enabled_services
enabled_services="$(/usr/bin/batocera-settings-get system.services 2>/dev/null)"
for s in $enabled_services; do
if [ "$s" = "$SERVICE_NAME" ]; then
echo "enabled"
return
fi
done
echo "disabled"
}
case "$1" in
start)
if [ ! -f "$SCRIPT" ]; then
echo "[${SERVICE_NAME}] Error: script not found at $SCRIPT"
exit 1
fi
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
echo "[${SERVICE_NAME}] Already running (PID $(cat "$PIDFILE"))"
exit 0
fi
echo "[${SERVICE_NAME}] Starting..."
mkdir -p "$(dirname "$LOGFILE")"
$PYTHON "$SCRIPT" >> "$LOGFILE" 2>&1 &
echo $! > "$PIDFILE"
echo "[${SERVICE_NAME}] Started (PID $(cat "$PIDFILE"))"
;;
stop)
if [ -f "$PIDFILE" ]; then
echo "[${SERVICE_NAME}] Stopping..."
kill $(cat "$PIDFILE") 2>/dev/null && rm -f "$PIDFILE"
echo "[${SERVICE_NAME}] Stopped"
else
pkill -f "$PYTHON $SCRIPT" && echo "[${SERVICE_NAME}] Stopped (no PID file)"
fi
;;
restart)
echo "[${SERVICE_NAME}] Restarting..."
"$0" stop
sleep 1
"$0" start
;;
status)
ENABLE_STATE=$(is_enabled)
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
echo "[${SERVICE_NAME}] Running (PID $(cat "$PIDFILE")) - ${ENABLE_STATE} on boot"
exit 0
elif pgrep -f "$PYTHON $SCRIPT" > /dev/null; then
echo "[${SERVICE_NAME}] Running (detected without PID file) - ${ENABLE_STATE} on boot"
exit 0
else
echo "[${SERVICE_NAME}] Not running - ${ENABLE_STATE} on boot"
exit 1
fi
;;
enable)
current=$(/usr/bin/batocera-settings-get system.services 2>/dev/null)
if echo "$current" | grep -qw "$SERVICE_NAME"; then
echo "[${SERVICE_NAME}] Already enabled on boot"
else
new_value="$current $SERVICE_NAME"
/usr/bin/batocera-settings-set system.services "$new_value"
echo "[${SERVICE_NAME}] Enabled on boot"
fi
;;
disable)
current=$(/usr/bin/batocera-settings-get system.services 2>/dev/null)
if echo "$current" | grep -qw "$SERVICE_NAME"; then
new_value=$(echo "$current" | sed "s/\b$SERVICE_NAME\b//g" | xargs)
/usr/bin/batocera-settings-set system.services "$new_value"
echo "[${SERVICE_NAME}] Disabled on boot"
else
echo "[${SERVICE_NAME}] Already disabled"
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status|enable|disable}"
;;
esac
exit 0

View File

@@ -13,7 +13,7 @@ except Exception:
pygame = None # type: ignore
# Version actuelle de l'application
app_version = "2.2.2.7"
app_version = "2.3.1.0"
def get_application_root():
@@ -28,32 +28,54 @@ def get_application_root():
# Si __file__ n'est pas défini (par exemple, exécution dans un REPL)
return os.path.abspath(os.getcwd())
def detect_operating_system():
"""Renvoie le nom du système d'exploitation."""
OPERATING_SYSTEM = platform.system()
return OPERATING_SYSTEM
### CONSTANTES DES CHEMINS DE BASE
# Chemins de base
APP_FOLDER = os.path.join(get_application_root(), "RGSX")
USERDATA_FOLDER = os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER)))
ROMS_FOLDER = os.path.join(USERDATA_FOLDER, "roms")
USERDATA_FOLDER = os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER))) # remonte de /userdata/roms/ports/rgsx à /userdata ou \Retrobat
SAVE_FOLDER = os.path.join(USERDATA_FOLDER, "saves", "ports", "rgsx")
GAMELISTXML = os.path.join(ROMS_FOLDER, "ports","gamelist.xml")
GAMELISTXML_WINDOWS = os.path.join(ROMS_FOLDER, "windows","gamelist.xml")
# ROMS_FOLDER - Charger depuis rgsx_settings.json si défini, sinon valeur par défaut
_default_roms_folder = os.path.join(USERDATA_FOLDER, "roms")
try:
# Import tardif pour éviter les dépendances circulaires
_settings_path = os.path.join(SAVE_FOLDER, "rgsx_settings.json")
if os.path.exists(_settings_path):
import json
with open(_settings_path, 'r', encoding='utf-8') as _f:
_settings = json.load(_f)
_custom_roms = _settings.get("roms_folder", "").strip()
if _custom_roms and os.path.isdir(_custom_roms):
ROMS_FOLDER = _custom_roms
else:
ROMS_FOLDER = _default_roms_folder
else:
ROMS_FOLDER = _default_roms_folder
except Exception as _e:
ROMS_FOLDER = _default_roms_folder
logging.getLogger(__name__).debug(f"Impossible de charger roms_folder depuis settings: {_e}")
# Configuration du logging
logger = logging.getLogger(__name__)
# File d'attente de téléchargements (jobs en attente)
download_queue = [] # Liste de dicts: {url, platform, game_name, ...}
# Indique si un téléchargement est en cours
download_active = False
log_dir = os.path.join(APP_FOLDER, "logs")
log_file = os.path.join(log_dir, "RGSX.log")
log_file_web = os.path.join(log_dir, 'rgsx_web.log')
#Dossier de l'APP : /roms/ports/rgsx
# Dans le Dossier de l'APP : /roms/ports/rgsx
UPDATE_FOLDER = os.path.join(APP_FOLDER, "update")
LANGUAGES_FOLDER = os.path.join(APP_FOLDER, "languages")
MUSIC_FOLDER = os.path.join(APP_FOLDER, "assets", "music")
GAMELISTXML = os.path.join(ROMS_FOLDER, "ports","gamelist.xml")
GAMELISTXML_WINDOWS = os.path.join(ROMS_FOLDER, "windows","gamelist.xml")
#Dossier de sauvegarde : /saves/ports/rgsx
# Dans le Dossier de sauvegarde : /saves/ports/rgsx
IMAGES_FOLDER = os.path.join(SAVE_FOLDER, "images")
GAMES_FOLDER = os.path.join(SAVE_FOLDER, "games")
SOURCES_FILE = os.path.join(SAVE_FOLDER, "systems_list.json")
@@ -61,30 +83,122 @@ JSON_EXTENSIONS = os.path.join(SAVE_FOLDER, "rom_extensions.json")
PRECONF_CONTROLS_PATH = os.path.join(APP_FOLDER, "assets", "controls")
CONTROLS_CONFIG_PATH = os.path.join(SAVE_FOLDER, "controls.json")
HISTORY_PATH = os.path.join(SAVE_FOLDER, "history.json")
# Séparation chemin / valeur pour éviter les confusions lors du chargement
DOWNLOADED_GAMES_PATH = os.path.join(SAVE_FOLDER, "downloaded_games.json")
RGSX_SETTINGS_PATH = os.path.join(SAVE_FOLDER, "rgsx_settings.json")
API_KEY_1FICHIER_PATH = os.path.join(SAVE_FOLDER, "1FichierAPI.txt")
API_KEY_ALLDEBRID_PATH = os.path.join(SAVE_FOLDER, "AllDebridAPI.txt")
API_KEY_REALDEBRID_PATH = os.path.join(SAVE_FOLDER, "RealDebridAPI.txt")
# Valeurs chargées (remplies dynamiquement par utils.load_api_key_*).
API_KEY_1FICHIER = ""
API_KEY_ALLDEBRID = ""
API_KEY_REALDEBRID = ""
RGSX_SETTINGS_PATH = os.path.join(SAVE_FOLDER, "rgsx_settings.json")
# URL
# URL - GitHub Releases
GITHUB_REPO = "RetroGameSets/RGSX"
GITHUB_RELEASES_URL = f"https://github.com/{GITHUB_REPO}/releases"
# URLs pour les mises à jour OTA (Over-The-Air)
# Utilise le fichier RGSX_latest.zip qui pointe toujours vers la dernière version
OTA_UPDATE_ZIP = f"{GITHUB_RELEASES_URL}/latest/download/RGSX_update_latest.zip"
OTA_VERSION_ENDPOINT = "https://retrogamesets.fr/softs/version.json" # Endpoint pour vérifier la version disponible
# URLs legacy (conservées pour compatibilité)
OTA_SERVER_URL = "https://retrogamesets.fr/softs/"
OTA_VERSION_ENDPOINT = os.path.join(OTA_SERVER_URL, "version.json")
OTA_UPDATE_ZIP = os.path.join(OTA_SERVER_URL, "RGSX.zip")
OTA_data_ZIP = os.path.join(OTA_SERVER_URL, "games.zip")
#CHEMINS DES EXECUTABLES
UNRAR_EXE = os.path.join(APP_FOLDER,"assets","progs","unrar.exe")
XDVDFS_EXE = os.path.join(APP_FOLDER,"assets", "progs", "xdvdfs.exe")
XDVDFS_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "xdvdfs")
PS3DEC_EXE = os.path.join(APP_FOLDER,"assets", "progs", "ps3dec_win.exe")
PS3DEC_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "ps3dec_linux")
SEVEN_Z_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "7zz")
SEVEN_Z_EXE = os.path.join(APP_FOLDER,"assets", "progs", "7z.exe")
# Détection du système d'exploitation (une seule fois au démarrage)
OPERATING_SYSTEM = platform.system()
# Informations système (Batocera)
SYSTEM_INFO = {
"model": "",
"system": "",
"architecture": "",
"cpu_model": "",
"cpu_cores": "",
"cpu_max_frequency": "",
"cpu_features": "",
"temperature": "",
"available_memory": "",
"total_memory": "",
"display_resolution": "",
"display_refresh_rate": "",
"data_partition_format": "",
"data_partition_space": "",
"network_ip": ""
}
def get_batocera_system_info():
"""Récupère les informations système via la commande batocera-info."""
global SYSTEM_INFO
try:
import subprocess
result = subprocess.run(['batocera-info'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
lines = result.stdout.strip().split('\n')
for line in lines:
if ':' in line:
key, value = line.split(':', 1)
key = key.strip()
value = value.strip()
if key == "Model":
SYSTEM_INFO["model"] = value
elif key == "System":
SYSTEM_INFO["system"] = value
elif key == "Architecture":
SYSTEM_INFO["architecture"] = value
elif key == "CPU Model":
SYSTEM_INFO["cpu_model"] = value
elif key == "CPU Cores":
SYSTEM_INFO["cpu_cores"] = value
elif key == "CPU Max Frequency":
SYSTEM_INFO["cpu_max_frequency"] = value
elif key == "CPU Features":
SYSTEM_INFO["cpu_features"] = value
elif key == "Temperature":
SYSTEM_INFO["temperature"] = value
elif key == "Available Memory":
SYSTEM_INFO["available_memory"] = value.split('/')[0].strip() if '/' in value else value
SYSTEM_INFO["total_memory"] = value.split('/')[1].strip() if '/' in value else ""
elif key == "Display Resolution":
SYSTEM_INFO["display_resolution"] = value
elif key == "Display Refresh Rate":
SYSTEM_INFO["display_refresh_rate"] = value
elif key == "Data Partition Format":
SYSTEM_INFO["data_partition_format"] = value
elif key == "Data Partition Available Space":
SYSTEM_INFO["data_partition_space"] = value
elif key == "Network IP Address":
SYSTEM_INFO["network_ip"] = value
logger.debug(f"Informations système Batocera récupérées: {SYSTEM_INFO}")
print(f"SYSTEM_INFO: {SYSTEM_INFO}")
return True
except FileNotFoundError:
logger.debug("Commande batocera-info non disponible (système non-Batocera)")
except subprocess.TimeoutExpired:
logger.warning("Timeout lors de l'exécution de batocera-info")
except Exception as e:
logger.debug(f"Erreur lors de la récupération des infos système: {e}")
# Fallback: informations basiques avec platform
SYSTEM_INFO["system"] = f"{platform.system()} {platform.release()}"
SYSTEM_INFO["architecture"] = platform.machine()
SYSTEM_INFO["cpu_model"] = platform.processor() or "Unknown"
return False
if not HEADLESS:
# Print des chemins pour debug
print(f"OPERATING_SYSTEM: {detect_operating_system()}")
print(f"OPERATING_SYSTEM: {OPERATING_SYSTEM}")
print(f"APP_FOLDER: {APP_FOLDER}")
print(f"USERDATA_FOLDER: {USERDATA_FOLDER}")
print(f"ROMS_FOLDER: {ROMS_FOLDER}")
@@ -96,140 +210,149 @@ if not HEADLESS:
print(f"GAMES_FOLDER: {GAMES_FOLDER}")
print(f"SOURCES_FILE: {SOURCES_FILE}")
# Récupérer les informations système au démarrage
get_batocera_system_info()
# Constantes pour la répétition automatique dans pause_menu
REPEAT_DELAY = 350 # Délai initial avant répétition (ms) - augmenté pour éviter les doubles actions
REPEAT_INTERVAL = 120 # Intervalle entre répétitions (ms) - ajusté pour une navigation plus contrôlée
REPEAT_ACTION_DEBOUNCE = 150 # Délai anti-rebond pour répétitions (ms) - augmenté pour éviter les doubles actions
# Variables d'état
platforms = []
current_platform = 0
accessibility_mode = False # Mode accessibilité pour les polices agrandies
accessibility_settings = {"font_scale": 1.0}
font_scale_options = [0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
current_font_scale_index = 3 # Index pour 1.0
platform_names = {} # {platform_id: platform_name}
games = []
current_game = 0
menu_state = "popup"
confirm_choice = False
scroll_offset = 0
visible_games = 15
popup_start_time = 0
last_progress_update = 0
needs_redraw = True
transition_state = "idle"
transition_progress = 0.0
transition_duration = 18
games_count = {}
music_enabled = True # Par défaut la musique est activée
sources_mode = "rgsx" # Mode des sources de jeux (rgsx/custom)
custom_sources_url = "" # URL personnalisée si mode custom
# Variables pour la sélection de langue
selected_language_index = 0
loading_progress = 0.0
current_loading_system = ""
error_message = ""
repeat_action = None
repeat_start_time = 0
repeat_last_action = 0
repeat_key = None
filtered_games = []
search_mode = False
search_query = ""
filter_active = False
extension_confirm_selection = 0
pending_download = None
controls_config = {}
selected_option = 0
previous_menu_state = None
history = [] # Liste des entrées d'historique avec platform, game_name, status, url, progress, message, timestamp
download_progress = {}
download_tasks = {} # Dictionnaire pour les tâches de téléchargement
download_result_message = ""
download_result_error = False
download_result_start_time = 0
needs_redraw = False
current_history_item = 0
history_scroll_offset = 0 # Offset pour le défilement de l'historique
visible_history_items = 15 # Nombre d'éléments d'historique visibles (ajusté dynamiquement)
confirm_clear_selection = 0 # confirmation clear historique
confirm_cancel_selection = 0 # confirmation annulation téléchargement
last_state_change_time = 0 # Temps du dernier changement d'état pour debounce
debounce_delay = 200 # Délai de debounce en millisecondes
platform_dicts = [] # Liste des dictionnaires de plateformes
selected_key = (0, 0) # Position du curseur dans le clavier virtuel
popup_message = "" # Message à afficher dans les popups
popup_timer = 0 # Temps restant pour le popup en millisecondes (0 = inactif)
last_frame_time = pygame.time.get_ticks() if pygame is not None else 0
current_music_name = None
music_popup_start_time = 0
selected_games = set() # Indices des jeux sélectionnés pour un téléchargement multiple (menu game)
batch_download_indices = [] # File d'attente des indices de jeux à traiter en lot
batch_in_progress = False # Indique qu'un lot est en cours
batch_pending_game = None # Données du jeu en attente de confirmation d'extension
PREMIUM_HOST_MARKERS = [
"1Fichier",
]
hide_premium_systems = False
# Indicateurs d'entrée (détectés au démarrage)
joystick = False
keyboard = False
xbox_controller = False
playstation_controller = False
nintendo_controller = False
logitech_controller = False
eightbitdo_controller = False
steam_controller = False
trimui_controller = False
generic_controller = False
xbox_elite_controller = False # Flag spécifique manette Xbox Elite
anbernic_rg35xx_controller = False # Flag spécifique Anbernic RG3xxx
controller_device_name = "" # Nom exact du joystick détecté (pour auto-préréglages)
# --- Filtre plateformes (UI) ---
selected_filter_index = 0 # index dans la liste visible triée
filter_platforms_scroll_offset = 0 # défilement si liste longue
filter_platforms_dirty = False # indique si modifications non sauvegardées
filter_platforms_selection = [] # copie de travail des plateformes visibles (bool masque?) structure: list of (name, hidden_bool)
GRID_COLS = 3 # Number of columns in the platform grid
GRID_ROWS = 4 # Number of rows in the platform grid
### Variables d'état par défaut
# Résolution de l'écran fallback
# Utilisée si la résolution définie dépasse les capacités de l'écran
SCREEN_WIDTH = 800
"""Largeur de l'écran en pixels."""
SCREEN_HEIGHT = 600
"""Hauteur de l'écran en pixels."""
SCREEN_WIDTH = 800 # Largeur de l'écran en pixels.
SCREEN_HEIGHT = 600 # Hauteur de l'écran en pixels.
# Polices
FONT = None
"""Police par défaut pour l'affichage, initialisée via init_font()."""
progress_font = None
"""Police pour l'affichage de la progression."""
title_font = None
"""Police pour les titres."""
search_font = None
"""Police pour la recherche."""
small_font = None
"""Police pour les petits textes."""
# Liste des familles de polices disponibles (identifiants logiques)
FONT = None # Police par défaut pour l'affichage, initialisée via init_font().
progress_font = None # Police pour l'affichage de la progression
title_font = None # Police pour les titres
search_font = None # Police pour la recherche
small_font = None # Police pour les petits textes
FONT_FAMILIES = [
"pixel", # police rétro Pixel-UniCode.ttf
"dejavu" # police plus standard lisible petites tailles
]
current_font_family_index = 0 # 0=pixel par défaut
# Après définition de FONT_FAMILIES et current_font_family_index, tenter de charger la famille depuis les settings
# Constantes pour la répétition automatique et le debounce
REPEAT_DELAY = 350 # Délai initial avant répétition (ms) - augmenté pour éviter les doubles actions
REPEAT_INTERVAL = 120 # Intervalle entre répétitions (ms) - ajusté pour une navigation plus contrôlée
REPEAT_ACTION_DEBOUNCE = 150 # Délai anti-rebond pour répétitions (ms) - augmenté pour éviter les doubles actions
repeat_action = None # Action en cours de répétition automatique
repeat_start_time = 0 # Timestamp de début de la répétition
repeat_last_action = 0 # Timestamp de la dernière action répétée
repeat_key = None # Touche ou bouton en cours de répétition
last_state_change_time = 0 # Temps du dernier changement d'état pour debounce
debounce_delay = 200 # Délai de debounce en millisecondes
# gestion des entrées et détection des joystick/clavier/controller
joystick = False
keyboard = False # Indicateur si un clavier est détecté
controller_device_name = "" # Nom exact du joystick détecté (pour auto-préréglages)
# Affichage des plateformes
GRID_COLS = 3 # Number of columns in the platform grid
GRID_ROWS = 4 # Number of rows in the platform grid
platforms = [] # Liste des plateformes disponibles
current_platform = 0 # Index de la plateforme actuelle sélectionnée
platform_names = {} # {platform_id: platform_name}
games_count = {} # Dictionnaire comptant le nombre de jeux par plateforme
platform_dicts = [] # Liste des dictionnaires de plateformes
# Filtre plateformes
selected_filter_index = 0 # index dans la liste visible triée
filter_platforms_scroll_offset = 0 # défilement si liste longue
filter_platforms_dirty = False # indique si modifications non sauvegardées
filter_platforms_selection = [] # copie de travail des plateformes visibles (bool masque?) structure: list of (name, hidden_bool)
# Affichage des jeux et sélection
games = [] # Liste des jeux pour la plateforme actuelle
current_game = 0 # Index du jeu actuellement sélectionné
menu_state = "loading" # État actuel de l'interface menu
scroll_offset = 0 # Offset de défilement pour la liste des jeux
visible_games = 15 # Nombre de jeux visibles en même temps par défaut
# Options d'affichage
accessibility_mode = False # Mode accessibilité pour les polices agrandies
accessibility_settings = {"font_scale": 1.0} # Paramètres d'accessibilité (échelle de police)
font_scale_options = [0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0] # Options disponibles pour l'échelle de police
current_font_scale_index = 3 # Index pour 1.0
popup_start_time = 0 # Timestamp de début d'affichage du popup
last_progress_update = 0 # Timestamp de la dernière mise à jour de progression
transition_state = "idle" # État de la transition d'écran
transition_progress = 0.0 # Progression de la transition (0.0 à 1.0)
transition_duration = 18 # Durée de la transition en frames
music_enabled = True # Par défaut la musique est activée
sources_mode = "rgsx" # Mode des sources de jeux (rgsx/custom)
custom_sources_url = {OTA_data_ZIP} # URL personnalisée si mode custom
selected_language_index = 0 # Index de la langue sélectionnée dans la liste
# Recherche et filtres
filtered_games = [] # Liste des jeux filtrés par recherche ou filtre
search_mode = False # Indicateur si le mode recherche est actif
search_query = "" # Chaîne de recherche saisie par l'utilisateur
filter_active = False # Indicateur si un filtre est appliqué
# Gestion des états du menu
needs_redraw = False # Indicateur si l'écran doit être redessiné
selected_option = 0 # Index de l'option sélectionnée dans le menu
previous_menu_state = None # État précédent du menu pour navigation
loading_progress = 0.0 # Progression du chargement initial (0.0 à 1.0)
current_loading_system = "" # Nom du système en cours de chargement
# Gestion des téléchargements et de l'historique
history = [] # Liste des entrées d'historique avec platform, game_name, status, url, progress, message, timestamp
pending_download = None # Objet de téléchargement en attente
download_progress = {} # Dictionnaire de progression des téléchargements actifs
download_tasks = {} # Dictionnaire pour les tâches de téléchargement
download_result_message = "" # Message de résultat du dernier téléchargement
download_result_error = False # Indicateur d'erreur pour le résultat de téléchargement
download_result_start_time = 0 # Timestamp de début du résultat affiché
current_history_item = 0 # Index de l'élément d'historique affiché
history_scroll_offset = 0 # Offset pour le défilement de l'historique
visible_history_items = 15 # Nombre d'éléments d'historique visibles (ajusté dynamiquement)
confirm_clear_selection = 0 # confirmation clear historique
confirm_cancel_selection = 0 # confirmation annulation téléchargement
# Tracking des jeux téléchargés
downloaded_games = {} # Dict {platform_name: {game_name: {"timestamp": "...", "size": "..."}}}
# Scraper de métadonnées
scraper_image_surface = None # Surface Pygame contenant l'image scrapée
scraper_image_url = "" # URL de l'image actuellement affichée
scraper_game_name = "" # Nom du jeu en cours de scraping
scraper_platform_name = "" # Nom de la plateforme en cours de scraping
scraper_loading = False # Indicateur de chargement en cours
scraper_error_message = "" # Message d'erreur du scraper
scraper_description = "" # Description du jeu
scraper_genre = "" # Genre(s) du jeu
scraper_release_date = "" # Date de sortie du jeu
scraper_game_page_url = "" # URL de la page du jeu sur TheGamesDB
# CLES API / PREMIUM HOSTS
API_KEY_1FICHIER = ""
API_KEY_ALLDEBRID = ""
API_KEY_REALDEBRID = ""
PREMIUM_HOST_MARKERS = [
"1Fichier",
]
hide_premium_systems = False # Indicateur pour masquer les systèmes premium
# Variables diverses
update_checked = False
extension_confirm_selection = 0 # Index de sélection pour confirmation d'extension
controls_config = {} # Configuration des contrôles personnalisés
selected_key = (0, 0) # Position du curseur dans le clavier virtuel
popup_message = "" # Message à afficher dans les popups
popup_timer = 0 # Temps restant pour le popup en millisecondes (0 = inactif)
last_frame_time = pygame.time.get_ticks() if pygame is not None else 0 # Timestamp de la dernière frame rendue
current_music_name = None # Nom de la piste musicale actuelle
music_popup_start_time = 0 # Timestamp de début du popup musique
error_message = "" # Message d'erreur à afficher
# Détection d'appui long sur confirm (menu game)
confirm_press_start_time = 0 # Timestamp du début de l'appui sur confirm
confirm_long_press_threshold = 2000 # Durée en ms pour déclencher l'appui long (2 secondes)
confirm_long_press_triggered = False # Flag pour éviter de déclencher plusieurs fois
# Tenter la récupération de la famille de police sauvegardée
try:
from rgsx_settings import get_font_family # import tardif pour éviter dépendances circulaires lors de l'exécution initiale
saved_family = get_font_family()
@@ -288,8 +411,6 @@ def init_font():
logger.error(f"Erreur fallback dejavu: {e2}")
font = title_font = search_font = progress_font = small_font = None
# Indique si une vérification/installation des mises à jour a déjà été effectuée au démarrage
update_checked = False
def validate_resolution():
"""Valide la résolution de l'écran par rapport aux capacités de l'écran."""
@@ -299,4 +420,5 @@ def validate_resolution():
if SCREEN_WIDTH > display_info.current_w or SCREEN_HEIGHT > display_info.current_h:
logger.warning(f"Résolution {SCREEN_WIDTH}x{SCREEN_HEIGHT} dépasse les limites de l'écran")
return display_info.current_w, display_info.current_h
return SCREEN_WIDTH, SCREEN_HEIGHT
return SCREEN_WIDTH, SCREEN_HEIGHT

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -25,34 +25,75 @@ def init_history():
return history_path
def load_history():
"""Charge l'historique depuis history.json."""
"""Charge l'historique depuis history.json avec gestion d'erreur robuste."""
history_path = getattr(config, 'HISTORY_PATH')
try:
if not os.path.exists(history_path):
logger.debug(f"Aucun fichier d'historique trouvé à {history_path}")
return []
# Vérifier que le fichier n'est pas vide avant de lire
if os.path.getsize(history_path) == 0:
logger.warning(f"Fichier history.json vide détecté, retour liste vide")
return []
with open(history_path, "r", encoding='utf-8') as f:
history = json.load(f)
content = f.read()
if not content or content.strip() == '':
logger.warning(f"Contenu history.json vide, retour liste vide")
return []
history = json.loads(content)
# Valider la structure : liste de dictionnaires avec 'platform', 'game_name', 'status'
if not isinstance(history, list):
logger.warning(f"Format history.json invalide (pas une liste), retour liste vide")
return []
# Filtrer les entrées valides au lieu de tout rejeter
valid_entries = []
invalid_count = 0
for entry in history:
if not all(key in entry for key in ['platform', 'game_name', 'status']):
logger.warning(f"Entrée d'historique invalide : {entry}")
return []
#logger.debug(f"Historique chargé depuis {history_path}, {len(history)} entrées")
return history
if isinstance(entry, dict) and all(key in entry for key in ['platform', 'game_name', 'status']):
valid_entries.append(entry)
else:
invalid_count += 1
logger.warning(f"Entrée d'historique invalide ignorée : {entry}")
if invalid_count > 0:
logger.info(f"Historique chargé : {len(valid_entries)} valides, {invalid_count} invalides ignorées")
#logger.debug(f"Historique chargé depuis {history_path}, {len(valid_entries)} entrées")
return valid_entries
except (FileNotFoundError, json.JSONDecodeError) as e:
logger.error(f"Erreur lors de la lecture de {history_path} : {e}")
return []
except Exception as e:
logger.error(f"Erreur inattendue lors de la lecture de {history_path} : {e}")
return []
def save_history(history):
"""Sauvegarde l'historique dans history.json."""
"""Sauvegarde l'historique dans history.json de manière atomique."""
history_path = getattr(config, 'HISTORY_PATH')
try:
os.makedirs(os.path.dirname(history_path), exist_ok=True)
with open(history_path, "w", encoding='utf-8') as f:
# Écriture atomique : écrire dans un fichier temporaire puis renommer
temp_path = history_path + '.tmp'
with open(temp_path, "w", encoding='utf-8') as f:
json.dump(history, f, indent=2, ensure_ascii=False)
f.flush() # Forcer l'écriture sur disque
os.fsync(f.fileno()) # Synchroniser avec le système de fichiers
# Renommer atomiquement (remplace l'ancien fichier)
os.replace(temp_path, history_path)
except Exception as e:
logger.error(f"Erreur lors de l'écriture de {history_path} : {e}")
# Nettoyer le fichier temporaire en cas d'erreur
try:
if os.path.exists(temp_path):
os.remove(temp_path)
except:
pass
def add_to_history(platform, game_name, status, url=None, progress=0, message=None, timestamp=None):
"""Ajoute une entrée à l'historique."""
@@ -73,11 +114,107 @@ def add_to_history(platform, game_name, status, url=None, progress=0, message=No
return entry
def clear_history():
"""Vide l'historique."""
"""Vide l'historique en conservant les téléchargements en cours."""
history_path = getattr(config, 'HISTORY_PATH')
try:
# Charger l'historique actuel
current_history = load_history()
# Conserver uniquement les entrées avec statut actif (téléchargement, extraction ou conversion en cours)
# Supporter les deux variantes de statut (anglais et français)
active_statuses = {"Downloading", "Téléchargement", "downloading", "Extracting", "Converting", "Queued"}
preserved_entries = [
entry for entry in current_history
if entry.get("status") in active_statuses
]
# Sauvegarder l'historique filtré
with open(history_path, "w", encoding='utf-8') as f:
json.dump([], f)
logger.info(f"Historique vidé : {history_path}")
json.dump(preserved_entries, f, indent=2, ensure_ascii=False)
removed_count = len(current_history) - len(preserved_entries)
logger.info(f"Historique vidé : {history_path} ({removed_count} entrées supprimées, {len(preserved_entries)} conservées)")
except Exception as e:
logger.error(f"Erreur lors du vidage de {history_path} : {e}")
logger.error(f"Erreur lors du vidage de {history_path} : {e}")
# ==================== GESTION DES JEUX TÉLÉCHARGÉS ====================
def load_downloaded_games():
"""Charge la liste des jeux déjà téléchargés depuis downloaded_games.json."""
downloaded_path = getattr(config, 'DOWNLOADED_GAMES_PATH')
try:
if not os.path.exists(downloaded_path):
logger.debug(f"Aucun fichier downloaded_games.json trouvé à {downloaded_path}")
return {}
if os.path.getsize(downloaded_path) == 0:
logger.warning(f"Fichier downloaded_games.json vide")
return {}
with open(downloaded_path, "r", encoding='utf-8') as f:
content = f.read()
if not content or content.strip() == '':
return {}
downloaded = json.loads(content)
if not isinstance(downloaded, dict):
logger.warning(f"Format downloaded_games.json invalide (pas un dict)")
return {}
logger.debug(f"Jeux téléchargés chargés : {sum(len(v) for v in downloaded.values())} jeux")
return downloaded
except (FileNotFoundError, json.JSONDecodeError) as e:
logger.error(f"Erreur lors de la lecture de {downloaded_path} : {e}")
return {}
except Exception as e:
logger.error(f"Erreur inattendue lors de la lecture de {downloaded_path} : {e}")
return {}
def save_downloaded_games(downloaded_games_dict):
"""Sauvegarde la liste des jeux téléchargés dans downloaded_games.json."""
downloaded_path = getattr(config, 'DOWNLOADED_GAMES_PATH')
try:
os.makedirs(os.path.dirname(downloaded_path), exist_ok=True)
# Écriture atomique
temp_path = downloaded_path + '.tmp'
with open(temp_path, "w", encoding='utf-8') as f:
json.dump(downloaded_games_dict, f, indent=2, ensure_ascii=False)
f.flush()
os.fsync(f.fileno())
os.replace(temp_path, downloaded_path)
logger.debug(f"Jeux téléchargés sauvegardés : {sum(len(v) for v in downloaded_games_dict.values())} jeux")
except Exception as e:
logger.error(f"Erreur lors de l'écriture de {downloaded_path} : {e}")
try:
if os.path.exists(temp_path):
os.remove(temp_path)
except:
pass
def mark_game_as_downloaded(platform_name, game_name, file_size=None):
"""Marque un jeu comme téléchargé."""
downloaded = config.downloaded_games
if platform_name not in downloaded:
downloaded[platform_name] = {}
downloaded[platform_name][game_name] = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"size": file_size or "N/A"
}
# Sauvegarder immédiatement
save_downloaded_games(downloaded)
logger.info(f"Jeu marqué comme téléchargé : {platform_name} / {game_name}")
def is_game_downloaded(platform_name, game_name):
"""Vérifie si un jeu a déjà été téléchargé."""
downloaded = config.downloaded_games
return platform_name in downloaded and game_name in downloaded.get(platform_name, {})

View File

@@ -37,6 +37,11 @@
"history_status_completed": "Abgeschlossen",
"history_status_error": "Fehler: {0}",
"history_status_canceled": "Abgebrochen",
"free_mode_waiting": "[Kostenloser Modus] Warten: {0}/{1}s",
"free_mode_download": "[Kostenloser Modus] Download: {0}",
"free_mode_submitting": "[Kostenloser Modus] Formular wird gesendet...",
"free_mode_link_found": "[Kostenloser Modus] Link gefunden: {0}...",
"free_mode_completed": "[Kostenloser Modus] Abgeschlossen: {0}",
"download_status": "{0}: {1}",
"download_canceled": "Download vom Benutzer abgebrochen.",
"extension_warning_zip": "Die Datei '{0}' ist ein Archiv und Batocera unterstützt keine Archive für dieses System. Die automatische Extraktion der Datei erfolgt nach dem Download, fortfahren?",
@@ -63,6 +68,7 @@
"menu_music_enabled": "Musik aktiviert: {0}",
"menu_music_disabled": "Musik deaktiviert",
"menu_restart": "Neustart",
"menu_support": "Unterstützung",
"menu_filter_platforms": "Systeme filtern",
"filter_platforms_title": "Systemsichtbarkeit",
"filter_platforms_info": "Sichtbar: {0} | Versteckt: {1} / Gesamt: {2}",
@@ -74,12 +80,17 @@
"menu_allow_unknown_ext_enabled": "Ausblenden der Warnung bei unbekannter Erweiterung aktiviert",
"menu_allow_unknown_ext_disabled": "Ausblenden der Warnung bei unbekannter Erweiterung deaktiviert",
"menu_quit": "Beenden",
"support_dialog_title": "Support-Datei",
"support_dialog_message": "Eine Support-Datei wurde mit allen Ihren Konfigurations- und Protokolldateien erstellt.\n\nDatei: {0}\n\nUm Hilfe zu erhalten:\n1. Treten Sie dem RGSX Discord-Server bei\n2. Beschreiben Sie Ihr Problem\n3. Teilen Sie diese ZIP-Datei\n\nDrücken Sie {1}, um zum Menü zurückzukehren.",
"support_dialog_error": "Fehler beim Erstellen der Support-Datei:\n{0}\n\nDrücken Sie {1}, um zum Menü zurückzukehren.",
"button_yes": "Ja",
"button_no": "Nein",
"button_OK": "OK",
"popup_restarting": "Neustart...",
"controls_action_clear_history": "Mehrfachauswahl / Verlauf leeren",
"controls_action_history": "Verlauf",
"controls_action_clear_history": "Verlauf leeren",
"controls_action_history": "Verlauf / Downloads",
"controls_action_close_history": "Verlauf schließen",
"controls_action_queue": "Warteschlange",
"controls_action_delete": "Löschen",
"controls_action_space": "Leerzeichen",
"controls_action_start": "Hilfe / Einstellungen",
@@ -93,14 +104,13 @@
"network_update_error": "Fehler während des Updates: {0}",
"network_download_extract_ok": "Download und Extraktion von {0} erfolgreich",
"network_check_update_error": "Fehler bei der Überprüfung von Updates: {0}",
"network_extraction_failed": "Fehler beim Extrahieren des Updates: {0}",
"network_extraction_failed": "Fehler beim Extrahieren: {0}",
"network_extraction_partial": "Extraktion erfolgreich, aber einige Dateien wurden aufgrund von Fehlern übersprungen: {0}",
"network_extraction_success": "Extraktion erfolgreich",
"network_zip_extraction_error": "Fehler beim Extrahieren des ZIP {0}: {1}",
"network_file_not_found": "Die Datei {0} existiert nicht",
"network_cannot_get_filename": "Dateiname konnte nicht abgerufen werden",
"network_cannot_get_download_url": "Download-URL konnte nicht abgerufen werden",
"download_initializing": "Initialisierung läuft...",
"accessibility_font_size": "Schriftgröße: {0}",
"confirm_cancel_download": "Laufenden Download abbrechen?",
"controls_help_title": "Hilfe zu Steuerung",
@@ -116,9 +126,20 @@
"network_download_failed": "Download nach {0} Versuchen fehlgeschlagen",
"network_api_error": "Fehler bei der API-Anfrage, der Schlüssel könnte falsch sein: {0}",
"network_download_error": "Downloadfehler {0}: {1}",
"network_connection_failed": "Verbindung nach {0} Versuchen fehlgeschlagen",
"network_http_error": "HTTP-Fehler {0}",
"network_timeout_error": "Verbindungszeitüberschreitung",
"network_connection_error": "Netzwerkverbindungsfehler",
"network_no_response": "Keine Antwort vom Server",
"network_auth_required": "Authentifizierung erforderlich (HTTP {0})",
"network_access_denied": "Zugriff verweigert (HTTP {0})",
"network_server_error": "Serverfehler (HTTP {0})",
"network_download_ok": "Download erfolgreich: {0}",
"download_already_present": " (bereits vorhanden)",
"download_already_extracted": " (bereits extrahiert)",
"download_in_progress": "Download läuft...",
"download_queued": "In Download-Warteschlange",
"download_started": "Download gestartet",
"utils_extracted": "Extrahiert: {0}",
"utils_corrupt_zip": "Beschädigtes ZIP-Archiv: {0}",
"utils_permission_denied": "Berechtigung während der Extraktion verweigert: {0}",
@@ -163,6 +184,7 @@
,"instruction_pause_games": "Verlauf öffnen, Quelle wechseln oder Liste aktualisieren"
,"instruction_pause_settings": "Musik, Symlink-Option & API-Schlüsselstatus"
,"instruction_pause_restart": "RGSX neu starten um Konfiguration neu zu laden"
,"instruction_pause_support": "Eine Diagnose-ZIP-Datei für den Support erstellen"
,"instruction_pause_quit": "RGSX Anwendung beenden"
,"instruction_controls_help": "Komplette Referenz für Controller & Tastatur anzeigen"
,"instruction_controls_remap": "Tasten / Buttons neu zuordnen"
@@ -180,6 +202,15 @@
,"instruction_settings_music": "Hintergrundmusik aktivieren oder deaktivieren"
,"instruction_settings_symlink": "Verwendung von Symlinks für Installationen umschalten"
,"instruction_settings_api_keys": "Gefundene Premium-API-Schlüssel ansehen"
,"instruction_settings_web_service": "Web-Dienst Autostart beim Booten aktivieren/deaktivieren"
,"settings_web_service": "Web-Dienst beim Booten"
,"settings_web_service_enabled": "Aktiviert"
,"settings_web_service_disabled": "Deaktiviert"
,"settings_web_service_enabling": "Web-Dienst wird aktiviert..."
,"settings_web_service_disabling": "Web-Dienst wird deaktiviert..."
,"settings_web_service_success_enabled": "Web-Dienst beim Booten aktiviert"
,"settings_web_service_success_disabled": "Web-Dienst beim Booten deaktiviert"
,"settings_web_service_error": "Fehler: {0}"
,"controls_desc_confirm": "Bestätigen (z.B. A/Kreuz)"
,"controls_desc_cancel": "Abbrechen/Zurück (z.B. B/Kreis)"
,"controls_desc_up": "UP ↑"
@@ -200,4 +231,125 @@
,"controls_mapping_press": "Drücke eine Taste oder einen Button"
,"status_already_present": "Bereits Vorhanden"
,"footer_joystick": "Joystick: {0}"
,"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_delete_game": "Spiel löschen"
,"history_option_error_info": "Fehlerdetails"
,"history_option_retry": "Download wiederholen"
,"history_option_back": "Zurück"
,"history_folder_path_label": "Zielpfad:"
,"history_scraper_not_implemented": "Scraper noch nicht implementiert"
,"history_confirm_delete": "Dieses Spiel von der Festplatte löschen?"
,"history_file_not_found": "Datei nicht gefunden"
,"history_extracting": "Extrahieren..."
,"history_extracted": "Extrahiert"
,"history_delete_success": "Spiel erfolgreich gelöscht"
,"history_delete_error": "Fehler beim Löschen des Spiels: {0}"
,"history_error_details_title": "Fehlerdetails"
,"history_no_error_message": "Keine Fehlermeldung verfügbar"
,"web_title": "RGSX Web-Oberfläche"
,"web_tab_platforms": "Systemliste"
,"web_tab_downloads": "Downloads"
,"web_tab_history": "Verlauf"
,"web_tab_settings": "Einstellungen"
,"web_tab_update": "Liste aktualisieren"
,"web_tooltip_platforms": "Systemliste"
,"web_tooltip_downloads": "Downloads"
,"web_tooltip_history": "Verlauf"
,"web_tooltip_settings": "Einstellungen"
,"web_tooltip_update": "Spieleliste aktualisieren"
,"web_search_platform": "Systeme oder Spiele suchen..."
,"web_search_game": "Spiel suchen..."
,"web_search_results": "Ergebnisse für"
,"web_no_results": "Keine Ergebnisse gefunden"
,"web_platforms": "Systeme"
,"web_games": "Spiele"
,"web_error_search": "Suchfehler"
,"web_back_platforms": "Zurück zu Plattformen"
,"web_back": "Zurück"
,"web_game_count": "{0} ({1} Spiele)"
,"web_download": "Herunterladen"
,"web_cancel": "Abbrechen"
,"web_download_canceled": "Download abgebrochen"
,"web_confirm_cancel": "Möchten Sie diesen Download wirklich abbrechen?"
,"web_update_title": "Spieleliste wird aktualisiert..."
,"web_update_message": "Cache wird gelöscht und Daten neu geladen..."
,"web_update_wait": "Dies kann 10-30 Sekunden dauern"
,"web_error": "Fehler"
,"web_error_unknown": "Unbekannter Fehler"
,"web_error_update": "Fehler beim Aktualisieren der Liste: {0}"
,"web_error_download": "Fehler: {0}"
,"web_history_clear": "Verlauf löschen"
,"web_history_cleared": "Verlauf erfolgreich gelöscht!"
,"web_error_clear_history": "Fehler beim Löschen des Verlaufs: {0}"
,"web_settings_title": "Info & Einstellungen"
,"web_settings_roms_folder": "Benutzerdefinierter ROMs-Ordner"
,"web_settings_roms_placeholder": "Leer lassen für Standard"
,"web_settings_browse": "Durchsuchen"
,"web_settings_language": "Sprache"
,"web_settings_font_scale": "Schriftgröße"
,"web_settings_grid": "Rasterlayout"
,"web_settings_font_family": "Schriftart"
,"web_settings_music": "Musik"
,"web_settings_symlink": "Symlink-Modus"
,"web_settings_source_mode": "Spielequelle"
,"web_settings_custom_url": "Benutzerdefinierte URL"
,"web_settings_custom_url_placeholder": "https://beispiel.com/spiele.zip"
,"web_settings_save": "Einstellungen speichern"
,"web_settings_saved": "Einstellungen erfolgreich gespeichert!"
,"web_settings_saved_restart": "Einstellungen erfolgreich gespeichert!\\n\\n⚠ Einige Einstellungen erfordern einen Serverneustart:\\n- Benutzerdefinierter ROMs-Ordner\\n- Sprache\\n\\nBitte starten Sie den Webserver neu, um diese Änderungen anzuwenden."
,"web_error_save_settings": "Fehler beim Speichern der Einstellungen: {0}"
,"web_browse_title": "Verzeichnisse durchsuchen"
,"web_browse_select_drive": "Laufwerk auswählen..."
,"web_browse_drives": "Laufwerke"
,"web_browse_parent": "Übergeordnet"
,"web_browse_select": "Diesen Ordner auswählen"
,"web_browse_cancel": "Abbrechen"
,"web_browse_empty": "Keine Unterverzeichnisse gefunden"
,"web_browse_alert_restart": "Wichtig: Sie müssen die Einstellungen SPEICHERN und dann den Webserver NEUSTARTEN, damit der benutzerdefinierte ROMs-Ordner wirksam wird.\\n\\n📝 Schritte:\\n1. Klicken Sie unten auf 'Einstellungen speichern'\\n2. Stoppen Sie den Webserver (Strg+C im Terminal)\\n3. Starten Sie den Webserver neu\\n\\nAusgewählter Pfad: {0}"
,"web_error_browse": "Fehler beim Durchsuchen der Verzeichnisse: {0}"
,"web_loading_platforms": "Lade Plattformen..."
,"web_loading_games": "Lade Spiele..."
,"web_no_platforms": "Keine Plattformen gefunden"
,"web_no_downloads": "Keine Downloads im Gange"
,"web_history_empty": "Keine abgeschlossenen Downloads"
,"web_history_platform": "Plattform"
,"web_history_size": "Größe"
,"web_history_status_completed": "Abgeschlossen"
,"web_history_status_error": "Fehler"
,"web_settings_os": "Betriebssystem"
,"web_settings_platforms_count": "Anzahl der Plattformen"
,"web_settings_show_unsupported": "Nicht unterstützte Plattformen anzeigen (System fehlt in es_systems.cfg)"
,"web_settings_allow_unknown": "Unbekannte Erweiterungen erlauben (keine Warnungen anzeigen)"
,"web_restart_confirm_title": "Anwendung neu starten?"
,"web_restart_confirm_message": "Die Einstellungen wurden gespeichert. Möchten Sie die Anwendung jetzt neu starten, um die Änderungen anzuwenden?"
,"web_restart_yes": "Ja, neu starten"
,"web_restart_no": "Nein, später"
,"web_restart_success": "Neustart läuft..."
,"web_restart_error": "Fehler beim Neustart: {0}"
,"web_support": "Support"
,"web_support_title": "📦 Support-Datei erstellt"
,"web_support_message": "Support-Datei erfolgreich erstellt!\\n\\n📁 Inhalt:\\n• Steuerungskonfiguration\\n• Download-Verlauf\\n• RGSX-Einstellungen\\n• Anwendungsprotokolle\\n• Webserver-Protokolle\\n\\n💬 Um Hilfe zu erhalten:\\n1. Trete dem RGSX Discord bei\\n2. Beschreibe dein Problem\\n3. Teile diese ZIP-Datei\\n\\nDownload startet..."
,"web_support_generating": "Support-Datei wird generiert..."
,"web_support_download": "Support-Datei herunterladen"
,"web_support_error": "Fehler beim Erstellen der Support-Datei: {0}"
,"web_tab_queue": "Warteschlange"
,"web_tooltip_queue": "Download-Warteschlange"
,"web_queue_active_download": "⏳ Ein Download ist aktiv"
,"web_queue_no_active": "✓ Kein aktiver Download"
,"web_queue_title": "Download-Warteschlange"
,"web_queue_empty": "Keine Elemente in der Warteschlange"
,"web_queue_clear": "Warteschlange löschen"
,"web_queue_cleared": "Warteschlange erfolgreich gelöscht!"
,"web_confirm_remove_queue": "Dieses Element aus der Warteschlange entfernen?"
,"web_confirm_clear_queue": "Gesamte Warteschlange löschen?"
,"web_remove": "Entfernen"
,"web_loading": "Lädt..."
,"web_sort": "Sortieren nach"
,"web_sort_name_asc": "A-Z (Name)"
,"web_sort_name_desc": "Z-A (Name)"
,"web_sort_size_asc": "Größe +- (Klein zuerst)"
,"web_sort_size_desc": "Größe -+ (Groß zuerst)"
}

View File

@@ -37,6 +37,11 @@
"history_status_completed": "Completed",
"history_status_error": "Error: {0}",
"history_status_canceled": "Canceled",
"free_mode_waiting": "[Free mode] Waiting: {0}/{1}s",
"free_mode_download": "[Free mode] Downloading: {0}",
"free_mode_submitting": "[Free mode] Submitting form...",
"free_mode_link_found": "[Free mode] Link found: {0}...",
"free_mode_completed": "[Free mode] Completed: {0}",
"download_status": "{0}: {1}",
"download_canceled": "Download canceled by user.",
"extension_warning_zip": "The file '{0}' is an archive and Batocera does not support archives for this system. Automatic extraction will occur after download, continue?",
@@ -73,16 +78,22 @@
"menu_allow_unknown_ext_off": "Hide unknown extension warning: No",
"menu_allow_unknown_ext_enabled": "Hide unknown extension warning enabled",
"menu_allow_unknown_ext_disabled": "Hide unknown extension warning disabled",
"menu_support": "Support",
"menu_quit": "Quit",
"button_yes": "Yes",
"button_no": "No",
"button_OK": "OK",
"popup_restarting": "Restarting...",
"controls_action_clear_history": "Multi-select / Clear History",
"controls_action_history": "History",
"controls_action_clear_history": "Clear History",
"controls_action_delete": "Delete",
"controls_action_space": "Space",
"controls_action_start": "Help / Settings",
"controls_action_queue": "Add to Queue",
"support_dialog_title": "Support File",
"support_dialog_message": "A support file has been created with all your configuration and log files.\n\nFile: {0}\n\nTo get help:\n1. Join the RGSX Discord server\n2. Describe your issue\n3. Share this ZIP file\n\nPress {1} to return to the menu.",
"support_dialog_error": "Error generating support file:\n{0}\n\nPress {1} to return to the menu.",
"controls_action_history": "History / Downloads",
"controls_action_close_history": "Close History",
"network_checking_updates": "Checking for updates...",
"network_update_available": "Update available: {0}",
"network_extracting_update": "Extracting update...",
@@ -92,7 +103,7 @@
"network_no_update_available": "No update available",
"network_update_error": "Error during update: {0}",
"network_check_update_error": "Error checking for updates: {0}",
"network_extraction_failed": "Failed to extract update: {0}",
"network_extraction_failed": "Failed to extract: {0}",
"network_extraction_partial": "Extraction successful, but some files were skipped due to errors: {0}",
"network_extraction_success": "Extraction successful",
"network_download_extract_ok": "Download and extraction successful of {0}",
@@ -103,16 +114,27 @@
"network_download_failed": "Download failed after {0} attempts",
"network_api_error": "API request error, the key may be incorrect: {0}",
"network_download_error": "Download error {0}: {1}",
"network_connection_failed": "Connection failed after {0} attempts",
"network_http_error": "HTTP error {0}",
"network_timeout_error": "Connection timeout",
"network_connection_error": "Network connection error",
"network_no_response": "No response from server",
"network_auth_required": "Authentication required (HTTP {0})",
"network_access_denied": "Access denied (HTTP {0})",
"network_server_error": "Server error (HTTP {0})",
"network_download_ok": "Download OK: {0}",
"download_already_present": " (already present)",
"download_already_extracted": " (already extracted)",
"download_in_progress": "Download in progress...",
"download_queued": "Queued for download",
"download_started": "Download started",
"network_download_already_queued": "This download is already in progress",
"utils_extracted": "Extracted: {0}",
"utils_corrupt_zip": "Corrupted ZIP archive: {0}",
"utils_permission_denied": "Permission denied during extraction: {0}",
"utils_extraction_failed": "Extraction failed: {0}",
"utils_unrar_unavailable": "Unrar command not available",
"utils_rar_list_failed": "Failed to list RAR files: {0}",
"download_initializing": "Initializing...",
"accessibility_font_size": "Font size: {0}",
"confirm_cancel_download": "Cancel current download?",
"controls_help_title": "Controls Help",
@@ -163,6 +185,7 @@
"instruction_pause_games": "Open history, switch source or refresh list",
"instruction_pause_settings": "Music, symlink option & API keys status",
"instruction_pause_restart": "Restart RGSX to reload configuration"
,"instruction_pause_support": "Generate a diagnostic ZIP file for support"
,"instruction_pause_quit": "Exit the RGSX application"
,"instruction_controls_help": "Show full controller & keyboard reference"
,"instruction_controls_remap": "Change button / key bindings"
@@ -180,6 +203,15 @@
,"instruction_settings_music": "Enable or disable background music playback"
,"instruction_settings_symlink": "Toggle using filesystem symlinks for installs"
,"instruction_settings_api_keys": "See detected premium provider API keys"
,"instruction_settings_web_service": "Enable/disable web service auto-start at boot"
,"settings_web_service": "Web Service at Boot"
,"settings_web_service_enabled": "Enabled"
,"settings_web_service_disabled": "Disabled"
,"settings_web_service_enabling": "Enabling web service..."
,"settings_web_service_disabling": "Disabling web service..."
,"settings_web_service_success_enabled": "Web service enabled at boot"
,"settings_web_service_success_disabled": "Web service disabled at boot"
,"settings_web_service_error": "Error: {0}"
,"controls_desc_confirm": "Confirm (e.g. A/Cross)"
,"controls_desc_cancel": "Cancel/Back (e.g. B/Circle)"
,"controls_desc_up": "UP ↑"
@@ -200,4 +232,125 @@
,"controls_mapping_press": "Press a key or a button"
,"status_already_present": "Already Present"
,"footer_joystick": "Joystick: {0}"
,"history_game_options_title": "Game Options"
,"history_option_download_folder": "Locate file"
,"history_option_extract_archive": "Extract archive"
,"history_option_scraper": "Scrape metadata"
,"history_option_delete_game": "Delete game"
,"history_option_error_info": "Error details"
,"history_option_retry": "Retry download"
,"history_option_back": "Back"
,"history_folder_path_label": "Destination path:"
,"history_scraper_not_implemented": "Scraper not yet implemented"
,"history_confirm_delete": "Delete this game from disk?"
,"history_file_not_found": "File not found"
,"history_extracting": "Extracting..."
,"history_extracted": "Extracted"
,"history_delete_success": "Game deleted successfully"
,"history_delete_error": "Error deleting game: {0}"
,"history_error_details_title": "Error Details"
,"history_no_error_message": "No error message available"
,"web_title": "RGSX Web Interface"
,"web_tab_platforms": "Platforms List"
,"web_tab_downloads": "Downloads"
,"web_tab_history": "History"
,"web_tab_settings": "Settings"
,"web_tab_update": "Update games list"
,"web_tooltip_platforms": "Platforms list"
,"web_tooltip_downloads": "Downloads"
,"web_tooltip_history": "History"
,"web_tooltip_settings": "Settings"
,"web_tooltip_update": "Update games list"
,"web_search_platform": "Search platforms or games..."
,"web_search_game": "Search a game..."
,"web_search_results": "results for"
,"web_no_results": "No results found"
,"web_platforms": "Platforms"
,"web_games": "Games"
,"web_error_search": "Search error"
,"web_back_platforms": "Back to platforms"
,"web_back": "Back"
,"web_game_count": "{0} ({1} games)"
,"web_download": "Download"
,"web_cancel": "Cancel"
,"web_download_canceled": "Download canceled"
,"web_confirm_cancel": "Do you really want to cancel this download?"
,"web_update_title": "Updating games list..."
,"web_update_message": "Clearing cache and reloading data..."
,"web_update_wait": "This may take 10-30 seconds"
,"web_error": "Error"
,"web_error_unknown": "Unknown error"
,"web_error_update": "Error updating games list: {0}"
,"web_error_download": "Error: {0}"
,"web_history_clear": "Clear History"
,"web_history_cleared": "History cleared successfully!"
,"web_error_clear_history": "Error clearing history: {0}"
,"web_settings_title": "Info & Settings"
,"web_settings_roms_folder": "Custom ROMs folder"
,"web_settings_roms_placeholder": "Leave empty for default"
,"web_settings_browse": "Browse"
,"web_settings_language": "Language"
,"web_settings_font_scale": "Font scale"
,"web_settings_grid": "Grid layout"
,"web_settings_font_family": "Font family"
,"web_settings_music": "Music"
,"web_settings_symlink": "Symlink mode"
,"web_settings_source_mode": "Games source"
,"web_settings_custom_url": "Custom URL"
,"web_settings_custom_url_placeholder": "Let empty for local /saves/ports/rgsx/games.zip or use a direct URL like https://example.com/games.zip"
,"web_settings_save": "Save Settings"
,"web_settings_saved": "Settings saved successfully!"
,"web_settings_saved_restart": "Settings saved successfully!\\n\\n⚠ Some settings require a server restart:\\n- Custom ROMs folder\\n- Language\\n\\nPlease restart the web server to apply these changes."
,"web_error_save_settings": "Error saving settings: {0}"
,"web_browse_title": "Browse Directories"
,"web_browse_select_drive": "Select a drive..."
,"web_browse_drives": "Drives"
,"web_browse_parent": "Parent"
,"web_browse_select": "Select this folder"
,"web_browse_cancel": "Cancel"
,"web_browse_empty": "No subdirectories found"
,"web_browse_alert_restart": "Important: You need to SAVE the settings and then RESTART the web server/application for the custom ROMs folder to take effect.\\n\\n📝 Steps:\\n1. Click 'Save Settings' button below\\n2. Stop the web server (Ctrl+C in terminal)\\n3. Restart the web server\\n\\nSelected path: {0}"
,"web_error_browse": "Error browsing directories: {0}"
,"web_loading_platforms": "Loading platforms..."
,"web_loading_games": "Loading games..."
,"web_no_platforms": "No platforms found"
,"web_no_downloads": "No downloads in progress"
,"web_history_empty": "No completed downloads"
,"web_history_platform": "Platform"
,"web_history_size": "Size"
,"web_history_status_completed": "Completed"
,"web_history_status_error": "Error"
,"web_settings_os": "Operating System"
,"web_settings_platforms_count": "Number of platforms"
,"web_settings_show_unsupported": "Show unsupported platforms (system not found in es_systems.cfg)"
,"web_settings_allow_unknown": "Allow unknown extensions (don't show warnings)"
,"web_restart_confirm_title": "Restart application?"
,"web_restart_confirm_message": "Settings have been saved. Do you want to restart the application now to apply the changes?"
,"web_restart_yes": "Yes, restart"
,"web_restart_no": "No, later"
,"web_restart_success": "Restarting..."
,"web_restart_error": "Restart error: {0}"
,"web_support": "Support"
,"web_support_title": "📦 Support File Generated"
,"web_support_message": "Support file created successfully!\\n\\n📁 Contents:\\n• Controls configuration\\n• Download history\\n• RGSX settings\\n• Application logs\\n• Web server logs\\n\\n💬 To get help:\\n1. Join RGSX Discord\\n2. Describe your issue\\n3. Share this ZIP file\\n\\nDownload will start..."
,"web_support_generating": "Generating support file..."
,"web_support_download": "Download support file"
,"web_support_error": "Error generating support file: {0}"
,"web_tab_queue": "Queue"
,"web_tooltip_queue": "Download queue"
,"web_queue_active_download": "⏳ A download is currently active"
,"web_queue_no_active": "✓ No active download"
,"web_queue_title": "Download Queue"
,"web_queue_empty": "No items in queue"
,"web_queue_clear": "Clear Queue"
,"web_queue_cleared": "Queue cleared successfully!"
,"web_confirm_remove_queue": "Remove this item from the queue?"
,"web_confirm_clear_queue": "Clear the entire queue?"
,"web_remove": "Remove"
,"web_loading": "Loading..."
,"web_sort": "Sort by"
,"web_sort_name_asc": "A-Z (Name)"
,"web_sort_name_desc": "Z-A (Name)"
,"web_sort_size_asc": "Size +- (Small first)"
,"web_sort_size_desc": "Size -+ (Large first)"
}

View File

@@ -37,6 +37,11 @@
"history_status_completed": "Completado",
"history_status_error": "Error: {0}",
"history_status_canceled": "Cancelado",
"free_mode_waiting": "[Modo gratuito] Esperando: {0}/{1}s",
"free_mode_download": "[Modo gratuito] Descargando: {0}",
"free_mode_submitting": "[Modo gratuito] Enviando formulario...",
"free_mode_link_found": "[Modo gratuito] Enlace encontrado: {0}...",
"free_mode_completed": "[Modo gratuito] Completado: {0}",
"download_status": "{0}: {1}",
"download_canceled": "Descarga cancelada por el usuario.",
"extension_warning_zip": "El archivo '{0}' es un archivo comprimido y Batocera no soporta archivos comprimidos para este sistema. La extracción automática del archivo se realizará después de la descarga, ¿continuar?",
@@ -63,6 +68,7 @@
"menu_music_enabled": "Música activada: {0}",
"menu_music_disabled": "Música desactivada",
"menu_restart": "Reiniciar",
"menu_support": "Soporte",
"menu_filter_platforms": "Filtrar sistemas",
"filter_platforms_title": "Visibilidad de sistemas",
"filter_platforms_info": "Visibles: {0} | Ocultos: {1} / Total: {2}",
@@ -74,15 +80,20 @@
"menu_allow_unknown_ext_enabled": "Aviso de extensión desconocida oculto (activado)",
"menu_allow_unknown_ext_disabled": "Aviso de extensión desconocida visible (desactivado)",
"menu_quit": "Salir",
"support_dialog_title": "Archivo de soporte",
"support_dialog_message": "Se ha creado un archivo de soporte con todos sus archivos de configuración y registros.\n\nArchivo: {0}\n\nPara obtener ayuda:\n1. Únete al servidor Discord de RGSX\n2. Describe tu problema\n3. Comparte este archivo ZIP\n\nPresiona {1} para volver al menú.",
"support_dialog_error": "Error al generar el archivo de soporte:\n{0}\n\nPresiona {1} para volver al menú.",
"button_yes": "Sí",
"button_no": "No",
"button_OK": "OK",
"popup_restarting": "Reiniciando...",
"controls_action_clear_history": "Multi-selección / Vaciar historial",
"controls_action_history": "Historial",
"controls_action_clear_history": "Vaciar historial",
"controls_action_history": "Historial / Descargas",
"controls_action_close_history": "Cerrar Historial",
"controls_action_delete": "Eliminar",
"controls_action_space": "Espacio",
"controls_action_start": "Ayuda / Configuración",
"controls_action_queue": "Poner en cola",
"network_checking_updates": "Verificando actualizaciones...",
"network_update_available": "Actualización disponible: {0}",
"network_extracting_update": "Extrayendo la actualización...",
@@ -93,14 +104,13 @@
"network_update_error": "Error durante la actualización: {0}",
"network_download_extract_ok": "Descarga y extracción exitosa de {0}",
"network_check_update_error": "Error al verificar actualizaciones: {0}",
"network_extraction_failed": "Error al extraer la actualización: {0}",
"network_extraction_failed": "Error al extraer: {0}",
"network_extraction_partial": "Extracción exitosa, pero algunos archivos fueron omitidos debido a errores: {0}",
"network_extraction_success": "Extracción exitosa",
"network_zip_extraction_error": "Error al extraer el ZIP {0}: {1}",
"network_file_not_found": "El archivo {0} no existe",
"network_cannot_get_filename": "No se pudo obtener el nombre del archivo",
"network_cannot_get_download_url": "No se pudo obtener la URL de descarga",
"download_initializing": "Inicializando...",
"accessibility_font_size": "Tamaño de fuente: {0}",
"confirm_cancel_download": "¿Cancelar la descarga en curso?",
"controls_help_title": "Ayuda de controles",
@@ -116,9 +126,20 @@
"network_download_failed": "Error en la descarga tras {0} intentos",
"network_api_error": "Error en la solicitud de API, la clave puede ser incorrecta: {0}",
"network_download_error": "Error en la descarga {0}: {1}",
"network_connection_failed": "Conexión fallida después de {0} intentos",
"network_http_error": "Error HTTP {0}",
"network_timeout_error": "Tiempo de espera agotado",
"network_connection_error": "Error de conexión de red",
"network_no_response": "Sin respuesta del servidor",
"network_auth_required": "Autenticación requerida (HTTP {0})",
"network_access_denied": "Acceso denegado (HTTP {0})",
"network_server_error": "Error del servidor (HTTP {0})",
"network_download_ok": "Descarga exitosa: {0}",
"download_already_present": " (ya presente)",
"download_already_extracted": " (ya extraído)",
"download_in_progress": "Descarga en curso...",
"download_queued": "En cola de descarga",
"download_started": "Descarga iniciada",
"utils_extracted": "Extraído: {0}",
"utils_corrupt_zip": "Archivo ZIP corrupto: {0}",
"utils_permission_denied": "Permiso denegado durante la extracción: {0}",
@@ -163,6 +184,7 @@
,"instruction_pause_games": "Abrir historial, cambiar fuente o refrescar lista"
,"instruction_pause_settings": "Música, opción symlink y estado de claves API"
,"instruction_pause_restart": "Reiniciar RGSX para recargar configuración"
,"instruction_pause_support": "Generar un archivo ZIP de diagnóstico para soporte"
,"instruction_pause_quit": "Salir de la aplicación RGSX"
,"instruction_controls_help": "Mostrar referencia completa de mando y teclado"
,"instruction_controls_remap": "Cambiar asignación de botones / teclas"
@@ -180,6 +202,15 @@
,"instruction_settings_music": "Activar o desactivar música de fondo"
,"instruction_settings_symlink": "Alternar uso de symlinks en instalaciones"
,"instruction_settings_api_keys": "Ver claves API premium detectadas"
,"instruction_settings_web_service": "Activar/desactivar inicio automático del servicio web"
,"settings_web_service": "Servicio Web al Inicio"
,"settings_web_service_enabled": "Activado"
,"settings_web_service_disabled": "Desactivado"
,"settings_web_service_enabling": "Activando servicio web..."
,"settings_web_service_disabling": "Desactivando servicio web..."
,"settings_web_service_success_enabled": "Servicio web activado al inicio"
,"settings_web_service_success_disabled": "Servicio web desactivado al inicio"
,"settings_web_service_error": "Error: {0}"
,"controls_desc_confirm": "Confirmar (ej. A/Cruz)"
,"controls_desc_cancel": "Cancelar/Volver (ej. B/Círculo)"
,"controls_desc_up": "UP ↑"
@@ -200,4 +231,125 @@
,"controls_mapping_press": "Pulsa una tecla o un botón"
,"status_already_present": "Ya Presente"
,"footer_joystick": "Joystick: {0}"
,"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_delete_game": "Eliminar juego"
,"history_option_error_info": "Detalles del error"
,"history_option_retry": "Reintentar descarga"
,"history_option_back": "Volver"
,"history_folder_path_label": "Ruta de destino:"
,"history_scraper_not_implemented": "Scraper aún no implementado"
,"history_confirm_delete": "¿Eliminar este juego del disco?"
,"history_file_not_found": "Archivo no encontrado"
,"history_extracting": "Extrayendo..."
,"history_extracted": "Extraído"
,"history_delete_success": "Juego eliminado con éxito"
,"history_delete_error": "Error al eliminar juego: {0}"
,"history_error_details_title": "Detalles del error"
,"history_no_error_message": "No hay mensaje de error disponible"
,"web_title": "Interfaz Web RGSX"
,"web_tab_platforms": "Lista de sistemas"
,"web_tab_downloads": "Descargas"
,"web_tab_history": "Historial"
,"web_tab_settings": "Configuración"
,"web_tab_update": "Actualizar lista"
,"web_tooltip_platforms": "Lista de sistemas"
,"web_tooltip_downloads": "Descargas"
,"web_tooltip_history": "Historial"
,"web_tooltip_settings": "Configuración"
,"web_tooltip_update": "Actualizar lista de juegos"
,"web_search_platform": "Buscar sistemas o juegos..."
,"web_search_game": "Buscar un juego..."
,"web_search_results": "resultados para"
,"web_no_results": "No se encontraron resultados"
,"web_platforms": "Sistemas"
,"web_games": "Juegos"
,"web_error_search": "Error de búsqueda"
,"web_back_platforms": "Volver a plataformas"
,"web_back": "Volver"
,"web_game_count": "{0} ({1} juegos)"
,"web_download": "Descargar"
,"web_cancel": "Cancelar"
,"web_download_canceled": "Descarga cancelada"
,"web_confirm_cancel": "¿Realmente desea cancelar esta descarga?"
,"web_update_title": "Actualizando lista de juegos..."
,"web_update_message": "Limpiando caché y recargando datos..."
,"web_update_wait": "Esto puede tardar 10-30 segundos"
,"web_error": "Error"
,"web_error_unknown": "Error desconocido"
,"web_error_update": "Error al actualizar la lista: {0}"
,"web_error_download": "Error: {0}"
,"web_history_clear": "Limpiar historial"
,"web_history_cleared": "¡Historial limpiado con éxito!"
,"web_error_clear_history": "Error al limpiar historial: {0}"
,"web_settings_title": "Información y Configuración"
,"web_settings_roms_folder": "Carpeta ROMs personalizada"
,"web_settings_roms_placeholder": "Dejar vacío para predeterminado"
,"web_settings_browse": "Explorar"
,"web_settings_language": "Idioma"
,"web_settings_font_scale": "Escala de fuente"
,"web_settings_grid": "Diseño de cuadrícula"
,"web_settings_font_family": "Familia de fuente"
,"web_settings_music": "Música"
,"web_settings_symlink": "Modo symlink"
,"web_settings_source_mode": "Fuente de juegos"
,"web_settings_custom_url": "URL personalizada"
,"web_settings_custom_url_placeholder": "Dejar vacío para /saves/ports/rgsx/games.zip o usar una URL directa como https://ejemplo.com/juegos.zip"
,"web_settings_save": "Guardar configuración"
,"web_settings_saved": "¡Configuración guardada con éxito!"
,"web_settings_saved_restart": "¡Configuración guardada con éxito!\\n\\n⚠ Algunos ajustes requieren reiniciar el servidor:\\n- Carpeta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie el servidor web para aplicar estos cambios."
,"web_error_save_settings": "Error al guardar configuración: {0}"
,"web_browse_title": "Explorar directorios"
,"web_browse_select_drive": "Seleccione una unidad..."
,"web_browse_drives": "Unidades"
,"web_browse_parent": "Arriba"
,"web_browse_select": "Seleccionar esta carpeta"
,"web_browse_cancel": "Cancelar"
,"web_browse_empty": "No se encontraron subdirectorios"
,"web_browse_alert_restart": "Importante: Debe GUARDAR la configuración y luego REINICIAR el servidor web para que la carpeta ROMs personalizada tenga efecto.\\n\\n📝 Pasos:\\n1. Haga clic en 'Guardar configuración' abajo\\n2. Detenga el servidor web (Ctrl+C en terminal)\\n3. Reinicie el servidor web\\n\\nRuta seleccionada: {0}"
,"web_error_browse": "Error al explorar directorios: {0}"
,"web_loading_platforms": "Cargando plataformas..."
,"web_loading_games": "Cargando juegos..."
,"web_no_platforms": "No se encontraron plataformas"
,"web_no_downloads": "No hay descargas en curso"
,"web_history_empty": "No hay descargas completadas"
,"web_history_platform": "Plataforma"
,"web_history_size": "Tamaño"
,"web_history_status_completed": "Completado"
,"web_history_status_error": "Error"
,"web_settings_os": "Sistema operativo"
,"web_settings_platforms_count": "Número de plataformas"
,"web_settings_show_unsupported": "Mostrar plataformas no compatibles (sistema ausente en es_systems.cfg)"
,"web_settings_allow_unknown": "Permitir extensiones desconocidas (no mostrar advertencias)"
,"web_restart_confirm_title": "¿Reiniciar aplicación?"
,"web_restart_confirm_message": "Los parámetros se han guardado. ¿Desea reiniciar la aplicación ahora para aplicar los cambios?"
,"web_restart_yes": "Sí, reiniciar"
,"web_restart_no": "No, más tarde"
,"web_restart_success": "Reiniciando..."
,"web_restart_error": "Error al reiniciar: {0}"
,"web_support": "Soporte"
,"web_support_title": "📦 Archivo de soporte generado"
,"web_support_message": "¡Archivo de soporte creado con éxito!\\n\\n📁 Contenido:\\n• Configuración de controles\\n• Historial de descargas\\n• Configuración RGSX\\n• Registros de la aplicación\\n• Registros del servidor web\\n\\n💬 Para obtener ayuda:\\n1. Únete al Discord de RGSX\\n2. Describe tu problema\\n3. Comparte este archivo ZIP\\n\\nLa descarga comenzará..."
,"web_support_generating": "Generando archivo de soporte..."
,"web_support_download": "Descargar archivo de soporte"
,"web_support_error": "Error al generar el archivo de soporte: {0}"
,"web_tab_queue": "Cola"
,"web_tooltip_queue": "Cola de descargas"
,"web_queue_active_download": "⏳ Una descarga está activa"
,"web_queue_no_active": "✓ Sin descargas activas"
,"web_queue_title": "Cola de Descargas"
,"web_queue_empty": "No hay elementos en la cola"
,"web_queue_clear": "Limpiar cola"
,"web_queue_cleared": "¡Cola limpiada con éxito!"
,"web_confirm_remove_queue": "¿Eliminar este elemento de la cola?"
,"web_confirm_clear_queue": "¿Limpiar toda la cola?"
,"web_remove": "Eliminar"
,"web_loading": "Cargando..."
,"web_sort": "Ordenar por"
,"web_sort_name_asc": "A-Z (Nombre)"
,"web_sort_name_desc": "Z-A (Nombre)"
,"web_sort_size_asc": "Tamaño +- (Menor primero)"
,"web_sort_size_desc": "Tamaño -+ (Mayor primero)"
}

View File

@@ -2,7 +2,7 @@
"welcome_message": "Bienvenue dans RGSX",
"disclaimer_line1": "It's dangerous to go alone, take all you need!",
"disclaimer_line2": "Mais ne téléchargez que des jeux",
"disclaimer_line3": "dont vous possédez les originaux !",
"disclaimer_line3": "que vous possédez !",
"disclaimer_line4": "RGSX n'est pas responsable des contenus téléchargés,",
"disclaimer_line5": "et n'heberge pas de ROMs.",
"loading_test_connection": "Test de la connexion...",
@@ -37,6 +37,11 @@
"history_status_completed": "Terminé",
"history_status_error": "Erreur : {0}",
"history_status_canceled": "Annulé",
"free_mode_waiting": "[Mode gratuit] Attente: {0}/{1}s",
"free_mode_download": "[Mode gratuit] Téléchargement: {0}",
"free_mode_submitting": "[Mode gratuit] Soumission formulaire...",
"free_mode_link_found": "[Mode gratuit] Lien trouvé: {0}...",
"free_mode_completed": "[Mode gratuit] Terminé: {0}",
"download_status": "{0} : {1}",
"download_canceled": "Téléchargement annulé par l'utilisateur.",
"extension_warning_zip": "Le fichier '{0}' est une archive et Batocera ne prend pas en charge les archives pour ce système. L'extraction automatique du fichier aura lieu après le téléchargement, continuer ?",
@@ -60,6 +65,7 @@
"menu_display": "Affichage",
"display_layout": "Disposition",
"menu_redownload_cache": "Mettre à jour la liste des jeux",
"menu_support": "Support",
"menu_quit": "Quitter",
"menu_music_enabled": "Musique activée : {0}",
"menu_music_disabled": "Musique désactivée",
@@ -69,6 +75,9 @@
"filter_platforms_info": "Visibles: {0} | Masqués: {1} / Total: {2}",
"filter_unsaved_warning": "Modifications non sauvegardées",
"menu_show_unsupported_enabled": "Affichage systèmes non supportés activé",
"support_dialog_title": "Fichier de support",
"support_dialog_message": "Un fichier de support a été créé avec tous vos fichiers de configuration et logs.\n\nFichier: {0}\n\nPour obtenir de l'aide :\n1. Rejoignez le Discord RGSX\n2. Décrivez votre problème\n3. Partagez ce fichier ZIP\n\nAppuyez sur {1} pour revenir au menu.",
"support_dialog_error": "Erreur lors de la génération du fichier de support :\n{0}\n\nAppuyez sur {1} pour revenir au menu.",
"menu_show_unsupported_disabled": "Affichage systèmes non supportés désactivé",
"menu_allow_unknown_ext_on": "Masquer avertissement extension inconnue : Oui",
"menu_allow_unknown_ext_off": "Masquer avertissement extension inconnue : Non",
@@ -78,8 +87,10 @@
"button_no": "Non",
"button_OK": "OK",
"popup_restarting": "Redémarrage...",
"controls_action_clear_history": "Multi-sélection / Vider Historique",
"controls_action_history": "Historique",
"controls_action_clear_history": "Vider Historique",
"controls_action_queue": "Mettre en file d'attente",
"controls_action_history": "Historique / Téléchargements",
"controls_action_close_history": "Fermer l'historique",
"controls_action_delete": "Supprimer",
"controls_action_space": "Espace",
"controls_action_start": "Aide / Réglages",
@@ -93,14 +104,13 @@
"network_update_error": "Erreur lors de la mise à jour : {0}",
"network_download_extract_ok": "Téléchargement et extraction réussi de {0}",
"network_check_update_error": "Erreur lors de la vérification des mises à jour : {0}",
"network_extraction_failed": "Échec de l'extraction de la mise à jour : {0}",
"network_extraction_failed": "Échec de l'extraction: {0}",
"network_extraction_partial": "Extraction réussie, mais certains fichiers ont été ignorés en raison d'erreurs : {0}",
"network_extraction_success": "Extraction réussie",
"network_zip_extraction_error": "Erreur lors de l'extraction du ZIP {0}: {1}",
"network_file_not_found": "Le fichier {0} n'existe pas",
"network_cannot_get_filename": "Impossible de récupérer le nom du fichier",
"network_cannot_get_download_url": "Impossible de récupérer l'URL de téléchargement",
"download_initializing": "Initialisation en cours...",
"accessibility_font_size": "Taille de police : {0}",
"confirm_cancel_download": "Annuler le téléchargement en cours ?",
"controls_help_title": "Aide des contrôles",
@@ -116,9 +126,21 @@
"network_download_failed": "Échec du téléchargement après {0} tentatives",
"network_api_error": "Erreur lors de la requête API, la clé est peut-être incorrecte: {0}",
"network_download_error": "Erreur téléchargement {0}: {1}",
"network_connection_failed": "Échec de connexion après {0} tentatives",
"network_http_error": "Erreur HTTP {0}",
"network_timeout_error": "Délai d'attente dépassé",
"network_connection_error": "Erreur de connexion réseau",
"network_no_response": "Aucune réponse du serveur",
"network_auth_required": "Authentification requise (HTTP {0})",
"network_access_denied": "Accès refusé (HTTP {0})",
"network_server_error": "Erreur serveur (HTTP {0})",
"network_download_ok": "Téléchargement ok : {0}",
"download_already_present": " (déjà présent)",
"download_already_extracted": " (déjà extrait)",
"download_in_progress": "Téléchargement en cours...",
"download_queued": "En file d'attente",
"download_started": "Téléchargement démarré",
"network_download_already_queued": "Ce téléchargement est déjà en cours",
"utils_extracted": "Extracted: {0}",
"utils_corrupt_zip": "Archive ZIP corrompue: {0}",
"utils_permission_denied": "Permission refusée lors de l'extraction: {0}",
@@ -163,6 +185,7 @@
,"instruction_pause_games": "Historique, source de liste ou rafraîchissement"
,"instruction_pause_settings": "Musique, option symlink & statut des clés API"
,"instruction_pause_restart": "Redémarrer RGSX pour recharger la configuration"
,"instruction_pause_support": "Générer un fichier ZIP de diagnostic pour l'assistance"
,"instruction_pause_quit": "Quitter l'application RGSX"
,"instruction_controls_help": "Afficher la référence complète manette & clavier"
,"instruction_controls_remap": "Modifier l'association boutons / touches"
@@ -180,6 +203,15 @@
,"instruction_settings_music": "Activer ou désactiver la lecture musicale"
,"instruction_settings_symlink": "Basculer l'utilisation de symlinks pour l'installation"
,"instruction_settings_api_keys": "Voir les clés API détectées des services premium"
,"instruction_settings_web_service": "Activer/désactiver le démarrage automatique du service web"
,"settings_web_service": "Service Web au démarrage"
,"settings_web_service_enabled": "Activé"
,"settings_web_service_disabled": "Désactivé"
,"settings_web_service_enabling": "Activation du service web..."
,"settings_web_service_disabling": "Désactivation du service web..."
,"settings_web_service_success_enabled": "Service web activé au démarrage"
,"settings_web_service_success_disabled": "Service web désactivé au démarrage"
,"settings_web_service_error": "Erreur : {0}"
,"controls_desc_confirm": "Valider (ex: A/Croix)"
,"controls_desc_cancel": "Annuler/Retour (ex: B/Rond)"
,"controls_desc_up": "UP ↑"
@@ -200,4 +232,125 @@
,"controls_mapping_press": "Appuyez sur une touche ou un bouton"
,"status_already_present": "Déjà Présent"
,"footer_joystick": "Joystick : {0}"
,"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_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_back": "Retour"
,"history_folder_path_label": "Chemin de destination :"
,"history_scraper_not_implemented": "Scraper pas encore implémenté"
,"history_confirm_delete": "Supprimer ce jeu du disque ?"
,"history_file_not_found": "Fichier introuvable"
,"history_extracting": "Extraction en cours..."
,"history_extracted": "Extrait"
,"history_delete_success": "Jeu supprimé avec succès"
,"history_delete_error": "Erreur lors de la suppression du jeu : {0}"
,"history_error_details_title": "Détails de l'erreur"
,"history_no_error_message": "Aucun message d'erreur disponible"
,"web_title": "Interface Web RGSX"
,"web_tab_platforms": "Liste des systèmes"
,"web_tab_downloads": "Téléchargements"
,"web_tab_history": "Historique"
,"web_tab_settings": "Paramètres"
,"web_tab_update": "Mettre à jour la liste"
,"web_tooltip_platforms": "Liste des systèmes"
,"web_tooltip_downloads": "Téléchargements"
,"web_tooltip_history": "Historique"
,"web_tooltip_settings": "Paramètres"
,"web_tooltip_update": "Mettre à jour la liste des jeux"
,"web_search_platform": "Rechercher des systèmes ou jeux..."
,"web_search_game": "Rechercher un jeu..."
,"web_search_results": "résultats pour"
,"web_no_results": "Aucun résultat trouvé"
,"web_platforms": "Systèmes"
,"web_games": "Jeux"
,"web_error_search": "Erreur de recherche"
,"web_back_platforms": "Retour aux plateformes"
,"web_back": "Retour"
,"web_game_count": "{0} ({1} jeux)"
,"web_download": "Télécharger"
,"web_cancel": "Annuler"
,"web_download_canceled": "Téléchargement annulé"
,"web_confirm_cancel": "Voulez-vous vraiment annuler ce téléchargement ?"
,"web_update_title": "Mise à jour de la liste des jeux..."
,"web_update_message": "Nettoyage du cache et rechargement des données..."
,"web_update_wait": "Cela peut prendre 10-30 secondes"
,"web_error": "Erreur"
,"web_error_unknown": "Erreur inconnue"
,"web_error_update": "Erreur lors de la mise à jour de la liste : {0}"
,"web_error_download": "Erreur : {0}"
,"web_history_clear": "Vider l'historique"
,"web_history_cleared": "Historique vidé avec succès !"
,"web_error_clear_history": "Erreur lors du vidage de l'historique : {0}"
,"web_settings_title": "Informations & Paramètres"
,"web_settings_roms_folder": "Dossier ROMs personnalisé"
,"web_settings_roms_placeholder": "Laisser vide pour le dossier par défaut"
,"web_settings_browse": "Parcourir"
,"web_settings_language": "Langue"
,"web_settings_font_scale": "Échelle de police"
,"web_settings_grid": "Grille d'affichage"
,"web_settings_font_family": "Police de caractères"
,"web_settings_music": "Musique"
,"web_settings_symlink": "Mode symlink"
,"web_settings_source_mode": "Source des jeux"
,"web_settings_custom_url": "URL personnalisée"
,"web_settings_custom_url_placeholder": "Laisser vide pour /saves/ports/rgsx/games.zip ou utiliser une URL directe comme https://exemple.com/jeux.zip"
,"web_settings_save": "Enregistrer les paramètres"
,"web_settings_saved": "Paramètres enregistrés avec succès !"
,"web_settings_saved_restart": "Paramètres enregistrés avec succès !\\n\\n⚠ Certains paramètres nécessitent un redémarrage du serveur :\\n- Dossier ROMs personnalisé\\n- Langue\\n\\nVeuillez redémarrer le serveur web pour appliquer ces changements."
,"web_error_save_settings": "Erreur lors de l'enregistrement : {0}"
,"web_browse_title": "Parcourir les dossiers"
,"web_browse_select_drive": "Sélectionnez un lecteur..."
,"web_browse_drives": "Lecteurs"
,"web_browse_parent": "Parent"
,"web_browse_select": "Sélectionner ce dossier"
,"web_browse_cancel": "Annuler"
,"web_browse_empty": "Aucun sous-dossier trouvé"
,"web_browse_alert_restart": "Important : Vous devez ENREGISTRER les paramètres puis REDÉMARRER le serveur web pour que le dossier ROMs personnalisé soit pris en compte.\\n\\n📝 Étapes :\\n1. Cliquez sur 'Enregistrer les paramètres' ci-dessous\\n2. Arrêtez le serveur web (Ctrl+C dans le terminal)\\n3. Redémarrez le serveur web\\n\\nChemin sélectionné : {0}"
,"web_error_browse": "Erreur lors de la navigation : {0}"
,"web_loading_platforms": "Chargement des plateformes..."
,"web_loading_games": "Chargement des jeux..."
,"web_no_platforms": "Aucune plateforme trouvée"
,"web_no_downloads": "Aucun téléchargement en cours"
,"web_history_empty": "Aucun téléchargement terminé"
,"web_history_platform": "Plateforme"
,"web_history_size": "Taille"
,"web_history_status_completed": "Terminé"
,"web_history_status_error": "Erreur"
,"web_settings_os": "Système d'exploitation"
,"web_settings_platforms_count": "Nombre de plateformes"
,"web_settings_show_unsupported": "Afficher les systèmes non supportés (absents de es_systems.cfg)"
,"web_settings_allow_unknown": "Autoriser les extensions inconnues (ne pas afficher d'avertissement)"
,"web_restart_confirm_title": "Redémarrer l'application ?"
,"web_restart_confirm_message": "Les paramètres ont été enregistrés. Voulez-vous redémarrer l'application maintenant pour appliquer les changements ?"
,"web_restart_yes": "Oui, redémarrer"
,"web_restart_no": "Non, plus tard"
,"web_restart_success": "Redémarrage en cours..."
,"web_restart_error": "Erreur lors du redémarrage : {0}"
,"web_support": "Support"
,"web_support_title": "📦 Fichier de support généré"
,"web_support_message": "Le fichier de support a été créé avec succès !\\n\\n📁 Contenu :\\n• Configuration des contrôles\\n• Historique des téléchargements\\n• Paramètres RGSX\\n• Logs de l'application\\n• Logs du serveur web\\n\\n💬 Pour obtenir de l'aide :\\n1. Rejoignez le Discord RGSX\\n2. Décrivez votre problème\\n3. Partagez ce fichier ZIP\\n\\nLe téléchargement va démarrer..."
,"web_support_generating": "Génération du fichier de support..."
,"web_support_download": "Télécharger le fichier de support"
,"web_support_error": "Erreur lors de la génération du fichier de support : {0}"
,"web_tab_queue": "File d'attente"
,"web_tooltip_queue": "File d'attente des téléchargements"
,"web_queue_active_download": "⏳ Un téléchargement est actuellement en cours"
,"web_queue_no_active": "✓ Aucun téléchargement actif"
,"web_queue_title": "File d'attente des téléchargements"
,"web_queue_empty": "Aucun élément en attente"
,"web_queue_clear": "Vider la file d'attente"
,"web_queue_cleared": "File d'attente vidée avec succès !"
,"web_confirm_remove_queue": "Supprimer cet élément de la file d'attente ?"
,"web_confirm_clear_queue": "Vider toute la file d'attente ?"
,"web_remove": "Supprimer"
,"web_loading": "Chargement..."
,"web_sort": "Trier par"
,"web_sort_name_asc": "A-Z (Nom)"
,"web_sort_name_desc": "Z-A (Nom)"
,"web_sort_size_asc": "Taille +- (Petit d'abord)"
,"web_sort_size_desc": "Taille -+ (Grand d'abord)"
}

View File

@@ -37,6 +37,11 @@
"history_status_completed": "Completato",
"history_status_error": "Errore: {0}",
"history_status_canceled": "Annullato",
"free_mode_waiting": "[Modalità gratuita] Attesa: {0}/{1}s",
"free_mode_download": "[Modalità gratuita] Download: {0}",
"free_mode_submitting": "[Modalità gratuita] Invio modulo...",
"free_mode_link_found": "[Modalità gratuita] Link trovato: {0}...",
"free_mode_completed": "[Modalità gratuita] Completato: {0}",
"download_status": "{0}: {1}",
"download_canceled": "Download annullato dall'utente.",
"extension_warning_zip": "Il file '{0}' è un archivio e Batocera non supporta archivi per questo sistema. L'estrazione automatica avverrà dopo il download, continuare?",
@@ -63,6 +68,7 @@
"menu_music_enabled": "Musica attivata: {0}",
"menu_music_disabled": "Musica disattivata",
"menu_restart": "Riavvia",
"menu_support": "Supporto",
"menu_filter_platforms": "Filtra sistemi",
"filter_platforms_title": "Visibilità sistemi",
"filter_platforms_info": "Visibili: {0} | Nascosti: {1} / Totale: {2}",
@@ -74,15 +80,20 @@
"menu_allow_unknown_ext_enabled": "Nascondi avviso estensione sconosciuta abilitato",
"menu_allow_unknown_ext_disabled": "Nascondi avviso estensione sconosciuta disabilitato",
"menu_quit": "Esci",
"support_dialog_title": "File di supporto",
"support_dialog_message": "È stato creato un file di supporto con tutti i file di configurazione e di registro.\n\nFile: {0}\n\nPer ottenere aiuto:\n1. Unisciti al server Discord RGSX\n2. Descrivi il tuo problema\n3. Condividi questo file ZIP\n\nPremi {1} per tornare al menu.",
"support_dialog_error": "Errore durante la generazione del file di supporto:\n{0}\n\nPremi {1} per tornare al menu.",
"button_yes": "Sì",
"button_no": "No",
"button_OK": "OK",
"popup_restarting": "Riavvio...",
"controls_action_clear_history": "Selezione multipla / Cancella cronologia",
"controls_action_history": "Cronologia",
"controls_action_clear_history": "Cancella cronologia",
"controls_action_history": "Cronologia / Downloads",
"controls_action_close_history": "Chiudi Cronologia",
"controls_action_delete": "Elimina",
"controls_action_space": "Spazio",
"controls_action_start": "Aiuto / Impostazioni",
"controls_action_queue": "Metti in coda",
"network_checking_updates": "Verifica aggiornamenti...",
"network_update_available": "Aggiornamento disponibile: {0}",
"network_extracting_update": "Estrazione aggiornamento...",
@@ -92,7 +103,7 @@
"network_no_update_available": "Nessun aggiornamento disponibile",
"network_update_error": "Errore durante l'aggiornamento: {0}",
"network_check_update_error": "Errore durante il controllo degli aggiornamenti: {0}",
"network_extraction_failed": "Impossibile estrarre l'aggiornamento: {0}",
"network_extraction_failed": "Impossibile estrarre: {0}",
"network_extraction_partial": "Estrazione riuscita, ma alcuni file sono stati ignorati a causa di errori: {0}",
"network_extraction_success": "Estrazione riuscita",
"network_download_extract_ok": "Download ed estrazione riusciti di {0}",
@@ -103,16 +114,20 @@
"network_download_failed": "Download fallito dopo {0} tentativi",
"network_api_error": "Errore richiesta API, la chiave potrebbe essere errata: {0}",
"network_download_error": "Errore download {0}: {1}",
"network_connection_failed": "Connessione fallita dopo {0} tentativi",
"network_http_error": "Errore HTTP {0}",
"network_timeout_error": "Timeout di connessione",
"network_connection_error": "Errore di connessione di rete",
"network_no_response": "Nessuna risposta dal server",
"network_auth_required": "Autenticazione richiesta (HTTP {0})",
"network_access_denied": "Accesso negato (HTTP {0})",
"network_server_error": "Errore del server (HTTP {0})",
"network_download_ok": "Download OK: {0}",
"download_already_present": " (già presente)",
"download_already_extracted": " (già estratto)",
"utils_extracted": "Estratto: {0}",
"utils_corrupt_zip": "Archivio ZIP corrotto: {0}",
"utils_permission_denied": "Permesso negato durante l'estrazione: {0}",
"utils_extraction_failed": "Estrazione fallita: {0}",
"utils_unrar_unavailable": "Comando unrar non disponibile",
"utils_rar_list_failed": "Impossibile elencare i file RAR: {0}",
"download_initializing": "Inizializzazione...",
"download_in_progress": "Download in corso...",
"download_queued": "In coda di download",
"download_started": "Download iniziato",
"accessibility_font_size": "Dimensione carattere: {0}",
"confirm_cancel_download": "Annullare il download corrente?",
"controls_help_title": "Guida ai controlli",
@@ -163,6 +178,7 @@
,"instruction_pause_games": "Aprire cronologia, cambiare sorgente o aggiornare elenco"
,"instruction_pause_settings": "Musica, opzione symlink e stato chiavi API"
,"instruction_pause_restart": "Riavvia RGSX per ricaricare la configurazione"
,"instruction_pause_support": "Genera un file ZIP diagnostico per il supporto"
,"instruction_pause_quit": "Uscire dall'applicazione RGSX"
,"instruction_controls_help": "Mostrare riferimento completo controller & tastiera"
,"instruction_controls_remap": "Modificare associazione pulsanti / tasti"
@@ -180,6 +196,15 @@
,"instruction_settings_music": "Abilitare o disabilitare musica di sottofondo"
,"instruction_settings_symlink": "Abilitare/disabilitare uso symlink per installazioni"
,"instruction_settings_api_keys": "Mostrare chiavi API premium rilevate"
,"instruction_settings_web_service": "Attivare/disattivare avvio automatico servizio web all'avvio"
,"settings_web_service": "Servizio Web all'Avvio"
,"settings_web_service_enabled": "Abilitato"
,"settings_web_service_disabled": "Disabilitato"
,"settings_web_service_enabling": "Abilitazione servizio web..."
,"settings_web_service_disabling": "Disabilitazione servizio web..."
,"settings_web_service_success_enabled": "Servizio web abilitato all'avvio"
,"settings_web_service_success_disabled": "Servizio web disabilitato all'avvio"
,"settings_web_service_error": "Errore: {0}"
,"controls_desc_confirm": "Confermare (es. A/Croce)"
,"controls_desc_cancel": "Annullare/Indietro (es. B/Cerchio)"
,"controls_desc_up": "UP ↑"
@@ -200,4 +225,125 @@
,"controls_mapping_press": "Premi un tasto o un pulsante"
,"status_already_present": "Già Presente"
,"footer_joystick": "Joystick: {0}"
,"history_game_options_title": "Opzioni gioco"
,"history_option_download_folder": "Localizza file"
,"history_option_extract_archive": "Estrai archivio"
,"history_option_scraper": "Scraper metadati"
,"history_option_delete_game": "Elimina gioco"
,"history_option_error_info": "Dettagli errore"
,"history_option_retry": "Riprova download"
,"history_option_back": "Indietro"
,"history_folder_path_label": "Percorso destinazione:"
,"history_scraper_not_implemented": "Scraper non ancora implementato"
,"history_confirm_delete": "Eliminare questo gioco dal disco?"
,"history_file_not_found": "File non trovato"
,"history_extracting": "Estrazione in corso..."
,"history_extracted": "Estratto"
,"history_delete_success": "Gioco eliminato con successo"
,"history_delete_error": "Errore durante l'eliminazione del gioco: {0}"
,"history_error_details_title": "Dettagli errore"
,"history_no_error_message": "Nessun messaggio di errore disponibile"
,"web_title": "Interfaccia Web RGSX"
,"web_tab_platforms": "Elenco sistemi"
,"web_tab_downloads": "Download"
,"web_tab_history": "Cronologia"
,"web_tab_settings": "Impostazioni"
,"web_tab_update": "Aggiorna elenco"
,"web_tooltip_platforms": "Elenco sistemi"
,"web_tooltip_downloads": "Download"
,"web_tooltip_history": "Cronologia"
,"web_tooltip_settings": "Impostazioni"
,"web_tooltip_update": "Aggiorna elenco giochi"
,"web_search_platform": "Cerca sistemi o giochi..."
,"web_search_game": "Cerca un gioco..."
,"web_search_results": "risultati per"
,"web_no_results": "Nessun risultato trovato"
,"web_platforms": "Sistemi"
,"web_games": "Giochi"
,"web_error_search": "Errore di ricerca"
,"web_back_platforms": "Torna alle piattaforme"
,"web_back": "Indietro"
,"web_game_count": "{0} ({1} giochi)"
,"web_download": "Scarica"
,"web_cancel": "Annulla"
,"web_download_canceled": "Download annullato"
,"web_confirm_cancel": "Vuoi davvero annullare questo download?"
,"web_update_title": "Aggiornamento elenco giochi..."
,"web_update_message": "Pulizia cache e ricaricamento dati..."
,"web_update_wait": "Potrebbe richiedere 10-30 secondi"
,"web_error": "Errore"
,"web_error_unknown": "Errore sconosciuto"
,"web_error_update": "Errore durante l'aggiornamento dell'elenco: {0}"
,"web_error_download": "Errore: {0}"
,"web_history_clear": "Cancella cronologia"
,"web_history_cleared": "Cronologia cancellata con successo!"
,"web_error_clear_history": "Errore durante la cancellazione della cronologia: {0}"
,"web_settings_title": "Info e Impostazioni"
,"web_settings_roms_folder": "Cartella ROMs personalizzata"
,"web_settings_roms_placeholder": "Lasciare vuoto per predefinito"
,"web_settings_browse": "Sfoglia"
,"web_settings_language": "Lingua"
,"web_settings_font_scale": "Scala carattere"
,"web_settings_grid": "Layout griglia"
,"web_settings_font_family": "Famiglia carattere"
,"web_settings_music": "Musica"
,"web_settings_symlink": "Modalità symlink"
,"web_settings_source_mode": "Fonte giochi"
,"web_settings_custom_url": "URL personalizzato"
,"web_settings_custom_url_placeholder": " Lasciare vuoto per /saves/ports/rgsx/games.zip o usare una URL diretta come https://esempio.com/giochi.zip"
,"web_settings_save": "Salva impostazioni"
,"web_settings_saved": "Impostazioni salvate con successo!"
,"web_settings_saved_restart": "Impostazioni salvate con successo!\\n\\n⚠ Alcune impostazioni richiedono il riavvio del server:\\n- Cartella ROMs personalizzata\\n- Lingua\\n\\nRiavviare il server web per applicare queste modifiche."
,"web_error_save_settings": "Errore durante il salvataggio delle impostazioni: {0}"
,"web_browse_title": "Sfoglia directory"
,"web_browse_select_drive": "Seleziona un'unità..."
,"web_browse_drives": "Unità"
,"web_browse_parent": "Superiore"
,"web_browse_select": "Seleziona questa cartella"
,"web_browse_cancel": "Annulla"
,"web_browse_empty": "Nessuna sottodirectory trovata"
,"web_browse_alert_restart": "Importante: È necessario SALVARE le impostazioni e poi RIAVVIARE il server web affinché la cartella ROMs personalizzata abbia effetto.\\n\\n📝 Passaggi:\\n1. Fare clic su 'Salva impostazioni' qui sotto\\n2. Arrestare il server web (Ctrl+C nel terminale)\\n3. Riavviare il server web\\n\\nPercorso selezionato: {0}"
,"web_error_browse": "Errore durante la navigazione delle directory: {0}"
,"web_loading_platforms": "Caricamento piattaforme..."
,"web_loading_games": "Caricamento giochi..."
,"web_no_platforms": "Nessuna piattaforma trovata"
,"web_no_downloads": "Nessun download in corso"
,"web_history_empty": "Nessun download completato"
,"web_history_platform": "Piattaforma"
,"web_history_size": "Dimensione"
,"web_history_status_completed": "Completato"
,"web_history_status_error": "Errore"
,"web_settings_os": "Sistema operativo"
,"web_settings_platforms_count": "Numero di piattaforme"
,"web_settings_show_unsupported": "Mostra piattaforme non supportate (sistema assente in es_systems.cfg)"
,"web_settings_allow_unknown": "Consenti estensioni sconosciute (non mostrare avvisi)"
,"web_restart_confirm_title": "Riavviare l'applicazione?"
,"web_restart_confirm_message": "Le impostazioni sono state salvate. Vuoi riavviare l'applicazione ora per applicare le modifiche?"
,"web_restart_yes": "Sì, riavvia"
,"web_restart_no": "No, più tardi"
,"web_restart_success": "Riavvio in corso..."
,"web_restart_error": "Errore durante il riavvio: {0}"
,"web_support": "Supporto"
,"web_support_title": "📦 File di supporto generato"
,"web_support_message": "File di supporto creato con successo!\\n\\n📁 Contenuto:\\n• Configurazione controlli\\n• Cronologia download\\n• Impostazioni RGSX\\n• Log dell'applicazione\\n• Log del server web\\n\\n💬 Per ottenere aiuto:\\n1. Unisciti al Discord RGSX\\n2. Descrivi il tuo problema\\n3. Condividi questo file ZIP\\n\\nIl download inizierà..."
,"web_support_generating": "Generazione file di supporto..."
,"web_support_download": "Scarica file di supporto"
,"web_support_error": "Errore nella generazione del file di supporto: {0}"
,"web_tab_queue": "Coda"
,"web_tooltip_queue": "Coda di download"
,"web_queue_active_download": "⏳ Un download è attivo"
,"web_queue_no_active": "✓ Nessun download attivo"
,"web_queue_title": "Coda di Download"
,"web_queue_empty": "Nessun elemento in coda"
,"web_queue_clear": "Svuota coda"
,"web_queue_cleared": "Coda svuotata con successo!"
,"web_confirm_remove_queue": "Rimuovere questo elemento dalla coda?"
,"web_confirm_clear_queue": "Svuotare l'intera coda?"
,"web_remove": "Rimuovi"
,"web_loading": "Caricamento..."
,"web_sort": "Ordina per"
,"web_sort_name_asc": "A-Z (Nome)"
,"web_sort_name_desc": "Z-A (Nome)"
,"web_sort_size_asc": "Dimensione +- (Piccolo primo)"
,"web_sort_size_desc": "Dimensione -+ (Grande primo)"
}

View File

@@ -37,6 +37,11 @@
"history_status_completed": "Concluído",
"history_status_error": "Erro: {0}",
"history_status_canceled": "Cancelado",
"free_mode_waiting": "[Modo gratuito] Aguardando: {0}/{1}s",
"free_mode_download": "[Modo gratuito] Baixando: {0}",
"free_mode_submitting": "[Modo gratuito] Enviando formulário...",
"free_mode_link_found": "[Modo gratuito] Link encontrado: {0}...",
"free_mode_completed": "[Modo gratuito] Concluído: {0}",
"download_status": "{0}: {1}",
"download_canceled": "Download cancelado pelo usuário.",
"extension_warning_zip": "O arquivo '{0}' é um arquivo compactado e o Batocera não suporta arquivos compactados para este sistema. A extração automática ocorrerá após o download, continuar?",
@@ -63,6 +68,7 @@
"menu_music_enabled": "Música ativada: {0}",
"menu_music_disabled": "Música desativada",
"menu_restart": "Reiniciar",
"menu_support": "Suporte",
"menu_filter_platforms": "Filtrar sistemas",
"filter_platforms_title": "Visibilidade dos sistemas",
"filter_platforms_info": "Visíveis: {0} | Ocultos: {1} / Total: {2}",
@@ -74,15 +80,20 @@
"menu_allow_unknown_ext_enabled": "Aviso de extensão desconhecida oculto (ativado)",
"menu_allow_unknown_ext_disabled": "Aviso de extensão desconhecida visível (desativado)",
"menu_quit": "Sair",
"support_dialog_title": "Arquivo de suporte",
"support_dialog_message": "Foi criado um arquivo de suporte com todos os seus arquivos de configuração e logs.\n\nArquivo: {0}\n\nPara obter ajuda:\n1. Junte-se ao servidor Discord RGSX\n2. Descreva seu problema\n3. Compartilhe este arquivo ZIP\n\nPressione {1} para voltar ao menu.",
"support_dialog_error": "Erro ao gerar o arquivo de suporte:\n{0}\n\nPressione {1} para voltar ao menu.",
"button_yes": "Sim",
"button_no": "Não",
"button_OK": "OK",
"popup_restarting": "Reiniciando...",
"controls_action_clear_history": "Seleção múltipla / Limpar histórico",
"controls_action_history": "Histórico",
"controls_action_clear_history": "Limpar histórico",
"controls_action_history": "Histórico / Downloads",
"controls_action_close_history": "Fechar Histórico",
"controls_action_delete": "Deletar",
"controls_action_space": "Espaço",
"controls_action_start": "Ajuda / Configurações",
"controls_action_queue": "Adicionar à fila",
"network_checking_updates": "Verificando atualizações...",
"network_update_available": "Atualização disponível: {0}",
"network_extracting_update": "Extraindo atualização...",
@@ -92,7 +103,7 @@
"network_no_update_available": "Nenhuma atualização disponível",
"network_update_error": "Erro durante atualização: {0}",
"network_check_update_error": "Erro ao verificar atualizações: {0}",
"network_extraction_failed": "Falha ao extrair atualização: {0}",
"network_extraction_failed": "Falha ao extrair: {0}",
"network_extraction_partial": "Extração concluída, mas alguns arquivos foram ignorados devido a erros: {0}",
"network_extraction_success": "Extração concluída",
"network_download_extract_ok": "Download e extração concluídos de {0}",
@@ -103,16 +114,20 @@
"network_download_failed": "Download falhou após {0} tentativas",
"network_api_error": "Erro na requisição da API, a chave pode estar incorreta: {0}",
"network_download_error": "Erro de download {0}: {1}",
"network_connection_failed": "Conexão falhou após {0} tentativas",
"network_http_error": "Erro HTTP {0}",
"network_timeout_error": "Tempo limite de conexão esgotado",
"network_connection_error": "Erro de conexão de rede",
"network_no_response": "Sem resposta do servidor",
"network_auth_required": "Autenticação necessária (HTTP {0})",
"network_access_denied": "Acesso negado (HTTP {0})",
"network_server_error": "Erro do servidor (HTTP {0})",
"network_download_ok": "Download OK: {0}",
"download_already_present": " (já presente)",
"download_already_extracted": " (já extraído)",
"utils_extracted": "Extraído: {0}",
"utils_corrupt_zip": "Arquivo ZIP corrompido: {0}",
"utils_permission_denied": "Permissão negada durante extração: {0}",
"utils_extraction_failed": "Extração falhou: {0}",
"utils_unrar_unavailable": "Comando unrar não disponível",
"utils_rar_list_failed": "Falha ao listar arquivos RAR: {0}",
"download_initializing": "Inicializando...",
"download_in_progress": "Download em andamento...",
"download_queued": "Na fila de download",
"download_started": "Download iniciado",
"accessibility_font_size": "Tamanho da fonte: {0}",
"confirm_cancel_download": "Cancelar download atual?",
"controls_help_title": "Ajuda de Controles",
@@ -163,6 +178,7 @@
,"instruction_pause_games": "Abrir histórico, mudar fonte ou atualizar lista"
,"instruction_pause_settings": "Música, opção symlink e status das chaves API"
,"instruction_pause_restart": "Reiniciar RGSX para recarregar configuração"
,"instruction_pause_support": "Gerar um arquivo ZIP de diagnóstico para suporte"
,"instruction_pause_quit": "Sair da aplicação RGSX"
,"instruction_controls_help": "Mostrar referência completa de controle e teclado"
,"instruction_controls_remap": "Modificar associação de botões / teclas"
@@ -180,6 +196,15 @@
,"instruction_settings_music": "Ativar ou desativar música de fundo"
,"instruction_settings_symlink": "Ativar/desativar uso de symlinks para instalações"
,"instruction_settings_api_keys": "Ver chaves API premium detectadas"
,"instruction_settings_web_service": "Ativar/desativar início automático do serviço web na inicialização"
,"settings_web_service": "Serviço Web na Inicialização"
,"settings_web_service_enabled": "Ativado"
,"settings_web_service_disabled": "Desativado"
,"settings_web_service_enabling": "Ativando serviço web..."
,"settings_web_service_disabling": "Desativando serviço web..."
,"settings_web_service_success_enabled": "Serviço web ativado na inicialização"
,"settings_web_service_success_disabled": "Serviço web desativado na inicialização"
,"settings_web_service_error": "Erro: {0}"
,"controls_desc_confirm": "Confirmar (ex. A/Cruz)"
,"controls_desc_cancel": "Cancelar/Voltar (ex. B/Círculo)"
,"controls_desc_up": "UP ↑"
@@ -200,4 +225,125 @@
,"controls_mapping_press": "Pressione uma tecla ou um botão"
,"status_already_present": "Já Presente"
,"footer_joystick": "Joystick: {0}"
,"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_delete_game": "Excluir jogo"
,"history_option_error_info": "Detalhes do erro"
,"history_option_retry": "Tentar novamente"
,"history_option_back": "Voltar"
,"history_folder_path_label": "Caminho de destino:"
,"history_scraper_not_implemented": "Scraper ainda não implementado"
,"history_confirm_delete": "Excluir este jogo do disco?"
,"history_file_not_found": "Arquivo não encontrado"
,"history_extracting": "Extraindo..."
,"history_extracted": "Extraído"
,"history_delete_success": "Jogo excluído com sucesso"
,"history_delete_error": "Erro ao excluir jogo: {0}"
,"history_error_details_title": "Detalhes do erro"
,"history_no_error_message": "Nenhuma mensagem de erro disponível"
,"web_title": "Interface Web RGSX"
,"web_tab_platforms": "Lista de sistemas"
,"web_tab_downloads": "Downloads"
,"web_tab_history": "Histórico"
,"web_tab_settings": "Configurações"
,"web_tab_update": "Atualizar lista"
,"web_tooltip_platforms": "Lista de sistemas"
,"web_tooltip_downloads": "Downloads"
,"web_tooltip_history": "Histórico"
,"web_tooltip_settings": "Configurações"
,"web_tooltip_update": "Atualizar lista de jogos"
,"web_search_platform": "Pesquisar sistemas ou jogos..."
,"web_search_game": "Pesquisar um jogo..."
,"web_search_results": "resultados para"
,"web_no_results": "Nenhum resultado encontrado"
,"web_platforms": "Sistemas"
,"web_games": "Jogos"
,"web_error_search": "Erro de pesquisa"
,"web_back_platforms": "Voltar às plataformas"
,"web_back": "Voltar"
,"web_game_count": "{0} ({1} jogos)"
,"web_download": "Baixar"
,"web_cancel": "Cancelar"
,"web_download_canceled": "Download cancelado"
,"web_confirm_cancel": "Você realmente deseja cancelar este download?"
,"web_update_title": "Atualizando lista de jogos..."
,"web_update_message": "Limpando cache e recarregando dados..."
,"web_update_wait": "Isso pode levar 10-30 segundos"
,"web_error": "Erro"
,"web_error_unknown": "Erro desconhecido"
,"web_error_update": "Erro ao atualizar a lista: {0}"
,"web_error_download": "Erro: {0}"
,"web_history_clear": "Limpar histórico"
,"web_history_cleared": "Histórico limpo com sucesso!"
,"web_error_clear_history": "Erro ao limpar histórico: {0}"
,"web_settings_title": "Informações e Configurações"
,"web_settings_roms_folder": "Pasta ROMs personalizada"
,"web_settings_roms_placeholder": "Deixar vazio para padrão"
,"web_settings_browse": "Procurar"
,"web_settings_language": "Idioma"
,"web_settings_font_scale": "Escala de fonte"
,"web_settings_grid": "Layout de grade"
,"web_settings_font_family": "Família de fonte"
,"web_settings_music": "Música"
,"web_settings_symlink": "Modo symlink"
,"web_settings_source_mode": "Fonte de jogos"
,"web_settings_custom_url": "URL personalizada"
,"web_settings_custom_url_placeholder": "Deixar vazio para /saves/ports/rgsx/games.zip ou usar uma URL direta como https://example.com/games.zip"
,"web_settings_save": "Salvar configurações"
,"web_settings_saved": "Configurações salvas com sucesso!"
,"web_settings_saved_restart": "Configurações salvas com sucesso!\\n\\n⚠ Algumas configurações exigem reiniciar o servidor:\\n- Pasta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie o servidor web para aplicar essas alterações."
,"web_error_save_settings": "Erro ao salvar configurações: {0}"
,"web_browse_title": "Procurar diretórios"
,"web_browse_select_drive": "Selecione uma unidade..."
,"web_browse_drives": "Unidades"
,"web_browse_parent": "Acima"
,"web_browse_select": "Selecionar esta pasta"
,"web_browse_cancel": "Cancelar"
,"web_browse_empty": "Nenhum subdiretório encontrado"
,"web_browse_alert_restart": "Importante: Você precisa SALVAR as configurações e então REINICIAR o servidor web para que a pasta ROMs personalizada tenha efeito.\\n\\n📝 Passos:\\n1. Clique em 'Salvar configurações' abaixo\\n2. Pare o servidor web (Ctrl+C no terminal)\\n3. Reinicie o servidor web\\n\\nCaminho selecionado: {0}"
,"web_error_browse": "Erro ao procurar diretórios: {0}"
,"web_loading_platforms": "Carregando plataformas..."
,"web_loading_games": "Carregando jogos..."
,"web_no_platforms": "Nenhuma plataforma encontrada"
,"web_no_downloads": "Nenhum download em andamento"
,"web_history_empty": "Nenhum download concluído"
,"web_history_platform": "Plataforma"
,"web_history_size": "Tamanho"
,"web_history_status_completed": "Concluído"
,"web_history_status_error": "Erro"
,"web_settings_os": "Sistema operacional"
,"web_settings_platforms_count": "Número de plataformas"
,"web_settings_show_unsupported": "Mostrar plataformas não suportadas (sistema ausente em es_systems.cfg)"
,"web_settings_allow_unknown": "Permitir extensões desconhecidas (não mostrar avisos)"
,"web_restart_confirm_title": "Reiniciar aplicação?"
,"web_restart_confirm_message": "As configurações foram salvas. Deseja reiniciar a aplicação agora para aplicar as alterações?"
,"web_restart_yes": "Sim, reiniciar"
,"web_restart_no": "Não, mais tarde"
,"web_restart_success": "Reiniciando..."
,"web_restart_error": "Erro ao reiniciar: {0}"
,"web_support": "Suporte"
,"web_support_title": "📦 Arquivo de suporte gerado"
,"web_support_message": "Arquivo de suporte criado com sucesso!\\n\\n📁 Conteúdo:\\n• Configuração de controles\\n• Histórico de downloads\\n• Configurações RGSX\\n• Logs da aplicação\\n• Logs do servidor web\\n\\n💬 Para obter ajuda:\\n1. Entre no Discord RGSX\\n2. Descreva seu problema\\n3. Compartilhe este arquivo ZIP\\n\\nO download vai começar..."
,"web_support_generating": "Gerando arquivo de suporte..."
,"web_support_download": "Baixar arquivo de suporte"
,"web_support_error": "Erro ao gerar arquivo de suporte: {0}"
,"web_tab_queue": "Fila"
,"web_tooltip_queue": "Fila de downloads"
,"web_queue_active_download": "⏳ Um download está ativo"
,"web_queue_no_active": "✓ Sem downloads ativos"
,"web_queue_title": "Fila de Downloads"
,"web_queue_empty": "Nenhum item na fila"
,"web_queue_clear": "Limpar fila"
,"web_queue_cleared": "Fila limpa com sucesso!"
,"web_confirm_remove_queue": "Remover este item da fila?"
,"web_confirm_clear_queue": "Limpar toda a fila?"
,"web_remove": "Remover"
,"web_loading": "Carregando..."
,"web_sort": "Ordenar por"
,"web_sort_name_asc": "A-Z (Nome)"
,"web_sort_name_desc": "Z-A (Nome)"
,"web_sort_size_asc": "Tamanho +- (Menor primeiro)"
,"web_sort_size_desc": "Tamanho -+ (Maior primeiro)"
}

File diff suppressed because it is too large Load Diff

View File

@@ -103,7 +103,7 @@ def ensure_data_present(verbose: bool = False):
headers = {"User-Agent": "Mozilla/5.0"}
# Always show progress when we're in the 'missing data' path
show = True or verbose
print("Source data not found, downloading...")
print("Source data not found, Downloading...")
print(f"Downloading data from {url}...")
try:
with requests.get(url, stream=True, headers=headers, timeout=60) as r:
@@ -394,7 +394,7 @@ async def _run_download_with_progress(url: str, platform_name: str, game_name: s
try:
if isinstance(config.history, list):
for e in config.history:
if e.get('url') == url and e.get('status') in ("downloading", "Téléchargement", "Extracting"):
if e.get('url') == url and e.get('status') in ("Downloading", "Téléchargement", "Extracting"):
downloaded = int(e.get('downloaded_size') or 0)
total = int(e.get('total_size') or 0)
speed = e.get('speed')
@@ -646,7 +646,7 @@ def cmd_download(args):
hist.append({
"platform": platform.get('platform_name') or platform.get('platform') or args.platform,
"game_name": title,
"status": "downloading",
"status": "Downloading",
"url": url,
"progress": 0,
"message": "Téléchargement en cours",

View File

@@ -68,7 +68,9 @@ def load_rgsx_settings():
"custom_url": ""
},
"show_unsupported_platforms": False,
"allow_unknown_extensions": False
"allow_unknown_extensions": False,
"roms_folder": "",
"web_service_at_boot": False
}
try:
@@ -315,3 +317,24 @@ def set_font_family(family: str):
disp["font_family"] = family
save_rgsx_settings(settings)
return family
# ----------------------- ROMs folder (custom path) ----------------------- #
def get_roms_folder(settings=None):
"""Retourne le chemin du dossier ROMs personnalisé (ou chaîne vide si par défaut)."""
if settings is None:
settings = load_rgsx_settings()
return settings.get("roms_folder", "").strip()
def set_roms_folder(path: str):
"""Définit le chemin du dossier ROMs personnalisé et sauvegarde."""
settings = load_rgsx_settings()
settings["roms_folder"] = path.strip()
save_rgsx_settings(settings)
return path.strip()
def get_language(settings=None):
"""Retourne la langue configurée (par défaut 'en')."""
if settings is None:
settings = load_rgsx_settings()
return settings.get("language", "en")

3656
ports/RGSX/rgsx_web.py Normal file

File diff suppressed because it is too large Load Diff

294
ports/RGSX/scraper.py Normal file
View File

@@ -0,0 +1,294 @@
"""
Module de scraping pour récupérer les métadonnées des jeux depuis TheGamesDB.net
"""
import logging
import requests
import re
from io import BytesIO
import pygame
logger = logging.getLogger(__name__)
# Mapping des noms de plateformes vers leurs IDs sur TheGamesDB
# Les noms correspondent exactement à ceux utilisés dans systems_list.json
PLATFORM_MAPPING = {
# Noms exacts du systems_list.json
"3DO Interactive Multiplayer": "25",
"3DS": "4912",
"Adventure Vision": "4974",
"Amiga CD32": "4947",
"Amiga CDTV": "4947", # Même ID que CD32
"Amiga OCS ECS": "4911",
"Apple II": "4942",
"Apple IIGS": "4942", # Même famille
"Arcadia 2001": "4963",
"Archimedes": "4944",
"Astrocade": "4968",
"Atari 2600": "22",
"Atari 5200": "26",
"Atari 7800": "27",
"Atari Lynx": "4924",
"Atari ST": "4937",
"Atom": "5014",
"Channel-F": "4928",
"ColecoVision": "31",
"Commodore 64": "40",
"Commodore Plus4": "5007",
"Commodore VIC-20": "4945",
"CreatiVision": "5005",
"Dos (x86)": "1",
"Dreamcast": "16",
"Family Computer Disk System": "4936",
"Final Burn Neo": "23", # Arcade
"FM-TOWNS": "4932",
"Gamate": "5004",
"Game Boy": "4",
"Game Boy Advance": "5",
"Game Boy Color": "41",
"Game Cube": "2",
"Game Gear": "20",
"Game Master": "4948", # Mega Duck
"Game.com": "4940",
"Jaguar": "28",
"Macintosh": "37",
"Master System": "35",
"Mattel Intellivision": "32",
"Mega CD": "21",
"Mega Drive": "36",
"Mega Duck Cougar Boy": "4948",
"MSX1": "4929",
"MSX2+": "4929",
"Namco System 246 256": "23", # Arcade
"Naomi": "23", # Arcade
"Naomi 2": "23", # Arcade
"Neo-Geo CD": "4956",
"Neo-Geo Pocket": "4922",
"Neo-Geo Pocket Color": "4923",
"Neo-Geo": "24",
"Nintendo 64": "3",
"Nintendo 64 Disk Drive": "3",
"Nintendo DS": "8",
"Nintendo DSi": "8",
"Nintendo Entertainment System": "7",
"Odyssey2": "4927",
"PC Engine": "34",
"PC Engine CD": "4955",
"PC Engine SuperGrafx": "34",
"PC-9800": "4934",
"PlayStation": "10",
"PlayStation 2": "11",
"PlayStation 3": "12",
"PlayStation Portable": "13",
"PlayStation Vita": "39",
"Pokemon Mini": "4957",
"PV-1000": "4964",
"Satellaview": "6", # SNES addon
"Saturn": "17",
"ScummVM": "1", # PC
"Sega 32X": "33",
"Sega Chihiro": "23", # Arcade
"Sega Pico": "4958",
"SG-1000": "4949",
"Sharp X1": "4977",
"SuFami Turbo": "6", # SNES addon
"Super A'Can": "4918", # Pas d'ID exact, utilise Virtual Boy
"Super Cassette Vision": "4966",
"Super Nintendo Entertainment System": "6",
"Supervision": "4959",
"Switch (1Fichier)": "4971",
"TI-99": "4953",
"V.Smile": "4988",
"Vectrex": "4939",
"Virtual Boy": "4918",
"Wii": "9",
"Wii (Virtual Console)": "9",
"Wii U": "38",
"Windows (1Fichier)": "1",
"WonderSwan": "4925",
"WonderSwan Color": "4926",
"Xbox": "14",
"Xbox 360": "15",
"ZX Spectrum": "4913",
"Game and Watch": "4950",
"Nintendo Famicom Disk System": "4936",
# Aliases communs (pour compatibilité)
"3DO": "25",
"NES": "7",
"SNES": "6",
"GBA": "5",
"GBC": "41",
"GameCube": "2",
"N64": "3",
"NDS": "8",
"PSX": "10",
"PS1": "10",
"PS2": "11",
"PS3": "12",
"PSP": "13",
"PS Vita": "39",
"Genesis": "18",
"32X": "33",
"Game & Watch": "4950",
"PC-98": "4934",
"TurboGrafx 16": "34",
"TurboGrafx CD": "4955",
"Mega Duck": "4948",
"Amiga": "4911"
}
def get_game_metadata(game_name, platform_name):
"""
Récupère les métadonnées complètes d'un jeu depuis TheGamesDB.net
Args:
game_name (str): Nom du jeu à rechercher
platform_name (str): Nom de la plateforme
Returns:
dict: Dictionnaire contenant les métadonnées ou message d'erreur
Keys: image_url, game_page_url, description, genre, release_date, error
"""
# Nettoyer le nom du jeu
clean_game_name = game_name
for ext in ['.zip', '.7z', '.rar', '.iso', '.chd', '.cue', '.bin', '.gdi', '.cdi']:
if clean_game_name.lower().endswith(ext):
clean_game_name = clean_game_name[:-len(ext)]
clean_game_name = re.sub(r'\s*[\(\[].*?[\)\]]', '', clean_game_name)
clean_game_name = clean_game_name.strip()
logger.info(f"Recherche métadonnées pour: '{clean_game_name}' sur plateforme '{platform_name}'")
# Obtenir l'ID de la plateforme
platform_id = PLATFORM_MAPPING.get(platform_name)
if not platform_id:
return {"error": f"Plateforme '{platform_name}' non supportée"}
# Construire l'URL de recherche
base_url = "https://thegamesdb.net/search.php"
params = {
"name": clean_game_name,
"platform_id[]": platform_id
}
try:
# Envoyer la requête GET pour la recherche
logger.debug(f"Recherche sur TheGamesDB: {base_url} avec params={params}")
response = requests.get(base_url, params=params, timeout=10)
if response.status_code != 200:
return {"error": f"Erreur HTTP {response.status_code}"}
html_content = response.text
# Trouver la première carte avec class 'card border-primary'
card_start = html_content.find('div class="card border-primary"')
if card_start == -1:
return {"error": "Aucun résultat trouvé"}
# Extraire l'URL de la page du jeu
href_match = re.search(r'<a href="(\.\/game\.php\?id=\d+)">', html_content[card_start-100:card_start+500])
game_page_url = None
if href_match:
game_page_url = f"https://thegamesdb.net/{href_match.group(1)[2:]}" # Enlever le ./
logger.info(f"Page du jeu trouvée: {game_page_url}")
# Extraire l'URL de l'image
img_start = html_content.find('<img class="card-img-top"', card_start)
image_url = None
if img_start != -1:
src_match = re.search(r'src="([^"]+)"', html_content[img_start:img_start+200])
if src_match:
image_url = src_match.group(1)
if not image_url.startswith("https://"):
image_url = f"https://thegamesdb.net{image_url}"
logger.info(f"Image trouvée: {image_url}")
# Extraire la date de sortie depuis les résultats de recherche
release_date = None
card_footer_start = html_content.find('class="card-footer', card_start)
if card_footer_start != -1:
# Chercher une date au format YYYY-MM-DD
date_match = re.search(r'<p>(\d{4}-\d{2}-\d{2})</p>', html_content[card_footer_start:card_footer_start+300])
if date_match:
release_date = date_match.group(1)
logger.info(f"Date de sortie trouvée: {release_date}")
# Si on a l'URL de la page, récupérer la description et le genre
description = None
genre = None
if game_page_url:
try:
logger.debug(f"Récupération de la page du jeu: {game_page_url}")
game_response = requests.get(game_page_url, timeout=10)
if game_response.status_code == 200:
game_html = game_response.text
# Extraire la description
desc_match = re.search(r'<p class="game-overview">(.*?)</p>', game_html, re.DOTALL)
if desc_match:
description = desc_match.group(1).strip()
# Nettoyer les entités HTML
description = description.replace('&#039;', "'")
description = description.replace('&quot;', '"')
description = description.replace('&amp;', '&')
logger.info(f"Description trouvée ({len(description)} caractères)")
# Extraire le genre
genre_match = re.search(r'<p>Genre\(s\): (.*?)</p>', game_html)
if genre_match:
genre = genre_match.group(1).strip()
logger.info(f"Genre trouvé: {genre}")
except Exception as e:
logger.warning(f"Erreur lors de la récupération de la page du jeu: {e}")
# Construire le résultat
result = {
"image_url": image_url,
"game_page_url": game_page_url,
"description": description,
"genre": genre,
"release_date": release_date
}
# Vérifier qu'on a au moins quelque chose
if not any([image_url, description, genre]):
result["error"] = "Métadonnées incomplètes"
return result
except requests.RequestException as e:
logger.error(f"Erreur lors de la requête: {str(e)}")
return {"error": f"Erreur réseau: {str(e)}"}
def download_image_to_surface(image_url):
"""
Télécharge une image depuis une URL et la convertit en surface Pygame
Args:
image_url (str): URL de l'image à télécharger
Returns:
pygame.Surface ou None: Surface Pygame contenant l'image, ou None en cas d'erreur
"""
try:
logger.debug(f"Téléchargement de l'image: {image_url}")
response = requests.get(image_url, timeout=10)
if response.status_code != 200:
logger.error(f"Erreur HTTP {response.status_code} lors du téléchargement de l'image")
return None
# Charger l'image depuis les bytes
image_data = BytesIO(response.content)
image_surface = pygame.image.load(image_data)
logger.info("Image téléchargée et chargée avec succès")
return image_surface
except Exception as e:
logger.error(f"Erreur lors du téléchargement de l'image: {str(e)}")
return None

View File

@@ -9,7 +9,6 @@ logger = logging.getLogger(__name__)
RGSX_ENTRY = {
"path": "./RGSX Retrobat.bat",
# 'name' left optional to preserve ES-chosen display name if already present
"name": "RGSX",
"desc": "Retro Games Sets X - Games Downloader",
"image": "./images/RGSX.png",
@@ -17,44 +16,29 @@ RGSX_ENTRY = {
"marquee": "./images/RGSX.png",
"thumbnail": "./images/RGSX.png",
"fanart": "./images/RGSX.png",
# Avoid forcing rating to not conflict with ES metadata; set only if absent
# "rating": "1",
"releasedate": "20250620T165718",
"developer": "RetroGameSets.fr",
"genre": "Various / Utilities"
}
def _get_root_dir():
"""Détecte le dossier racine RetroBat sans importer config."""
# Ce script est dans .../roms/ports/RGSX/
here = os.path.abspath(os.path.dirname(__file__))
# Remonter à .../roms/ports/
ports_dir = os.path.dirname(here)
# Remonter à .../roms/
roms_dir = os.path.dirname(ports_dir)
# Remonter à la racine RetroBat
root_dir = os.path.dirname(roms_dir)
return root_dir
def update_gamelist():
try:
root_dir = _get_root_dir()
gamelist_xml = os.path.join(root_dir, "roms", "windows", "gamelist.xml")
from config import GAMELISTXML_WINDOWS
# Si le fichier n'existe pas, est vide ou non valide, créer une nouvelle structure
if not os.path.exists(gamelist_xml) or os.path.getsize(gamelist_xml) == 0:
logger.info(f"Création de {gamelist_xml}")
if not os.path.exists(GAMELISTXML_WINDOWS) or os.path.getsize(GAMELISTXML_WINDOWS) == 0:
logger.info(f"Création de {GAMELISTXML_WINDOWS}")
root = ET.Element("gameList")
else:
try:
logger.info(f"Lecture de {gamelist_xml}")
tree = ET.parse(gamelist_xml)
logger.info(f"Lecture de {GAMELISTXML_WINDOWS}")
tree = ET.parse(GAMELISTXML_WINDOWS)
root = tree.getroot()
if root.tag != "gameList":
logger.info(f"{gamelist_xml} n'a pas de balise <gameList>, création d'une nouvelle structure")
logger.info(f"{GAMELISTXML_WINDOWS} n'a pas de balise <gameList>, création d'une nouvelle structure")
root = ET.Element("gameList")
except ET.ParseError:
logger.info(f"{gamelist_xml} est invalide, création d'une nouvelle structure")
logger.info(f"{GAMELISTXML_WINDOWS} est invalide, création d'une nouvelle structure")
root = ET.Element("gameList")
# Rechercher une entrée existante pour ce chemin
@@ -72,37 +56,37 @@ def update_gamelist():
elem = ET.SubElement(game_elem, key)
elem.text = value
logger.info("Nouvelle entrée RGSX ajoutée")
else:
# Fusionner: préserver les champs gérés par ES, compléter/mettre à jour nos champs
def ensure(tag, value):
elem = game_elem.find(tag)
if elem is None:
elem = ET.SubElement(game_elem, tag)
if elem.text is None or elem.text.strip() == "":
elem.text = value
# else:
# # Fusionner: préserver les champs gérés par ES, compléter/mettre à jour nos champs
# def ensure(tag, value):
# elem = game_elem.find(tag)
# if elem is None:
# elem = ET.SubElement(game_elem, tag)
# if elem.text is None or elem.text.strip() == "":
# elem.text = value
# S'assurer du chemin
ensure("path", RGSX_ENTRY["path"])
# Ne pas écraser le nom s'il existe déjà (ES peut le définir selon le fichier)
name_elem = game_elem.find("name")
existing_name = ""
if name_elem is not None and name_elem.text:
existing_name = name_elem.text.strip()
if not existing_name:
ensure("name", RGSX_ENTRY.get("name", "RGSX"))
# # S'assurer du chemin
# ensure("path", RGSX_ENTRY["path"])
# # Ne pas écraser le nom s'il existe déjà (ES peut le définir selon le fichier)
# name_elem = game_elem.find("name")
# existing_name = ""
# if name_elem is not None and name_elem.text:
# existing_name = name_elem.text.strip()
# if not existing_name:
# ensure("name", RGSX_ENTRY.get("name", "RGSX"))
# Champs d'habillage que nous voulons imposer/mettre à jour
for tag in ("desc", "image", "video", "marquee", "thumbnail", "fanart", "developer", "genre", "releasedate"):
val = RGSX_ENTRY.get(tag)
if val:
elem = game_elem.find(tag)
if elem is None:
elem = ET.SubElement(game_elem, tag)
# Toujours aligner ces champs sur nos valeurs pour garder l'expérience RGSX
elem.text = val
# # Champs d'habillage que nous voulons imposer/mettre à jour
# for tag in ("desc", "image", "video", "marquee", "thumbnail", "fanart", "developer", "genre", "releasedate"):
# val = RGSX_ENTRY.get(tag)
# if val:
# elem = game_elem.find(tag)
# if elem is None:
# elem = ET.SubElement(game_elem, tag)
# # Toujours aligner ces champs sur nos valeurs pour garder l'expérience RGSX
# elem.text = val
# Ne pas toucher aux champs: playcount, lastplayed, gametime, lang, favorite, kidgame, hidden, rating
logger.info("Entrée RGSX mise à jour (fusion)")
# # Ne pas toucher aux champs: playcount, lastplayed, gametime, lang, favorite, kidgame, hidden, rating
# logger.info("Entrée RGSX mise à jour (fusion)")
# Générer le XML avec minidom pour une indentation correcte
rough_string = '<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(root, encoding='unicode')
@@ -110,13 +94,13 @@ def update_gamelist():
pretty_xml = parsed.toprettyxml(indent="\t", encoding='utf-8').decode('utf-8')
# Supprimer les lignes vides inutiles générées par minidom
pretty_xml = '\n'.join(line for line in pretty_xml.split('\n') if line.strip())
with open(gamelist_xml, 'w', encoding='utf-8') as f:
with open(GAMELISTXML_WINDOWS, 'w', encoding='utf-8') as f:
f.write(pretty_xml)
logger.info(f"{gamelist_xml} mis à jour avec succès")
logger.info(f"{GAMELISTXML_WINDOWS} mis à jour avec succès")
# Définir les permissions
try:
os.chmod(gamelist_xml, 0o644)
os.chmod(GAMELISTXML_WINDOWS, 0o644)
except Exception:
# Sur Windows, chmod peut être partiel; ignorer silencieusement
pass

File diff suppressed because it is too large Load Diff

View File

@@ -1,326 +0,0 @@
#!/bin/bash
# Script pour telecharger et installer l'application RGSX depuis retrogamesets.fr
# et mettre a jour gamelist.xml pour ajouter l'entree RGSX
# Supprime rgsx-install.sh et RGSX.zip apres une installation reussie
# Affiche des messages informatifs sur la console (mode CONSOLE) ou via xterm (mode DISPLAY)
# Variables
URL="https://retrogamesets.fr/softs/RGSX.zip"
ZIP_FILE="/tmp/rgsx.zip"
DEST_DIR="/userdata/roms/ports"
RGSX_DIR="${DEST_DIR}/RGSX"
GAMELIST_FILE="${DEST_DIR}/gamelist.xml"
UPDATE_GAMELIST_PY="${RGSX_DIR}/update_gamelist.py"
LOG_DIR="${DEST_DIR}/logs"
LOG_FILE="${LOG_DIR}/rgsx_install.log"
TEMP_LOG="/tmp/rgsx_install_temp.log"
SCRIPT_FILE="${DEST_DIR}/rgsx-install.sh"
XTERM="/usr/bin/xterm"
MODE="DISPLAY" # Par defaut, mode graphique pour PORTS
TEXT_SIZE="48" # Taille de police pour xterm
TEXT_COLOR="green"
# Chemins absolus pour les commandes
CURL="/usr/bin/curl"
WGET="/usr/bin/wget"
UNZIP="/usr/bin/unzip"
PING="/bin/ping"
RM="/bin/rm"
MKDIR="/bin/mkdir"
CHMOD="/bin/chmod"
FIND="/usr/bin/find"
PYTHON3="/usr/bin/python3"
SYNC="/bin/sync"
SLEEP="/bin/sleep"
LS="/bin/ls"
CAT="/bin/cat"
WHOAMI="/usr/bin/whoami"
ENV="/usr/bin/env"
TOUCH="/bin/touch"
DF="/bin/df"
MOUNT="/bin/mount"
NSLOOKUP="/usr/bin/nslookup"
# Verifier le mode (DISPLAY ou CONSOLE)
if [ "$1" = "CONSOLE" ] || [ "$1" = "console" ]; then
MODE="CONSOLE"
fi
# Fonction pour journaliser avec horodatage dans les fichiers de log
log() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
if [ -d "$LOG_DIR" ] && [ -w "$LOG_DIR" ]; then
echo "[$timestamp] $1" >> "$LOG_FILE" 2>&1
else
echo "[$timestamp] $1" >> "$TEMP_LOG" 2>&1
fi
}
# Fonction pour afficher des messages informatifs (console ou xterm)
console_log() {
local message="[RGSX Install] $1"
log "$message"
echo "$message" # Toujours afficher dans le terminal
if [ "$MODE" = "DISPLAY" ] && [ -x "$XTERM" ]; then
echo "$message" >> /tmp/rgsx_install_display.log
fi
}
# Fonction pour executer une commande et journaliser son execution
run_command() {
local cmd_name="$2"
log "Execution de la commande : $1"
output=$(eval "$1" 2>&1)
local exit_code=$?
log "Sortie de '$cmd_name' :"
log "$output"
log "Code de retour : $exit_code"
return $exit_code
}
# Fonction pour gerer les erreurs avec journalisation et message console/xterm
error_exit() {
local error_msg="$1"
log "Erreur fatale : $error_msg"
console_log "Erreur lors de l'installation : $error_msg"
console_log "Consultez $LOG_FILE pour plus de details."
log "Nettoyage du fichier ZIP temporaire : $ZIP_FILE"
if [ -f "$ZIP_FILE" ]; then
run_command "$RM -f $ZIP_FILE" "rm_zip"
fi
log "Arrêt du script avec code d'erreur 1"
if [ "$MODE" = "DISPLAY" ] && [ -x "$XTERM" ]; then
LC_ALL=C $XTERM -fullscreen -fg $TEXT_COLOR -bg black -fs $TEXT_SIZE -e "cat /tmp/rgsx_install_display.log; echo 'Appuyez sur une touche pour quitter...'; read -n 1; exit"
rm -f /tmp/rgsx_install_display.log
fi
exit 1
}
# Configurer l'environnement graphique pour xterm
if [ "$MODE" = "DISPLAY" ]; then
export DISPLAY=:0.0
export LC_ALL=C # Definir la locale pour eviter l'avertissement Xlib
if [ -x "$XTERM" ]; then
cp $XTERM /tmp/rgsx-install-xterm && chmod 777 /tmp/rgsx-install-xterm
echo "[RGSX Install] Demarrage de l'installation de RGSX..." > /tmp/rgsx_install_display.log
# Lancer xterm en arriere-plan pour afficher la progression
LC_ALL=C /tmp/rgsx-install-xterm -fullscreen -fg $TEXT_COLOR -bg black -fs $TEXT_SIZE -e "tail -f /tmp/rgsx_install_display.log" &
XTERM_PID=$!
sleep 1 # Attendre que xterm demarre
else
log "xterm non disponible, passage en mode journalisation uniquement."
MODE="CONSOLE"
fi
else
console_log "Demarrage de l'installation de RGSX..."
fi
# Verifier l'accessibilite de /tmp pour le journal temporaire
log "Verification de l'accessibilite de /tmp pour le journal temporaire"
run_command "$TOUCH $TEMP_LOG && $RM $TEMP_LOG" "test_tmp_access" || error_exit "Le repertoire /tmp n'est pas accessible en ecriture."
# Nettoyer les dossiers mal crees
log "Verification des dossiers mal crees sous /userdata"
if [ -d "/userdata/\"/userdata/roms/ports\"" ]; then
log "Suppression du dossier incorrect /userdata/\"/userdata/roms/ports\""
run_command "$RM -rf /userdata/\\\"/userdata/roms/ports\\\"" "rm_incorrect_dir"
fi
# Journaliser l'etat du systeme de fichiers
log "etat du systeme de fichiers :"
run_command "$DF -h" "df_filesystem"
log "Points de montage :"
run_command "$MOUNT" "mount_points"
# Verifier et creer le repertoire /userdata/roms/ports
console_log "Verification du repertoire $DEST_DIR..."
log "Verification et creation du repertoire $DEST_DIR"
run_command "$MKDIR -p $DEST_DIR" "mkdir_dest_dir" || error_exit "Impossible de creer $DEST_DIR."
log "Verification de l'existence de $DEST_DIR"
if [ ! -d "$DEST_DIR" ]; then
error_exit "$DEST_DIR n'a pas ete cree."
fi
log "Permissions de $DEST_DIR apres creation"
run_command "$LS -ld $DEST_DIR" "ls_dest_dir"
if [ ! -w "$DEST_DIR" ]; then
log "Tentative de correction des permissions de $DEST_DIR"
run_command "$CHMOD u+w $DEST_DIR" "chmod_dest_dir" || error_exit "Impossible de rendre $DEST_DIR accessible en ecriture."
fi
# Creer le repertoire des logs
log "Creation du repertoire des logs : $LOG_DIR"
run_command "$MKDIR -p $LOG_DIR" "mkdir_log_dir" || error_exit "Impossible de creer $LOG_DIR."
# Copier le journal temporaire dans LOG_FILE
if [ -f "$TEMP_LOG" ] && [ -d "$LOG_DIR" ]; then
log "Copie du journal temporaire $TEMP_LOG vers $LOG_FILE"
run_command "$CAT $TEMP_LOG >> $LOG_FILE" "copy_temp_log"
run_command "$RM -f $TEMP_LOG" "rm_temp_log"
fi
# Journaliser l'environnement d'execution
log "Utilisateur actuel :"
run_command "$WHOAMI" "whoami"
log "Variables d'environnement :"
run_command "$ENV" "env"
log "Chemin PATH : $PATH"
# Verifier les dependances
log "Verification des commandes necessaires"
for cmd in "$CURL" "$UNZIP" "$PING" "$RM" "$MKDIR" "$CHMOD" "$FIND" "$PYTHON3" "$SYNC" "$SLEEP" "$LS" "$CAT" "$TOUCH" "$DF" "$MOUNT"; do
if [ ! -x "$cmd" ]; then
error_exit "Commande $cmd non trouvee ou non executable."
fi
log "Commande $cmd : OK"
done
if [ -x "$WGET" ]; then
log "Commande $WGET : OK"
else
log "Commande $WGET : Non disponible, utilisation de curl uniquement."
fi
if [ -x "$NSLOOKUP" ]; then
log "Commande $NSLOOKUP : OK"
else
log "Commande $NSLOOKUP : Non disponible."
fi
# Verifier la connexion Internet
log "Test de connexion Internet..."
run_command "$PING 8.8.4.4 -c 1" "ping_google" || error_exit "Pas de connexion Internet."
# Tester la resolution DNS
log "Test de resolution DNS pour retrogamesets.fr"
if [ -x "$NSLOOKUP" ]; then
run_command "$NSLOOKUP retrogamesets.fr" "nslookup_retrogamesets"
fi
run_command "$PING -c 1 retrogamesets.fr" "ping_retrogamesets"
# Telecharger le ZIP avec curl
console_log "Telechargement de RGSX..."
log "Tentative de telechargement avec curl : $URL vers $ZIP_FILE..."
run_command "$CURL -L --insecure -v -o $ZIP_FILE $URL" "curl_download"
if [ $? -ne 0 ]; then
log "echec du telechargement avec curl, tentative avec wget si disponible..."
if [ -x "$WGET" ]; then
run_command "$WGET --no-check-certificate -O $ZIP_FILE $URL" "wget_download" || error_exit "echec du telechargement avec wget."
else
error_exit "echec du telechargement avec curl et wget non disponible."
fi
fi
log "Details du fichier telecharge :"
run_command "$LS -l $ZIP_FILE" "ls_zip_file"
# Verifier si le fichier ZIP existe
log "Verification de l'existence de $ZIP_FILE"
if [ ! -f "$ZIP_FILE" ]; then
error_exit "Le fichier ZIP $ZIP_FILE n'a pas ete telecharge."
fi
log "Fichier $ZIP_FILE trouve."
# Verifier si le fichier ZIP est valide
log "Verification de l'integrite du fichier ZIP : $ZIP_FILE"
run_command "$UNZIP -t $ZIP_FILE" "unzip_test" || error_exit "Le fichier ZIP est corrompu ou invalide."
log "Contenu du ZIP :"
run_command "$UNZIP -l $ZIP_FILE" "unzip_list"
# Supprimer l'ancien dossier RGSX s'il existe
if [ -d "$RGSX_DIR" ]; then
log "Suppression de l'ancien dossier $RGSX_DIR..."
run_command "$RM -rf $RGSX_DIR" "rm_rgsx_dir" || error_exit "Impossible de supprimer $RGSX_DIR."
run_command "$SYNC" "sync_after_rm"
log "Attente de 2 secondes apres suppression..."
run_command "$SLEEP 2" "sleep_after_rm"
if [ -d "$RGSX_DIR" ]; then
error_exit "Le dossier $RGSX_DIR existe toujours apres tentative de suppression."
fi
log "Ancien dossier $RGSX_DIR supprime avec succes."
else
log "Aucun dossier $RGSX_DIR existant trouve."
fi
# Extraire le ZIP
console_log "Extraction des fichiers..."
log "Extraction de $ZIP_FILE vers $DEST_DIR..."
run_command "$UNZIP -q -o $ZIP_FILE -d $DEST_DIR" "unzip_extract" || error_exit "echec de l'extraction de $ZIP_FILE."
log "Contenu de $DEST_DIR apres extraction :"
run_command "$LS -la $DEST_DIR" "ls_dest_dir_after_extract"
# Verifier si le dossier RGSX a ete extrait
log "Verification de l'existence de $RGSX_DIR"
if [ ! -d "$RGSX_DIR" ]; then
error_exit "Le dossier RGSX n'a pas ete trouve dans $DEST_DIR apres extraction."
fi
log "Dossier $RGSX_DIR trouve."
# Rendre les fichiers .sh executables
log "Rendre les fichiers .sh executables dans $RGSX_DIR..."
run_command "$FIND $RGSX_DIR -type f -name \"*.sh\" -exec $CHMOD +x {} \;" "chmod_sh_files" || log "Avertissement : Impossible de rendre certains fichiers .sh executables."
log "Fichiers .sh dans $RGSX_DIR :"
run_command "$FIND $RGSX_DIR -type f -name \"*.sh\" -ls" "find_sh_files"
# Rendre update_gamelist.py executable
log "Rendre $UPDATE_GAMELIST_PY executable..."
if [ -f "$UPDATE_GAMELIST_PY" ]; then
run_command "$CHMOD +x $UPDATE_GAMELIST_PY" "chmod_update_gamelist" || log "Avertissement : Impossible de rendre $UPDATE_GAMELIST_PY executable."
else
error_exit "Le script Python $UPDATE_GAMELIST_PY n'existe pas."
fi
# Definir les permissions du dossier RGSX
log "Definition des permissions de $RGSX_DIR..."
run_command "$CHMOD -R u+rwX $RGSX_DIR" "chmod_rgsx_dir" || log "Avertissement : Impossible de definir les permissions de $RGSX_DIR."
# Verifier les permissions d'ecriture
log "Verification des permissions d'ecriture sur $DEST_DIR"
if [ ! -w "$DEST_DIR" ]; then
error_exit "Le repertoire $DEST_DIR n'est pas accessible en ecriture."
fi
log "Permissions d'ecriture sur $DEST_DIR : OK"
# Mettre a jour gamelist.xml avec Python
console_log "Mise a jour de gamelist.xml..."
log "Mise a jour de $GAMELIST_FILE avec Python..."
run_command "$PYTHON3 $UPDATE_GAMELIST_PY" "python_update_gamelist" || error_exit "echec de la mise a jour de $GAMELIST_FILE avec Python."
log "Contenu de $GAMELIST_FILE apres mise a jour :"
run_command "$CAT $GAMELIST_FILE" "cat_gamelist"
# Verifier les permissions du fichier gamelist.xml
log "Definition des permissions de $GAMELIST_FILE..."
run_command "$CHMOD 644 $GAMELIST_FILE" "chmod_gamelist" || log "Avertissement : Impossible de definir les permissions de $GAMELIST_FILE."
# Nettoyer le fichier ZIP temporaire
console_log "Nettoyage des fichiers temporaires..."
log "Nettoyage du fichier ZIP temporaire : $ZIP_FILE"
if [ -f "$ZIP_FILE" ]; then
run_command "$RM -f $ZIP_FILE" "rm_zip" || log "Avertissement : Impossible de supprimer $ZIP_FILE."
fi
# Nettoyer le fichier ZIP dans /userdata/roms/ports
log "Nettoyage du fichier ZIP dans $DEST_DIR : $DEST_DIR/RGSX.zip"
if [ -f "$DEST_DIR/RGSX.zip" ]; then
run_command "$RM -f $DEST_DIR/RGSX.zip" "rm_dest_zip" || log "Avertissement : Impossible de supprimer $DEST_DIR/RGSX.zip."
fi
# Finalisation
console_log "Installation reussie dans le système PORTS! Appuyez sur entrée pour quitter. Actualisez la liste des jeux si RGSX n'apparait pas."
log "Installation reussie dans le système PORTS! Appuyez sur entrée pour quitter. Actualisez la liste des jeux si RGSX n'apparait pas."
log "L'entree RGSX a ete ajoutee a $GAMELIST_FILE."
log "Fin du script avec code de retour 0"
run_command "$PING -q www.google.fr -c 5" "ping_google"
curl -s http://127.0.0.1:1234/reloadgames
# Afficher la finalisation dans xterm et attendre une entree utilisateur
if [ "$MODE" = "DISPLAY" ] && [ -x "$XTERM" ]; then
kill $XTERM_PID 2>/dev/null
LC_ALL=C $XTERM -fullscreen -fg $TEXT_COLOR -bg black -fs $TEXT_SIZE -e "cat /tmp/rgsx_install_display.log; echo 'Installation terminee. Appuyez sur une touche pour quitter...'; read -n 1; exit"
rm -f /tmp/rgsx_install_display.log
rm -f /tmp/rgsx-install-xterm
fi
# Supprimer le script d'installation
log "Suppression du script d'installation : $SCRIPT_FILE"
if [ -f "$SCRIPT_FILE" ]; then
run_command "$RM -f $SCRIPT_FILE" "rm_script" || log "Avertissement : Impossible de supprimer $SCRIPT_FILE."
fi
exit 0

View File

@@ -55,8 +55,8 @@ echo UPDATE_GAMELIST_SCRIPT : !UPDATE_GAMELIST_SCRIPT! >> "%LOG_FILE%"
echo Checking python.exe...
echo [%DATE% %TIME%] Checking python.exe at !PYTHON_EXE_FULL! >> "%LOG_FILE%"
if not exist "!PYTHON_EXE_FULL!" (
echo python.exe not found. Preparing download...
echo [%DATE% %TIME%] python.exe not found. Preparing download... >> "%LOG_FILE%"
echo python.exe not found in system/tools. Preparing to extract..
echo [%DATE% %TIME%] python.exe not found in system/tools. Preparing to extract.. >> "%LOG_FILE%"
:: Créer le dossier Python s'il n'existe pas
set "TOOLS_FOLDER_FULL=!ROOT_DIR!\system\tools"
@@ -67,29 +67,21 @@ if not exist "!PYTHON_EXE_FULL!" (
mkdir "!TOOLS_FOLDER_FULL!\Python"
)
set ZIP_URL=https://retrogamesets.fr/softs/python.zip
set "ZIP_FILE=!TOOLS_FOLDER_FULL!\python.zip"
echo ZIP_URL : !ZIP_URL!
echo [%DATE% %TIME%] ZIP_URL : !ZIP_URL! >> "%LOG_FILE%"
echo ZIP_FILE : !ZIP_FILE!
set "ZIP_FILE=%ROOT_DIR%\roms\windows\python.zip"
echo Extracting ZIP_FILE : !ZIP_FILE! in /system/tools/Python
echo [%DATE% %TIME%] ZIP_FILE : !ZIP_FILE! >> "%LOG_FILE%"
echo Downloading python.zip...
echo [%DATE% %TIME%] Downloading python.zip from !ZIP_URL!... >> "%LOG_FILE%"
curl -L "!ZIP_URL!" -o "!ZIP_FILE!"
if exist "!ZIP_FILE!" (
echo Download complete. Extracting python.zip...
echo [%DATE% %TIME%] Download complete. Extracting python.zip to !TOOLS_FOLDER_FULL!... >> "%LOG_FILE%"
echo [%DATE% %TIME%] Extracting python.zip to !TOOLS_FOLDER_FULL!... >> "%LOG_FILE%"
tar -xf "!ZIP_FILE!" -C "!TOOLS_FOLDER_FULL!\Python" --strip-components=0
echo Extraction finished.
echo [%DATE% %TIME%] Extraction finished. >> "%LOG_FILE%"
del /q "!ZIP_FILE!"
del /s /q "!ZIP_FILE!"
echo python.zip file deleted.
echo [%DATE% %TIME%] python.zip file deleted. >> "%LOG_FILE%"
) else (
echo Error: Failed to download python.zip.
echo [%DATE% %TIME%] Error: Failed to download python.zip. >> "%LOG_FILE%"
echo Error: Error python.zip not found please download it from github and put in /roms/windows folder.
echo [%DATE% %TIME%] Error: Error python.zip not found please download it from github and put in /roms/windows folder >> "%LOG_FILE%"
goto :error
)

BIN
windows/python.zip Normal file

Binary file not shown.