Compare commits

...

32 Commits

Author SHA1 Message Date
skymike03
61b615f4c7 v2.3.1.7
- correct typo error in xiso linux
2025-11-05 23:04:54 +01:00
skymike03
03d64d4401 v2.3.1.6 (2025.11.05)
- Replace xdvdfs by extract_xiso because conversion not working in all xbox games
2025-11-05 05:33:18 +01:00
skymike03
798ef13dd3 v2.3.1.5 (2025.11.04)
- update integrated roms info scraper to use tgdb api instead of https web requests
- Add Docker support (web server)
- Add region filters (web server)
- Add one-ROM-per-game filter with region priority (web server)
- Refactor code structure in RGSX Web to improved readability and maintainability (js, css, html separated)
- update language files and correct a bug that crash
when changing language, or changing filter
2025-11-04 19:25:43 +01:00
skymike03
40d0826a6b update language files and correct a bug that crash
when changing language
2025-11-04 19:08:43 +01:00
skymike03
82dbf4e49d Refactor code structure in RGSX Web to improved readability and maintainability, move docker files to folder, update language files 2025-11-04 19:00:54 +01:00
RGS
26f8499c83 Merge pull request #28 from SeeThruHead/main
Improve Docker support with configurable permissions and SMB compatib…
2025-11-04 18:35:15 +01:00
shane keulen
0f671ccdf2 Improve Docker support with configurable permissions and SMB compatibility
Added flexible user/group ID handling to support different storage backends:

- Configurable PUID/PGID environment variables for NFS and local storage
- RUN_AS_ROOT mode for SMB mounts that only allow root writes
- Pre-chown app files during build to enable non-root rsync
- Improved error messages with troubleshooting guidance
- Updated documentation with setup examples for different scenarios

This allows the container to work correctly with Unraid SMB shares, NFS mounts,
and local storage by adapting to how different filesystems handle permissions.

Default behavior (PUID=99, PGID=100) remains compatible with Unraid nobody:users.
2025-11-04 09:31:04 -05:00
skymike03
0c55d6d6d6 update jap logo 2025-11-03 23:45:59 +01:00
skymike03
cdc81f795d update region priority to allow user change order, add regions icons , add multiple region search ex if game has 'USA, EUROPE' in name, you can find it with USA or EUROPE filter. 2025-11-03 23:43:40 +01:00
skymike03
961e46a77d Merge branch 'pr-27' 2025-11-03 23:41:01 +01:00
skymike03
7a061ef0bc update scraper to use tgdb api 2025-11-03 22:44:55 +01:00
Shane Keulen
57b75dc199 Merge branch 'RetroGameSets:main' into main 2025-11-02 14:21:07 -05:00
skymike03
af42c31476 v2.3.1.4
- add scummvm handling (extract subfolder and create blank .scummvm file)
2025-11-02 11:39:47 +01:00
skymike03
eeea4763f5 .. 2025-11-02 11:23:38 +01:00
skymike03
201d56fff4 . 2025-11-02 11:23:09 +01:00
shane keulen
3caa780e5a Add region exclude filters (3-state toggle) and preserve disc numbers in 1G1R filter 2025-11-02 01:08:53 -04:00
shane keulen
716fa23e4e Add one-ROM-per-game filter with region priority (USA→Canada→World→Europe) 2025-11-02 00:54:01 -04:00
shane keulen
d0eaf387b2 Fix: Always sync app code on container start
- Changed entrypoint to always copy/update code
- Enables container updates without manual intervention
- Fixes issue where code changes weren't reflected
2025-11-02 00:35:03 -04:00
shane keulen
28a0013bee Add advanced game filtering to web interface
- Region filtering: USA, Europe, Japan, World, Other
- Hide demos/betas/protos checkbox
- Regex search mode option
- Live filter status display
- All filters work together with AND logic
2025-11-02 00:25:43 -04:00
shane keulen
acb3eb33c3 Add Docker support for RGSX web server
- Minimal Dockerfile with Python 3.11 and required dependencies
- docker-entrypoint.sh initializes folder structure and settings
- README-DOCKER.md with simple build and run instructions
- Updated .gitignore to exclude Docker test data
2025-11-01 23:42:46 -04:00
skymike03
486c9d0244 v2.3.1.3
- new update json file
2025-11-02 00:04:52 +01:00
skymike03
aed7da8b51 v2.3.1.3
- new update json file
2025-11-02 00:04:27 +01:00
skymike03
93fc4a023d v2.3.1.2.1
- Update application version to  and change OTA version endpoint URL
2025-11-01 23:42:33 +01:00
skymike03
b4398b1d82 v2.3.1.2
- corrrect version check link
2025-11-01 23:24:20 +01:00
skymike03
751800026c v2.3.1.2
- corrrect version check link
2025-11-01 23:23:59 +01:00
skymike03
8427ba60eb v2.3.1.1
- update version check now on github to avoid error about accessing updates on retrogamesets.fr
- correct some imports
2025-11-01 23:21:17 +01:00
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
34 changed files with 6001 additions and 3899 deletions

View File

@@ -43,7 +43,7 @@ jobs:
"*.log"
cd ../..
cp -f "dist/RGSX_update_latest.zip" "dist/RGSX_latest.zip"
echo "✓ RGSX package created successfully"
@@ -84,23 +84,48 @@ jobs:
draft: false
prerelease: false
body: |
## 📦 RGSX Release ${{ github.ref_name }}
# 📦 RGSX Release ${{ github.ref_name }}
### 📥 Manual Installation
## 📥 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 `RGSX_full_latest.zip`
### 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. `RGSX_full_latest.zip`
### 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)
[README.md](https://github.com/${{ github.repository }}/blob/main/README.md)
files: |
dist/RGSX_update_latest.zip
dist/RGSX_full_latest.zip

5
.gitignore vendored
View File

@@ -11,10 +11,13 @@ ports/gamelist.xml
prboom/
*.log
*.rar
*.zip
.vscode/
ports/RGSX.bat
.venv/
audit_i18n.py
prune_i18n.py
Info.txt
# Docker test data
data/

40
docker/Dockerfile Normal file
View File

@@ -0,0 +1,40 @@
FROM python:3.11-slim
# Install system dependencies for ROM extraction
RUN apt-get update && apt-get install -y --no-install-recommends \
p7zip-full \
unrar-free \
curl \
rsync \
gosu \
&& rm -rf /var/lib/apt/lists/*
# Create required directories
RUN mkdir -p /userdata/saves/ports/rgsx \
&& mkdir -p /userdata/roms/ports \
&& mkdir -p /app
# Copy RGSX application files to /app (will be copied to volume at runtime)
COPY ports/RGSX/ /app/RGSX/
# Copy entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Install Python dependencies
# pygame is imported in some modules even in headless mode, so we include it
RUN pip install --no-cache-dir requests pygame
# Set environment to headless mode
ENV RGSX_HEADLESS=1
# Expose web interface port
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:5000/ || exit 1
# Entrypoint copies app to volume, then runs command
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["python", "rgsx_web.py", "--host", "0.0.0.0", "--port", "5000"]

179
docker/README-DOCKER.md Normal file
View File

@@ -0,0 +1,179 @@
# 🐳 RGSX Docker - Headless Web Server
Run RGSX as a web-only service without the Pygame UI. Perfect for homelab/server setups.
## Quick Start
```bash
# Build the image
docker build -t rgsx .
# Run with docker
docker run -d \
--name rgsx \
-p 5000:5000 \
-e PUID=99 \
-e PGID=100 \
-e RGSX_HEADLESS=1 \
-v ./data/saves:/userdata/saves/ports/rgsx \
-v ./data/roms:/userdata/roms/ports \
-v ./data/logs:/userdata/roms/ports/RGSX/logs \
rgsx
# Access the web interface
open http://localhost:5000
```
## What This Does
- Runs RGSX web server in headless mode (no Pygame UI)
- Web interface accessible from any browser
- ROMs and settings persist in `./data/` volumes
- Container restarts automatically
## Configuration
### User Permissions (Important!)
**For SMB mounts (Unraid, Windows shares):**
Don't set PUID/PGID. The container runs as root, and the SMB server maps files to your authenticated user.
```bash
docker run \
-e RGSX_HEADLESS=1 \
...
```
**For NFS/local storage:**
Set PUID and PGID to match your host user. Files will be owned by that user.
```bash
docker run \
-e PUID=1000 \
-e PGID=1000 \
-e RGSX_HEADLESS=1 \
...
```
**Find your user ID:**
```bash
id -u # Your UID
id -g # Your GID
```
### Change Port
```bash
docker run -p 8080:5000 ... # Access on port 8080
```
### Custom ROM Location
Map to your existing ROM collection:
```bash
docker run -v /your/existing/roms:/userdata/roms/ports ...
```
### API Keys
Add your download service API keys to `./data/saves/`:
```bash
# Add your API key (just the key, no extra text)
echo "YOUR_KEY_HERE" > ./data/saves/1FichierAPI.txt
# Optional: AllDebrid/RealDebrid fallbacks
echo "YOUR_KEY" > ./data/saves/AllDebridAPI.txt
echo "YOUR_KEY" > ./data/saves/RealDebridAPI.txt
# Restart to apply
docker restart rgsx
```
## Commands
```bash
# Start
docker start rgsx
# View logs
docker logs -f rgsx
# Stop
docker stop rgsx
# Update (after git pull)
docker build --no-cache -t rgsx .
docker stop rgsx && docker rm rgsx
# Then re-run the docker run command
```
## Directory Structure
```
RGSX/
├── data/ # Created on first run
│ ├── saves/ # Settings, history, API keys
│ ├── roms/ # Downloaded ROMs
│ └── logs/ # Application logs
├── Dockerfile
└── docker-compose.yml
```
## How It Works
RGSX already has a headless mode (`RGSX_HEADLESS=1`) and the web server (`rgsx_web.py`) works standalone - this was designed for the Batocera web service. The Docker setup just runs it in a container with proper volume mappings.
## Troubleshooting
**Permission denied errors / Can't delete files:**
The container creates files with the UID/GID specified by PUID/PGID environment variables:
```bash
# Set correct PUID/PGID for your environment
docker run -e PUID=1000 -e PGID=1000 ...
```
**Changed PUID/PGID and container won't start:**
When you change PUID/PGID, old files with different ownership will cause rsync to fail. You MUST fix ownership on the storage server:
```bash
# On your NAS/Unraid (via SSH), either:
# Option 1: Delete old files (easiest)
rm -rf /mnt/user/roms/rgsx/roms/ports/RGSX/*
# Option 2: Change ownership to new PUID/PGID
chown -R 1000:1000 /mnt/user/roms/rgsx/roms/ports/RGSX/
```
Then restart the container.
**Port already in use:**
```bash
docker run -p 8080:5000 ... # Use port 8080 instead
```
**Container won't start:**
```bash
docker logs rgsx
```
## vs Traditional Install
| Feature | Docker | Batocera/RetroBat |
|---------|--------|-------------------|
| Interface | Web only | Pygame UI + Web |
| Install | `docker run` | Manual setup |
| Updates | `docker build` | git pull |
| Access | Any device on network | Device only |
| Use Case | Server/homelab | Gaming device |
## Support
- RGSX Issues: https://github.com/RetroGameSets/RGSX/issues
- Discord: https://discord.gg/Vph9jwg3VV

View File

@@ -0,0 +1,72 @@
#!/bin/bash
set -e
# If PUID/PGID are set, create user and run as that user
# If not set, run as root (works for SMB mounts)
if [ -n "$PUID" ] && [ -n "$PGID" ]; then
echo "=== Creating user with PUID=$PUID, PGID=$PGID ==="
# Create group if it doesn't exist
if ! getent group $PGID >/dev/null 2>&1; then
groupadd -g $PGID rgsx
fi
# Create user if it doesn't exist
if ! getent passwd $PUID >/dev/null 2>&1; then
useradd -u $PUID -g $PGID -m -s /bin/bash rgsx
fi
# Fix ownership of app files
chown -R $PUID:$PGID /app /userdata 2>/dev/null || true
echo "=== Running as user $(id -un $PUID) (UID=$PUID, GID=$PGID) ==="
RUN_USER="gosu rgsx"
else
echo "=== Running as root (no PUID/PGID set) - for SMB mounts ==="
RUN_USER=""
fi
# Always sync RGSX app code to the mounted volume (for updates)
echo "Syncing RGSX app code to /userdata/roms/ports/RGSX..."
$RUN_USER mkdir -p /userdata/roms/ports/RGSX
# Try rsync
if ! $RUN_USER rsync -av --delete /app/RGSX/ /userdata/roms/ports/RGSX/ 2>&1; then
echo ""
echo "=========================================="
echo "WARNING: rsync partially failed!"
echo "=========================================="
echo "Some files may not have synced. Container will continue for debugging."
echo ""
if [ -n "$PUID" ] && [ -n "$PGID" ]; then
echo "If using SMB, try removing PUID/PGID to run as root"
fi
echo ""
fi
echo "RGSX app code sync attempted."
# Create Batocera folder structure only if folders don't exist
$RUN_USER mkdir -p /userdata/saves/ports/rgsx/images
$RUN_USER mkdir -p /userdata/saves/ports/rgsx/games
$RUN_USER mkdir -p /userdata/roms/ports/RGSX/logs
# Create default settings with show_unsupported_platforms enabled if config doesn't exist
SETTINGS_FILE="/userdata/saves/ports/rgsx/rgsx_settings.json"
if [ ! -f "$SETTINGS_FILE" ]; then
echo "Creating default settings with all platforms visible..."
$RUN_USER bash -c "cat > '$SETTINGS_FILE' << 'EOF'
{
\"show_unsupported_platforms\": true
}
EOF"
echo "Default settings created!"
fi
# Run the command
cd /userdata/roms/ports/RGSX
if [ -z "$RUN_USER" ]; then
exec "$@"
else
exec $RUN_USER "$@"
fi

View File

@@ -541,7 +541,6 @@ async def main():
config.needs_redraw = True
logger.info("Scraping terminé")
import threading
thread = threading.Thread(target=scrape_async, daemon=True)
thread.start()
@@ -712,7 +711,7 @@ async def main():
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")
@@ -822,7 +821,7 @@ async def main():
config.history.append({
"platform": platform_name,
"game_name": game_name,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"message": _("download_in_progress") if _ else "Download in progress",
"url": url,
@@ -868,7 +867,7 @@ async def main():
config.history.append({
"platform": platform_name,
"game_name": game_name,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"message": _("download_in_progress") if _ else "Download in progress",
"url": url,
@@ -909,7 +908,7 @@ async def main():
logger.debug(f"[HISTORY_SEARCH] Searching in {len(config.history)} history entries for url={url[:50]}...")
for entry in config.history:
#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"]:
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
@@ -941,7 +940,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
@@ -972,7 +971,7 @@ async def main():
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
@@ -1002,7 +1001,7 @@ async def main():
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

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.3.0.7"
app_version = "2.3.1.7"
def get_application_root():
@@ -98,7 +98,7 @@ 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
OTA_VERSION_ENDPOINT = "https://raw.githubusercontent.com/RetroGameSets/RGSX/refs/heads/main/version.json" # Endpoint pour vérifier la version disponible
# URLs legacy (conservées pour compatibilité)
OTA_SERVER_URL = "https://retrogamesets.fr/softs/"
@@ -106,8 +106,8 @@ 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")
XISO_EXE = os.path.join(APP_FOLDER,"assets", "progs", "extract-xiso_win.exe")
XISO_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "extract-xiso_linux")
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")

View File

@@ -6,20 +6,29 @@ import re
import os
import datetime
import threading
import logging
import config
from config import REPEAT_DELAY, REPEAT_INTERVAL, REPEAT_ACTION_DEBOUNCE
from config import CONTROLS_CONFIG_PATH
from config import REPEAT_DELAY, REPEAT_INTERVAL, REPEAT_ACTION_DEBOUNCE, CONTROLS_CONFIG_PATH
from display import draw_validation_transition, show_toast
from network import download_rom, download_from_1fichier, is_1fichier_url, request_cancel
from utils import (
load_games, check_extension_before_download, is_extension_supported,
load_extensions_json, play_random_music, sanitize_filename,
save_music_config, load_api_keys, _get_dest_folder_name,
extract_zip, extract_rar, find_file_with_or_without_extension
extract_zip, extract_rar, find_file_with_or_without_extension, toggle_web_service_at_boot, check_web_service_status,
restart_application, generate_support_zip, load_sources,
ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string
)
from history import load_history, clear_history, add_to_history, save_history
import logging
from language import _ # Import de la fonction de traduction
from language import _, get_available_languages, set_language
from rgsx_settings import (
get_allow_unknown_extensions, set_display_grid, get_font_family, set_font_family,
get_show_unsupported_platforms, set_show_unsupported_platforms,
set_allow_unknown_extensions, get_hide_premium_systems, set_hide_premium_systems,
get_sources_mode, set_sources_mode, set_symlink_option, get_symlink_option, load_rgsx_settings, save_rgsx_settings
)
from accessibility import save_accessibility_settings
from scraper import get_game_metadata, download_image_to_surface
logger = logging.getLogger(__name__)
@@ -214,10 +223,10 @@ def _launch_next_queued_download():
is_1fichier = queue_item['is_1fichier']
task_id = queue_item['task_id']
# Mettre à jour le statut dans l'historique: queued -> downloading
# Mettre à jour le statut dans l'historique: queued -> Downloading
for entry in config.history:
if entry.get('task_id') == task_id and entry.get('status') == 'queued':
entry['status'] = 'downloading'
if entry.get('task_id') == task_id and entry.get('status') == 'Queued':
entry['status'] = 'Downloading'
entry['message'] = _("download_in_progress")
save_history(config.history)
break
@@ -640,12 +649,7 @@ def handle_controls(event, sources, joystick, screen):
load_extensions_json()
)
zip_ok = bool(pending_download[3])
allow_unknown = False
try:
from rgsx_settings import get_allow_unknown_extensions
allow_unknown = get_allow_unknown_extensions()
except Exception:
allow_unknown = False
allow_unknown = get_allow_unknown_extensions()
# Si extension non supportée ET pas en archive connu, afficher avertissement
if (not is_supported and not zip_ok) and not allow_unknown:
@@ -665,15 +669,15 @@ def handle_controls(event, sources, joystick, screen):
'is_zip_non_supported': pending_download[3],
'is_1fichier': is_1fichier_url(url),
'task_id': task_id,
'status': 'queued'
'status': 'Queued'
}
config.download_queue.append(queue_item)
# Ajouter une entrée à l'historique avec status "queued"
# Ajouter une entrée à l'historique avec status "Queued"
config.history.append({
'platform': platform,
'game_name': game_name,
'status': 'queued',
'status': 'Queued',
'url': url,
'progress': 0,
'message': _("download_queued"),
@@ -724,7 +728,6 @@ def handle_controls(event, sources, joystick, screen):
if config.pending_download and len(config.pending_download) == 4:
url, platform, game_name, is_zip_non_supported = config.pending_download
if is_1fichier_url(url):
from utils import ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string
ensure_download_provider_keys(False)
@@ -827,7 +830,7 @@ def handle_controls(event, sources, joystick, screen):
elif is_input_matched(event, "cancel") or is_input_matched(event, "history"):
if config.history and config.current_history_item < len(config.history):
entry = config.history[config.current_history_item]
if entry.get("status") in ["downloading", "Téléchargement", "Extracting"] and is_input_matched(event, "cancel"):
if entry.get("status") in ["Downloading", "Téléchargement", "Extracting"] and is_input_matched(event, "cancel"):
config.menu_state = "confirm_cancel_download"
config.confirm_cancel_selection = 0
config.needs_redraw = True
@@ -889,7 +892,7 @@ def handle_controls(event, sources, joystick, screen):
# 0 = Non, 1 = Oui
if config.confirm_clear_selection == 1: # Oui
clear_history()
config.history = []
config.history = load_history() # Recharger l'historique (conserve les téléchargements en cours)
config.current_history_item = 0
config.history_scroll_offset = 0
config.menu_state = "history"
@@ -1020,7 +1023,6 @@ def handle_controls(event, sources, joystick, screen):
# 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)
@@ -1078,7 +1080,7 @@ def handle_controls(event, sources, joystick, screen):
url = entry.get("url")
if url:
# Mettre à jour le statut
entry["status"] = "downloading"
entry["status"] = "Downloading"
entry["progress"] = 0
entry["message"] = "Téléchargement en cours"
save_history(config.history)
@@ -1209,7 +1211,6 @@ def handle_controls(event, sources, joystick, screen):
entry = config.history[config.current_history_item]
platform = entry.get("platform", "")
# threading est déjà importé en haut du fichier (ligne 8)
# Utiliser le chemin réel trouvé (avec ou sans extension)
file_path = getattr(config, 'history_actual_path', None)
@@ -1283,7 +1284,7 @@ def handle_controls(event, sources, joystick, screen):
# Mark all in-progress downloads as canceled in history
try:
for entry in getattr(config, 'history', []) or []:
if entry.get("status") in ["downloading", "Téléchargement", "Extracting"]:
if entry.get("status") in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Canceled"
entry["progress"] = 0
entry["message"] = _("download_canceled") if _ else "Download canceled"
@@ -1355,10 +1356,8 @@ def handle_controls(event, sources, joystick, screen):
config.last_state_change_time = pygame.time.get_ticks()
config.needs_redraw = True
elif config.selected_option == 5: # Restart
from utils import restart_application
restart_application(2000)
elif config.selected_option == 6: # Support
from utils import generate_support_zip
success, message, zip_path = generate_support_zip()
if success:
config.support_zip_path = zip_path
@@ -1435,7 +1434,6 @@ def handle_controls(event, sources, joystick, screen):
idx = (idx + 1) % len(layouts) if is_input_matched(event, "right") else (idx - 1) % len(layouts)
new_cols, new_rows = layouts[idx]
try:
from rgsx_settings import set_display_grid
set_display_grid(new_cols, new_rows)
except Exception as e:
logger.error(f"Erreur set_display_grid: {e}")
@@ -1443,7 +1441,6 @@ def handle_controls(event, sources, joystick, screen):
config.GRID_ROWS = new_rows
# Redémarrage automatique
try:
from utils import restart_application
config.menu_state = "restart_popup"
config.popup_message = _("popup_restarting") if _ else "Restarting..."
config.popup_timer = 2000
@@ -1453,7 +1450,6 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
# 1 font size
elif sel == 1 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
from accessibility import save_accessibility_settings
opts = getattr(config, 'font_scale_options', [0.75,1.0,1.25,1.5,1.75])
idx = getattr(config, 'current_font_scale_index', 1)
idx = max(0, idx-1) if is_input_matched(event, "left") else min(len(opts)-1, idx+1)
@@ -1473,7 +1469,6 @@ def handle_controls(event, sources, joystick, screen):
# 2 font family cycle
elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
try:
from rgsx_settings import get_font_family, set_font_family
families = getattr(config, 'FONT_FAMILIES', ["pixel"]) or ["pixel"]
current = get_font_family()
try:
@@ -1508,10 +1503,8 @@ def handle_controls(event, sources, joystick, screen):
# 3 unsupported toggle
elif sel == 3 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
try:
from rgsx_settings import get_show_unsupported_platforms, set_show_unsupported_platforms
current = get_show_unsupported_platforms()
new_val = set_show_unsupported_platforms(not current)
from utils import load_sources
load_sources()
config.popup_message = _("menu_show_unsupported_enabled") if new_val else _("menu_show_unsupported_disabled")
config.popup_timer = 3000
@@ -1521,7 +1514,6 @@ def handle_controls(event, sources, joystick, screen):
# 4 allow unknown extensions
elif sel == 4 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
try:
from rgsx_settings import get_allow_unknown_extensions, set_allow_unknown_extensions
current = get_allow_unknown_extensions()
new_val = set_allow_unknown_extensions(not current)
config.popup_message = _("menu_allow_unknown_ext_enabled") if new_val else _("menu_allow_unknown_ext_disabled")
@@ -1532,7 +1524,6 @@ def handle_controls(event, sources, joystick, screen):
# 5 hide premium systems
elif sel == 5 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
try:
from rgsx_settings import get_hide_premium_systems, set_hide_premium_systems
cur = get_hide_premium_systems()
new_val = set_hide_premium_systems(not cur)
config.popup_message = ("Premium hidden" if new_val else "Premium visible") if _ is None else (_("popup_hide_premium_on") if new_val else _("popup_hide_premium_off"))
@@ -1578,7 +1569,6 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
elif sel == 1 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
try:
from rgsx_settings import get_sources_mode, set_sources_mode
current_mode = get_sources_mode()
new_mode = set_sources_mode('custom' if current_mode == 'rgsx' else 'rgsx')
config.sources_mode = new_mode
@@ -1609,7 +1599,13 @@ def handle_controls(event, sources, joystick, screen):
# Sous-menu Settings
elif config.menu_state == "pause_settings_menu":
sel = getattr(config, 'pause_settings_selection', 0)
# Calculer le nombre total d'options selon le système
total = 4 # music, symlink, api keys, back
web_service_index = -1
if config.OPERATING_SYSTEM == "Linux":
total = 5 # music, symlink, web_service, api keys, back
web_service_index = 2
if is_input_matched(event, "up"):
config.pause_settings_selection = (sel - 1) % total
config.needs_redraw = True
@@ -1618,6 +1614,7 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
elif is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right"):
sel = getattr(config, 'pause_settings_selection', 0)
# Option 0: Music toggle
if sel == 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
config.music_enabled = not config.music_enabled
save_music_config()
@@ -1630,18 +1627,39 @@ def handle_controls(event, sources, joystick, screen):
pygame.mixer.music.stop()
config.needs_redraw = True
logger.info(f"Musique {'activée' if config.music_enabled else 'désactivée'} via settings")
# Option 1: Symlink toggle
elif sel == 1 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
from rgsx_settings import set_symlink_option, get_symlink_option
current_status = get_symlink_option()
success, message = set_symlink_option(not current_status)
config.popup_message = message
config.popup_timer = 3000 if success else 5000
config.needs_redraw = True
logger.info(f"Symlink option {'activée' if not current_status else 'désactivée'} via settings")
elif sel == 2 and is_input_matched(event, "confirm"):
# Option 2: Web Service toggle (seulement si Linux)
elif sel == web_service_index and web_service_index >= 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
current_status = check_web_service_status()
# Afficher un message de chargement
config.popup_message = _("settings_web_service_enabling") if not current_status else _("settings_web_service_disabling")
config.popup_timer = 1000
config.needs_redraw = True
# Exécuter en thread pour ne pas bloquer l'UI
def toggle_service():
success, message = toggle_web_service_at_boot(not current_status)
config.popup_message = message
config.popup_timer = 5000 if success else 7000
config.needs_redraw = True
if success:
logger.info(f"Service web {'activé' if not current_status else 'désactivé'} au démarrage")
else:
logger.error(f"Erreur toggle service web: {message}")
threading.Thread(target=toggle_service, daemon=True).start()
# Option API Keys (index varie selon Linux ou pas)
elif sel == (web_service_index + 1 if web_service_index >= 0 else 2) and is_input_matched(event, "confirm"):
config.menu_state = "pause_api_keys_status"
config.needs_redraw = True
elif sel == 3 and is_input_matched(event, "confirm"):
# Option Back (dernière option)
elif sel == (total - 1) and is_input_matched(event, "confirm"):
config.menu_state = "pause_menu"
config.last_state_change_time = pygame.time.get_ticks()
config.needs_redraw = True
@@ -1684,7 +1702,6 @@ def handle_controls(event, sources, joystick, screen):
idx = (idx - 1) % len(layouts) if is_input_matched(event, "left") else (idx + 1) % len(layouts)
new_cols, new_rows = layouts[idx]
try:
from rgsx_settings import set_display_grid
set_display_grid(new_cols, new_rows)
except Exception as e:
logger.error(f"Erreur set_display_grid: {e}")
@@ -1693,7 +1710,6 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
# Redémarrage automatique pour appliquer proprement la modification de layout
try:
from utils import restart_application
# Montrer brièvement l'info puis redémarrer
config.menu_state = "restart_popup"
config.popup_message = _("popup_restarting")
@@ -1703,7 +1719,6 @@ def handle_controls(event, sources, joystick, screen):
logger.error(f"Erreur lors du redémarrage après changement de layout: {e}")
# 1: font size adjust
elif sel == 1 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
from accessibility import save_accessibility_settings
opts = getattr(config, 'font_scale_options', [0.75, 1.0, 1.25, 1.5, 1.75])
idx = getattr(config, 'current_font_scale_index', 1)
idx = max(0, idx - 1) if is_input_matched(event, "left") else min(len(opts)-1, idx + 1)
@@ -1723,10 +1738,8 @@ def handle_controls(event, sources, joystick, screen):
# 2: toggle unsupported
elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
try:
from rgsx_settings import get_show_unsupported_platforms, set_show_unsupported_platforms
current = get_show_unsupported_platforms()
new_val = set_show_unsupported_platforms(not current)
from utils import load_sources
load_sources()
config.popup_message = _("menu_show_unsupported_enabled") if new_val else _("menu_show_unsupported_disabled")
config.popup_timer = 3000
@@ -1736,7 +1749,6 @@ def handle_controls(event, sources, joystick, screen):
# 3: toggle allow unknown extensions
elif sel == 3 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
try:
from rgsx_settings import get_allow_unknown_extensions, set_allow_unknown_extensions
current = get_allow_unknown_extensions()
new_val = set_allow_unknown_extensions(not current)
config.popup_message = _("menu_allow_unknown_ext_enabled") if new_val else _("menu_allow_unknown_ext_disabled")
@@ -1794,7 +1806,6 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
logger.debug("Passage à restart_popup")
# Redémarrage automatique
from utils import restart_application
restart_application(2000)
except Exception as e:
logger.error(f"Erreur lors de la suppression du fichier sources.json ou dossiers: {e}")
@@ -1809,7 +1820,6 @@ def handle_controls(event, sources, joystick, screen):
config.popup_timer = 2000
config.needs_redraw = True
logger.debug("Passage à restart_popup")
from utils import restart_application
restart_application(2000)
else: # Non
config.menu_state = validate_menu_state(config.previous_menu_state)
@@ -1833,8 +1843,6 @@ def handle_controls(event, sources, joystick, screen):
# Sélecteur de langue
elif config.menu_state == "language_select":
# Gestion directe des événements pour le sélecteur de langue
from language import get_available_languages, set_language, _
available_languages = get_available_languages()
if not available_languages:
@@ -1913,8 +1921,6 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
else:
btn_idx = config.selected_filter_index - total_items
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
from utils import load_sources
settings = load_rgsx_settings()
if btn_idx == 0: # all visible
config.filter_platforms_selection = [(n, False) for n, _ in config.filter_platforms_selection]
@@ -1996,7 +2002,6 @@ def handle_controls(event, sources, joystick, screen):
# Vérifier d'abord l'extension avant d'ajouter à l'historique
if is_1fichier_url(url):
from utils import ensure_download_provider_keys, missing_all_provider_keys
ensure_download_provider_keys(False)
# Avertissement si pas de clé (utilisation mode gratuit)
@@ -2011,12 +2016,7 @@ def handle_controls(event, sources, joystick, screen):
load_extensions_json()
)
zip_ok = bool(config.pending_download[3])
allow_unknown = False
try:
from rgsx_settings import get_allow_unknown_extensions
allow_unknown = get_allow_unknown_extensions()
except Exception:
allow_unknown = False
allow_unknown = get_allow_unknown_extensions()
if (not is_supported and not zip_ok) and not allow_unknown:
config.previous_menu_state = config.menu_state
config.menu_state = "extension_warning"
@@ -2047,12 +2047,7 @@ def handle_controls(event, sources, joystick, screen):
extensions_data
)
zip_ok = bool(config.pending_download[3])
allow_unknown = False
try:
from rgsx_settings import get_allow_unknown_extensions
allow_unknown = get_allow_unknown_extensions()
except Exception:
allow_unknown = False
allow_unknown = get_allow_unknown_extensions()
if (not is_supported and not zip_ok) and not allow_unknown:
config.previous_menu_state = config.menu_state
config.menu_state = "extension_warning"
@@ -2101,7 +2096,6 @@ def handle_controls(event, sources, joystick, screen):
# Vérifier d'abord l'extension avant d'ajouter à l'historique
if is_1fichier_url(url):
from utils import ensure_download_provider_keys, missing_all_provider_keys
ensure_download_provider_keys(False)
# Avertissement si pas de clé (utilisation mode gratuit)
@@ -2116,12 +2110,7 @@ def handle_controls(event, sources, joystick, screen):
load_extensions_json()
)
zip_ok = bool(config.pending_download[3])
allow_unknown = False
try:
from rgsx_settings import get_allow_unknown_extensions
allow_unknown = get_allow_unknown_extensions()
except Exception:
allow_unknown = False
allow_unknown = get_allow_unknown_extensions()
if (not is_supported and not zip_ok) and not allow_unknown:
config.previous_menu_state = config.menu_state
config.menu_state = "extension_warning"
@@ -2152,12 +2141,7 @@ def handle_controls(event, sources, joystick, screen):
extensions_data
)
zip_ok = bool(config.pending_download[3])
allow_unknown = False
try:
from rgsx_settings import get_allow_unknown_extensions
allow_unknown = get_allow_unknown_extensions()
except Exception:
allow_unknown = False
allow_unknown = get_allow_unknown_extensions()
if (not is_supported and not zip_ok) and not allow_unknown:
config.previous_menu_state = config.menu_state
config.menu_state = "extension_warning"

View File

@@ -956,7 +956,7 @@ def draw_history_list(screen):
# Cherche une entrée en cours de téléchargement pour afficher la vitesse
speed_str = ""
for entry in history:
if entry.get("status") in ["Téléchargement", "downloading"]:
if entry.get("status") in ["Téléchargement", "Downloading"]:
speed = entry.get("speed", 0.0)
if speed and speed > 0:
speed_str = f" - {speed:.2f} Mo/s"
@@ -1001,7 +1001,7 @@ def draw_history_list(screen):
current_history_item_inverted = 0
speed = 0.0
if history and history[current_history_item_inverted].get("status") in ["Téléchargement", "downloading"]:
if history and history[current_history_item_inverted].get("status") in ["Téléchargement", "Downloading"]:
speed = history[current_history_item_inverted].get("speed", 0.0)
if speed > 0:
speed_str = f"{speed:.2f} Mo/s"
@@ -1094,7 +1094,7 @@ def draw_history_list(screen):
provider_prefix = entry.get("provider_prefix") or (entry.get("provider") + ":" if entry.get("provider") else "")
# Compute status text (optimized version without redundant prefix for errors)
if status in ["Téléchargement", "downloading"]:
if status in ["Téléchargement", "Downloading"]:
# Vérifier si un message personnalisé existe (ex: mode gratuit avec attente)
custom_message = entry.get('message', '')
# Détecter les messages du mode gratuit (commencent par '[' dans toutes les langues)
@@ -2040,6 +2040,7 @@ def draw_pause_games_menu(screen, selected_index):
def draw_pause_settings_menu(screen, selected_index):
from rgsx_settings import get_symlink_option
from utils import check_web_service_status
# Music
if config.music_enabled:
music_name = config.current_music_name or ""
@@ -2057,16 +2058,34 @@ def draw_pause_settings_menu(screen, selected_index):
if ' : ' in symlink_option:
base, val = symlink_option.split(' : ',1)
symlink_option = f"{base} : < {val.strip()} >"
# Web Service at boot (only on Linux/Batocera)
web_service_txt = ""
if config.OPERATING_SYSTEM == "Linux":
web_service_enabled = check_web_service_status()
web_service_status = _("settings_web_service_enabled") if web_service_enabled else _("settings_web_service_disabled")
web_service_txt = f"{_('settings_web_service')} : < {web_service_status} >"
api_keys_txt = _("menu_api_keys_status") if _ else "API Keys"
back_txt = _("menu_back") if _ else "Back"
options = [music_option, symlink_option, api_keys_txt, back_txt]
# Construction de la liste des options
options = [music_option, symlink_option]
if web_service_txt: # Ajouter seulement si Linux/Batocera
options.append(web_service_txt)
options.extend([api_keys_txt, back_txt])
_draw_submenu_generic(screen, _("menu_settings_category") if _ else "Settings", options, selected_index)
instruction_keys = [
"instruction_settings_music",
"instruction_settings_symlink",
]
if web_service_txt:
instruction_keys.append("instruction_settings_web_service")
instruction_keys.extend([
"instruction_settings_api_keys",
"instruction_generic_back",
]
])
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
if key:
button_height = int(config.screen_height * 0.045)
@@ -2182,6 +2201,7 @@ def draw_pause_api_keys_status(screen):
hint_rect = hint_surf.get_rect(center=(config.screen_width//2, menu_y + menu_height - 30))
screen.blit(hint_surf, hint_rect)
def draw_filter_platforms_menu(screen):
"""Affiche le menu de filtrage des plateformes (afficher/masquer)."""
from rgsx_settings import load_rgsx_settings

View File

@@ -50,12 +50,20 @@ def load_history():
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 []
@@ -106,12 +114,26 @@ 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}")

View File

@@ -134,13 +134,12 @@
"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)",
"network_download_already_queued": "Dieser Download läuft bereits",
"utils_extracted": "Extrahiert: {0}",
"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}",
"utils_extraction_failed": "Extraktion fehlgeschlagen: {0}",
@@ -175,172 +174,192 @@
"api_key_empty_suffix": "leer",
"menu_hide_premium_systems": "Premium-Systeme ausblenden",
"popup_hide_premium_on": "Premium-Systeme ausgeblendet",
"popup_hide_premium_off": "Premium-Systeme sichtbar"
,"submenu_display_font_family": "Schrift"
,"popup_font_family_changed": "Schrift geändert: {0}"
,"instruction_pause_language": "Sprache der Oberfläche ändern"
,"instruction_pause_controls": "Steuerungsübersicht ansehen oder neu zuordnen"
,"instruction_pause_display": "Layout, Schriften und Systemsichtbarkeit konfigurieren"
,"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"
,"instruction_generic_back": "Zum vorherigen Menü zurückkehren"
,"instruction_display_layout": "Rasterabmessungen (Spalten × Zeilen) durchschalten"
,"instruction_display_font_size": "Schriftgröße für bessere Lesbarkeit anpassen"
,"instruction_display_font_family": "Zwischen verfügbaren Schriftarten wechseln"
,"instruction_display_show_unsupported": "Nicht in es_systems.cfg definierte Systeme anzeigen/ausblenden"
,"instruction_display_unknown_ext": "Warnung für in es_systems.cfg fehlende Dateiendungen an-/abschalten"
,"instruction_display_hide_premium": "Systeme ausblenden, die Premiumzugang erfordern über API: {providers}"
,"instruction_display_filter_platforms": "Manuell wählen welche Systeme sichtbar sind"
,"instruction_games_history": "Vergangene Downloads und Status anzeigen"
,"instruction_games_source_mode": "Zwischen RGSX oder eigener Quellliste wechseln"
,"instruction_games_update_cache": "Aktuelle Spieleliste erneut herunterladen & aktualisieren"
,"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"
,"controls_desc_confirm": "Bestätigen (z.B. A/Kreuz)"
,"controls_desc_cancel": "Abbrechen/Zurück (z.B. B/Kreis)"
,"controls_desc_up": "UP ↑"
,"controls_desc_down": "DOWN ↓"
,"controls_desc_left": "LEFT ←"
,"controls_desc_right": "RIGHT →"
,"controls_desc_page_up": "Schnell nach oben (z.B. LT/L2)"
,"controls_desc_page_down": "Schnell nach unten (z.B. RT/R2)"
,"controls_desc_history": "Verlauf öffnen (z.B. Y/Dreieck)"
,"controls_desc_clear_history": "Downloads: Mehrfachauswahl / Verlauf: Leeren (z.B. X/Quadrat)"
,"controls_desc_filter": "Filtermodus: Öffnen/Bestätigen (z.B. Select)"
,"controls_desc_delete": "Filtermodus: Zeichen löschen (z.B. LB/L1)"
,"controls_desc_space": "Filtermodus: Leerzeichen hinzufügen (z.B. RB/R1)"
,"controls_desc_start": "Pausenmenü öffnen (z.B. Start)"
,"controls_mapping_title": "Steuerungszuordnung"
,"controls_mapping_instruction": "Zum Bestätigen gedrückt halten:"
,"controls_mapping_waiting": "Warte auf eine Taste oder einen Button..."
,"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)"
"popup_hide_premium_off": "Premium-Systeme sichtbar",
"submenu_display_font_family": "Schrift",
"popup_font_family_changed": "Schrift geändert: {0}",
"instruction_pause_language": "Sprache der Oberfläche ändern",
"instruction_pause_controls": "Steuerungsübersicht ansehen oder neu zuordnen",
"instruction_pause_display": "Layout, Schriften und Systemsichtbarkeit konfigurieren",
"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",
"instruction_generic_back": "Zum vorherigen Menü zurückkehren",
"instruction_display_layout": "Rasterabmessungen (Spalten × Zeilen) durchschalten",
"instruction_display_font_size": "Schriftgröße für bessere Lesbarkeit anpassen",
"instruction_display_font_family": "Zwischen verfügbaren Schriftarten wechseln",
"instruction_display_show_unsupported": "Nicht in es_systems.cfg definierte Systeme anzeigen/ausblenden",
"instruction_display_unknown_ext": "Warnung für in es_systems.cfg fehlende Dateiendungen an-/abschalten",
"instruction_display_hide_premium": "Systeme ausblenden, die Premiumzugang erfordern über API: {providers}",
"instruction_display_filter_platforms": "Manuell wählen welche Systeme sichtbar sind",
"instruction_games_history": "Vergangene Downloads und Status anzeigen",
"instruction_games_source_mode": "Zwischen RGSX oder eigener Quellliste wechseln",
"instruction_games_update_cache": "Aktuelle Spieleliste erneut herunterladen & aktualisieren",
"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 ↑",
"controls_desc_down": "DOWN ↓",
"controls_desc_left": "LEFT ←",
"controls_desc_right": "RIGHT →",
"controls_desc_page_up": "Schnell nach oben (z.B. LT/L2)",
"controls_desc_page_down": "Schnell nach unten (z.B. RT/R2)",
"controls_desc_history": "Verlauf öffnen (z.B. Y/Dreieck)",
"controls_desc_clear_history": "Downloads: Mehrfachauswahl / Verlauf: Leeren (z.B. X/Quadrat)",
"controls_desc_filter": "Filtermodus: Öffnen/Bestätigen (z.B. Select)",
"controls_desc_delete": "Filtermodus: Zeichen löschen (z.B. LB/L1)",
"controls_desc_space": "Filtermodus: Leerzeichen hinzufügen (z.B. RB/R1)",
"controls_desc_start": "Pausenmenü öffnen (z.B. Start)",
"controls_mapping_title": "Steuerungszuordnung",
"controls_mapping_instruction": "Zum Bestätigen gedrückt halten:",
"controls_mapping_waiting": "Warte auf eine Taste oder einen Button...",
"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)",
"download_already_present": " (bereits vorhanden)",
"network_download_ok": "Download erfolgreich: {0}",
"web_filter_region": "Region",
"web_filter_hide_non_release": "Demos/Betas/Protos ausblenden",
"web_filter_regex_mode": "Regex-Suche aktivieren",
"web_filter_one_rom_per_game": "Eine ROM pro Spiel",
"web_filter_configure_priority": "Regions-Prioritätsreihenfolge konfigurieren",
"filter_all": "Alles auswählen",
"filter_none": "Alles abwählen",
"filter_apply": "Filter anwenden",
"filter_back": "Zurück"
}

View File

@@ -8,7 +8,7 @@
"loading_test_connection": "Testing connection...",
"loading_download_data": "Downloading initial Data folder...",
"loading_progress": "Progress: {0}%",
"loading_check_updates": "Checking for updates... Please wait...",
"loading_check_updates": "Update in progress... Please wait...",
"error_check_updates_failed": "Failed to check updates.",
"loading_downloading_games_images": "Downloading games and images...",
"loading_extracting_data": "Extracting initial Data folder...",
@@ -93,8 +93,8 @@
"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...",
"controls_action_close_history": "Close History",
"network_checking_updates": "Update in progress please wait...",
"network_update_available": "Update available: {0}",
"network_extracting_update": "Extracting update...",
"network_update_completed": "Update completed",
@@ -174,174 +174,192 @@
"menu_games": "Games",
"api_keys_hint_manage": "Put your keys in {path}",
"api_key_empty_suffix": "empty",
"menu_hide_premium_systems": "Hide Premium systems"
,"popup_hide_premium_on": "Premium systems hidden"
,"popup_hide_premium_off": "Premium systems visible"
,"submenu_display_font_family": "Font"
,"popup_font_family_changed": "Font changed: {0}",
"menu_hide_premium_systems": "Hide Premium systems",
"popup_hide_premium_on": "Premium systems hidden",
"popup_hide_premium_off": "Premium systems visible",
"submenu_display_font_family": "Font",
"popup_font_family_changed": "Font changed: {0}",
"instruction_pause_language": "Change the interface language",
"instruction_pause_controls": "View control layout or start remapping",
"instruction_pause_display": "Configure layout, fonts and system visibility",
"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"
,"instruction_generic_back": "Return to the previous menu"
,"instruction_display_layout": "Cycle grid dimensions (columns × rows)"
,"instruction_display_font_size": "Adjust text scale for readability"
,"instruction_display_font_family": "Switch between available font families"
,"instruction_display_show_unsupported": "Show/hide systems not defined in es_systems.cfg"
,"instruction_display_unknown_ext": "Enable/disable warning for file extensions absent from es_systems.cfg"
,"instruction_display_hide_premium": "Hide systems requiring premium access via API: {providers}"
,"instruction_display_filter_platforms": "Manually choose which systems are visible"
,"instruction_games_history": "List past downloads and statuses"
,"instruction_games_source_mode": "Switch between RGSX or your own custom list source"
,"instruction_games_update_cache": "Redownload & refresh current games list"
,"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"
,"controls_desc_confirm": "Confirm (e.g. A/Cross)"
,"controls_desc_cancel": "Cancel/Back (e.g. B/Circle)"
,"controls_desc_up": "UP ↑"
,"controls_desc_down": "DOWN ↓"
,"controls_desc_left": "LEFT ←"
,"controls_desc_right": "RIGHT →"
,"controls_desc_page_up": "Fast scroll up (e.g. LT/L2)"
,"controls_desc_page_down": "Fast scroll down (e.g. RT/R2)"
,"controls_desc_history": "Open history (e.g. Y/Triangle)"
,"controls_desc_clear_history": "Downloads: Multi-select / History: Clear (e.g. X/Square)"
,"controls_desc_filter": "Filter mode: Open/Confirm (e.g. Select)"
,"controls_desc_delete": "Filter mode: Delete character (e.g. LB/L1)"
,"controls_desc_space": "Filter mode: Add space (e.g. RB/R1)"
,"controls_desc_start": "Open pause menu (e.g. Start)"
,"controls_mapping_title": "Controls mapping"
,"controls_mapping_instruction": "Hold to confirm the mapping:"
,"controls_mapping_waiting": "Waiting for a key or button..."
,"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)"
"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",
"instruction_generic_back": "Return to the previous menu",
"instruction_display_layout": "Cycle grid dimensions (columns × rows)",
"instruction_display_font_size": "Adjust text scale for readability",
"instruction_display_font_family": "Switch between available font families",
"instruction_display_show_unsupported": "Show/hide systems not defined in es_systems.cfg",
"instruction_display_unknown_ext": "Enable/disable warning for file extensions absent from es_systems.cfg",
"instruction_display_hide_premium": "Hide systems requiring premium access via API: {providers}",
"instruction_display_filter_platforms": "Manually choose which systems are visible",
"instruction_games_history": "List past downloads and statuses",
"instruction_games_source_mode": "Switch between RGSX or your own custom list source",
"instruction_games_update_cache": "Redownload & refresh current games list",
"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 ↑",
"controls_desc_down": "DOWN ↓",
"controls_desc_left": "LEFT ←",
"controls_desc_right": "RIGHT →",
"controls_desc_page_up": "Fast scroll up (e.g. LT/L2)",
"controls_desc_page_down": "Fast scroll down (e.g. RT/R2)",
"controls_desc_history": "Open history (e.g. Y/Triangle)",
"controls_desc_clear_history": "Downloads: Multi-select / History: Clear (e.g. X/Square)",
"controls_desc_filter": "Filter mode: Open/Confirm (e.g. Select)",
"controls_desc_delete": "Filter mode: Delete character (e.g. LB/L1)",
"controls_desc_space": "Filter mode: Add space (e.g. RB/R1)",
"controls_desc_start": "Open pause menu (e.g. Start)",
"controls_mapping_title": "Controls mapping",
"controls_mapping_instruction": "Hold to confirm the mapping:",
"controls_mapping_waiting": "Waiting for a key or button...",
"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)",
"web_filter_region": "Region",
"web_filter_hide_non_release": "Hide Demos/Betas/Protos",
"web_filter_regex_mode": "Enable Regex Search",
"web_filter_one_rom_per_game": "One ROM Per Game",
"web_filter_configure_priority": "Configure region priority order",
"filter_all": "Check All",
"filter_none": "Uncheck All",
"filter_apply": "Apply Filter",
"filter_back": "Back"
}

View File

@@ -89,7 +89,7 @@
"popup_restarting": "Reiniciando...",
"controls_action_clear_history": "Vaciar historial",
"controls_action_history": "Historial / Descargas",
"controls_action_close_history": "Cerrar Historial",
"controls_action_close_history": "Cerrar Historial",
"controls_action_delete": "Eliminar",
"controls_action_space": "Espacio",
"controls_action_start": "Ayuda / Configuración",
@@ -140,6 +140,7 @@
"download_in_progress": "Descarga en curso...",
"download_queued": "En cola de descarga",
"download_started": "Descarga iniciada",
"network_download_already_queued": "Esta descarga ya está en curso",
"utils_extracted": "Extraído: {0}",
"utils_corrupt_zip": "Archivo ZIP corrupto: {0}",
"utils_permission_denied": "Permiso denegado durante la extracción: {0}",
@@ -175,172 +176,190 @@
"api_key_empty_suffix": "vacío",
"menu_hide_premium_systems": "Ocultar sistemas Premium",
"popup_hide_premium_on": "Sistemas Premium ocultos",
"popup_hide_premium_off": "Sistemas Premium visibles"
,"submenu_display_font_family": "Fuente"
,"popup_font_family_changed": "Fuente cambiada: {0}"
,"instruction_pause_language": "Cambiar el idioma de la interfaz"
,"instruction_pause_controls": "Ver esquema de controles o remapear"
,"instruction_pause_display": "Configurar distribución, fuentes y visibilidad de sistemas"
,"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"
,"instruction_generic_back": "Volver al menú anterior"
,"instruction_display_layout": "Alternar dimensiones de la cuadrícula (columnas × filas)"
,"instruction_display_font_size": "Ajustar tamaño del texto para mejor legibilidad"
,"instruction_display_font_family": "Cambiar entre familias de fuentes disponibles"
,"instruction_display_show_unsupported": "Mostrar/ocultar sistemas no definidos en es_systems.cfg"
,"instruction_display_unknown_ext": "Activar/desactivar aviso para extensiones no presentes en es_systems.cfg"
,"instruction_display_hide_premium": "Ocultar sistemas que requieren acceso premium vía API: {providers}"
,"instruction_display_filter_platforms": "Elegir manualmente qué sistemas son visibles"
,"instruction_games_history": "Ver descargas pasadas y su estado"
,"instruction_games_source_mode": "Cambiar entre lista RGSX o fuente personalizada"
,"instruction_games_update_cache": "Volver a descargar y refrescar la lista de juegos"
,"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"
,"controls_desc_confirm": "Confirmar (ej. A/Cruz)"
,"controls_desc_cancel": "Cancelar/Volver (ej. B/Círculo)"
,"controls_desc_up": "UP ↑"
,"controls_desc_down": "DOWN ↓"
,"controls_desc_left": "LEFT ←"
,"controls_desc_right": "RIGHT →"
,"controls_desc_page_up": "Desplazamiento rápido - (ej. LT/L2)"
,"controls_desc_page_down": "Desplazamiento rápido + (ej. RT/R2)"
,"controls_desc_history": "Abrir historial (ej. Y/Triángulo)"
,"controls_desc_clear_history": "Descargas: Selección múltiple / Historial: Limpiar (ej. X/Cuadrado)"
,"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ej. Select)"
,"controls_desc_delete": "Modo filtro: Eliminar carácter (ej. LB/L1)"
,"controls_desc_space": "Modo filtro: Añadir espacio (ej. RB/R1)"
,"controls_desc_start": "Abrir menú pausa (ej. Start)"
,"controls_mapping_title": "Asignación de controles"
,"controls_mapping_instruction": "Mantén para confirmar la asignación:"
,"controls_mapping_waiting": "Esperando una tecla o botón..."
,"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)"
"popup_hide_premium_off": "Sistemas Premium visibles",
"submenu_display_font_family": "Fuente",
"popup_font_family_changed": "Fuente cambiada: {0}",
"instruction_pause_language": "Cambiar el idioma de la interfaz",
"instruction_pause_controls": "Ver esquema de controles o remapear",
"instruction_pause_display": "Configurar distribución, fuentes y visibilidad de sistemas",
"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",
"instruction_generic_back": "Volver al menú anterior",
"instruction_display_layout": "Alternar dimensiones de la cuadrícula (columnas × filas)",
"instruction_display_font_size": "Ajustar tamaño del texto para mejor legibilidad",
"instruction_display_font_family": "Cambiar entre familias de fuentes disponibles",
"instruction_display_show_unsupported": "Mostrar/ocultar sistemas no definidos en es_systems.cfg",
"instruction_display_unknown_ext": "Activar/desactivar aviso para extensiones no presentes en es_systems.cfg",
"instruction_display_hide_premium": "Ocultar sistemas que requieren acceso premium vía API: {providers}",
"instruction_display_filter_platforms": "Elegir manualmente qué sistemas son visibles",
"instruction_games_history": "Ver descargas pasadas y su estado",
"instruction_games_source_mode": "Cambiar entre lista RGSX o fuente personalizada",
"instruction_games_update_cache": "Volver a descargar y refrescar la lista de juegos",
"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 ↑",
"controls_desc_down": "DOWN ↓",
"controls_desc_left": "LEFT ←",
"controls_desc_right": "RIGHT →",
"controls_desc_page_up": "Desplazamiento rápido - (ej. LT/L2)",
"controls_desc_page_down": "Desplazamiento rápido + (ej. RT/R2)",
"controls_desc_history": "Abrir historial (ej. Y/Triángulo)",
"controls_desc_clear_history": "Descargas: Selección múltiple / Historial: Limpiar (ej. X/Cuadrado)",
"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ej. Select)",
"controls_desc_delete": "Modo filtro: Eliminar carácter (ej. LB/L1)",
"controls_desc_space": "Modo filtro: Añadir espacio (ej. RB/R1)",
"controls_desc_start": "Abrir menú pausa (ej. Start)",
"controls_mapping_title": "Asignación de controles",
"controls_mapping_instruction": "Mantén para confirmar la asignación:",
"controls_mapping_waiting": "Esperando una tecla o botón...",
"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)",
"web_filter_region": "Región",
"web_filter_hide_non_release": "Ocultar Demos/Betas/Protos",
"web_filter_regex_mode": "Activar búsqueda Regex",
"web_filter_one_rom_per_game": "Una ROM por juego",
"web_filter_configure_priority": "Configurar orden de prioridad de regiones",
"filter_all": "Marcar todo",
"filter_none": "Desmarcar todo",
"filter_apply": "Aplicar filtro",
"filter_back": "Volver"
}

View File

@@ -141,7 +141,7 @@
"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_extracted": "Extrait: {0}",
"utils_corrupt_zip": "Archive ZIP corrompue: {0}",
"utils_permission_denied": "Permission refusée lors de l'extraction: {0}",
"utils_extraction_failed": "Échec de l'extraction: {0}",
@@ -176,172 +176,190 @@
"api_key_empty_suffix": "vide",
"menu_hide_premium_systems": "Masquer systèmes Premium",
"popup_hide_premium_on": "Systèmes Premium masqués",
"popup_hide_premium_off": "Systèmes Premium visibles"
,"submenu_display_font_family": "Police"
,"popup_font_family_changed": "Police changée : {0}"
,"instruction_pause_language": "Changer la langue de l'interface"
,"instruction_pause_controls": "Afficher la configuration ou remapper"
,"instruction_pause_display": "Agencer l'affichage, polices et systèmes visibles"
,"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"
,"instruction_generic_back": "Revenir au menu précédent"
,"instruction_display_layout": "Changer les dimensions de la grille"
,"instruction_display_font_size": "Ajuster la taille du texte pour la lisibilité"
,"instruction_display_font_family": "Basculer entre les polices disponibles"
,"instruction_display_show_unsupported": "Afficher/masquer systèmes absents de es_systems.cfg"
,"instruction_display_unknown_ext": "Avertir ou non pour extensions absentes de es_systems.cfg"
,"instruction_display_hide_premium": "Masquer les systèmes nécessitant un accès premium via API: {providers}"
,"instruction_display_filter_platforms": "Choisir manuellement les systèmes visibles"
,"instruction_games_history": "Lister les téléchargements passés et leur statut"
,"instruction_games_source_mode": "Basculer entre liste RGSX ou source personnalisée"
,"instruction_games_update_cache": "Retélécharger & rafraîchir la liste des jeux"
,"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"
,"controls_desc_confirm": "Valider (ex: A/Croix)"
,"controls_desc_cancel": "Annuler/Retour (ex: B/Rond)"
,"controls_desc_up": "UP ↑"
,"controls_desc_down": "DOWN ↓"
,"controls_desc_left": "LEFT ←"
,"controls_desc_right": "RIGHT →"
,"controls_desc_page_up": "Défilement Rapide - (ex: LT/L2)"
,"controls_desc_page_down": "Défilement Rapide + (ex: RT/R2)"
,"controls_desc_history": "Ouvrir l'historique (ex: Y/Triangle)"
,"controls_desc_clear_history": "Téléchargements : Sélection multiple / Historique : Vider (ex: X/Carré)"
,"controls_desc_filter": "Mode Filtre : Ouvrir/Valider (ex: Select)"
,"controls_desc_delete": "Mode Filtre : Supprimer caractère (ex: LB/L1)"
,"controls_desc_space": "Mode Filtre : Ajouter espace (ex: RB/R1)"
,"controls_desc_start": "Ouvrir le menu pause (ex: Start)"
,"controls_mapping_title": "Configuration des contrôles"
,"controls_mapping_instruction": "Maintenez pour confirmer l'association :"
,"controls_mapping_waiting": "En attente d'une touche ou d'un bouton..."
,"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)"
"popup_hide_premium_off": "Systèmes Premium visibles",
"submenu_display_font_family": "Police",
"popup_font_family_changed": "Police changée : {0}",
"instruction_pause_language": "Changer la langue de l'interface",
"instruction_pause_controls": "Afficher la configuration ou remapper",
"instruction_pause_display": "Agencer l'affichage, polices et systèmes visibles",
"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",
"instruction_generic_back": "Revenir au menu précédent",
"instruction_display_layout": "Changer les dimensions de la grille",
"instruction_display_font_size": "Ajuster la taille du texte pour la lisibilité",
"instruction_display_font_family": "Basculer entre les polices disponibles",
"instruction_display_show_unsupported": "Afficher/masquer systèmes absents de es_systems.cfg",
"instruction_display_unknown_ext": "Avertir ou non pour extensions absentes de es_systems.cfg",
"instruction_display_hide_premium": "Masquer les systèmes nécessitant un accès premium via API: {providers}",
"instruction_display_filter_platforms": "Choisir manuellement les systèmes visibles",
"instruction_games_history": "Lister les téléchargements passés et leur statut",
"instruction_games_source_mode": "Basculer entre liste RGSX ou source personnalisée",
"instruction_games_update_cache": "Retélécharger & rafraîchir la liste des jeux",
"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 ↑",
"controls_desc_down": "DOWN ↓",
"controls_desc_left": "LEFT ←",
"controls_desc_right": "RIGHT →",
"controls_desc_page_up": "Défilement Rapide - (ex: LT/L2)",
"controls_desc_page_down": "Défilement Rapide + (ex: RT/R2)",
"controls_desc_history": "Ouvrir l'historique (ex: Y/Triangle)",
"controls_desc_clear_history": "Téléchargements : Sélection multiple / Historique : Vider (ex: X/Carré)",
"controls_desc_filter": "Mode Filtre : Ouvrir/Valider (ex: Select)",
"controls_desc_delete": "Mode Filtre : Supprimer caractère (ex: LB/L1)",
"controls_desc_space": "Mode Filtre : Ajouter espace (ex: RB/R1)",
"controls_desc_start": "Ouvrir le menu pause (ex: Start)",
"controls_mapping_title": "Configuration des contrôles",
"controls_mapping_instruction": "Maintenez pour confirmer l'association :",
"controls_mapping_waiting": "En attente d'une touche ou d'un bouton...",
"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)",
"web_filter_region": "Région",
"web_filter_hide_non_release": "Masquer Démos/Betas/Protos",
"web_filter_regex_mode": "Activer recherche Regex",
"web_filter_one_rom_per_game": "Une ROM par jeu",
"web_filter_configure_priority": "Configurer l'ordre de priorité des régions",
"filter_all": "Tout cocher",
"filter_none": "Tout décocher",
"filter_apply": "Appliquer filtre",
"filter_back": "Retour"
}

View File

@@ -89,7 +89,7 @@
"popup_restarting": "Riavvio...",
"controls_action_clear_history": "Cancella cronologia",
"controls_action_history": "Cronologia / Downloads",
"controls_action_close_history": "Chiudi Cronologia",
"controls_action_close_history": "Chiudi Cronologia",
"controls_action_delete": "Elimina",
"controls_action_space": "Spazio",
"controls_action_start": "Aiuto / Impostazioni",
@@ -128,9 +128,16 @@
"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",
"network_download_already_queued": "Questo download è già in corso",
"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}",
"symlink_option_enabled": "Opzione symlink abilitata",
"symlink_option_disabled": "Opzione symlink disabilitata",
"menu_games_source_prefix": "Sorgente giochi",
"controls_category_navigation": "Navigazione",
"controls_category_main_actions": "Azioni principali",
"controls_category_downloads": "Download",
@@ -140,9 +147,6 @@
"controls_confirm_select": "Conferma/Seleziona",
"controls_cancel_back": "Annulla/Indietro",
"controls_filter_search": "Filtro/Ricerca",
"symlink_option_enabled": "Opzione symlink abilitata",
"symlink_option_disabled": "Opzione symlink disabilitata",
"menu_games_source_prefix": "Sorgente giochi",
"games_source_rgsx": "RGSX",
"sources_mode_rgsx_select_info": "RGSX: aggiorna l'elenco dei giochi",
"games_source_custom": "Personalizzato",
@@ -169,172 +173,193 @@
"api_key_empty_suffix": "vuoto",
"menu_hide_premium_systems": "Nascondi sistemi Premium",
"popup_hide_premium_on": "Sistemi Premium nascosti",
"popup_hide_premium_off": "Sistemi Premium visibili"
,"submenu_display_font_family": "Font"
,"popup_font_family_changed": "Font cambiato: {0}"
,"instruction_pause_language": "Cambiare la lingua dell'interfaccia"
,"instruction_pause_controls": "Vedere schema controlli o avviare rimappatura"
,"instruction_pause_display": "Configurare layout, font e visibilità sistemi"
,"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"
,"instruction_generic_back": "Tornare al menu precedente"
,"instruction_display_layout": "Scorrere dimensioni griglia (colonne × righe)"
,"instruction_display_font_size": "Regolare dimensione testo per leggibilità"
,"instruction_display_font_family": "Cambiare famiglia di font disponibile"
,"instruction_display_show_unsupported": "Mostrare/nascondere sistemi non definiti in es_systems.cfg"
,"instruction_display_unknown_ext": "Attivare/disattivare avviso per estensioni assenti in es_systems.cfg"
,"instruction_display_hide_premium": "Nascondere sistemi che richiedono accesso premium via API: {providers}"
,"instruction_display_filter_platforms": "Scegliere manualmente quali sistemi sono visibili"
,"instruction_games_history": "Elencare download passati e stato"
,"instruction_games_source_mode": "Passare tra elenco RGSX o sorgente personalizzata"
,"instruction_games_update_cache": "Riscaria e aggiorna l'elenco dei giochi"
,"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"
,"controls_desc_confirm": "Confermare (es. A/Croce)"
,"controls_desc_cancel": "Annullare/Indietro (es. B/Cerchio)"
,"controls_desc_up": "UP ↑"
,"controls_desc_down": "DOWN ↓"
,"controls_desc_left": "LEFT ←"
,"controls_desc_right": "RIGHT →"
,"controls_desc_page_up": "Scorrimento rapido su (es. LT/L2)"
,"controls_desc_page_down": "Scorrimento rapido giù (es. RT/R2)"
,"controls_desc_history": "Aprire cronologia (es. Y/Triangolo)"
,"controls_desc_clear_history": "Download: Selezione multipla / Cronologia: Svuotare (es. X/Quadrato)"
,"controls_desc_filter": "Modalità filtro: Aprire/Confermare (es. Select)"
,"controls_desc_delete": "Modalità filtro: Eliminare carattere (es. LB/L1)"
,"controls_desc_space": "Modalità filtro: Aggiungere spazio (es. RB/R1)"
,"controls_desc_start": "Aprire menu pausa (es. Start)"
,"controls_mapping_title": "Mappatura controlli"
,"controls_mapping_instruction": "Tieni premuto per confermare l'associazione:"
,"controls_mapping_waiting": "In attesa di un tasto o pulsante..."
,"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)"
"popup_hide_premium_off": "Sistemi Premium visibili",
"submenu_display_font_family": "Font",
"popup_font_family_changed": "Font cambiato: {0}",
"instruction_pause_language": "Cambiare la lingua dell'interfaccia",
"instruction_pause_controls": "Vedere schema controlli o avviare rimappatura",
"instruction_pause_display": "Configurare layout, font e visibilità sistemi",
"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",
"instruction_generic_back": "Tornare al menu precedente",
"instruction_display_layout": "Scorrere dimensioni griglia (colonne × righe)",
"instruction_display_font_size": "Regolare dimensione testo per leggibilità",
"instruction_display_font_family": "Cambiare famiglia di font disponibile",
"instruction_display_show_unsupported": "Mostrare/nascondere sistemi non definiti in es_systems.cfg",
"instruction_display_unknown_ext": "Attivare/disattivare avviso per estensioni assenti in es_systems.cfg",
"instruction_display_hide_premium": "Nascondere sistemi che richiedono accesso premium via API: {providers}",
"instruction_display_filter_platforms": "Scegliere manualmente quali sistemi sono visibili",
"instruction_games_history": "Elencare download passati e stato",
"instruction_games_source_mode": "Passare tra elenco RGSX o sorgente personalizzata",
"instruction_games_update_cache": "Riscaria e aggiorna l'elenco dei giochi",
"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 ↑",
"controls_desc_down": "DOWN ↓",
"controls_desc_left": "LEFT ←",
"controls_desc_right": "RIGHT →",
"controls_desc_page_up": "Scorrimento rapido su (es. LT/L2)",
"controls_desc_page_down": "Scorrimento rapido giù (es. RT/R2)",
"controls_desc_history": "Aprire cronologia (es. Y/Triangolo)",
"controls_desc_clear_history": "Download: Selezione multipla / Cronologia: Svuotare (es. X/Quadrato)",
"controls_desc_filter": "Modalità filtro: Aprire/Confermare (es. Select)",
"controls_desc_delete": "Modalità filtro: Eliminare carattere (es. LB/L1)",
"controls_desc_space": "Modalità filtro: Aggiungere spazio (es. RB/R1)",
"controls_desc_start": "Aprire menu pausa (es. Start)",
"controls_mapping_title": "Mappatura controlli",
"controls_mapping_instruction": "Tieni premuto per confermare l'associazione:",
"controls_mapping_waiting": "In attesa di un tasto o pulsante...",
"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)",
"accessibility_font_size": "Dimensione carattere: {0}",
"confirm_cancel_download": "Annullare il download corrente?",
"controls_help_title": "Guida ai controlli",
"web_filter_region": "Regione",
"web_filter_hide_non_release": "Nascondi Demo/Beta/Proto",
"web_filter_regex_mode": "Attiva ricerca Regex",
"web_filter_one_rom_per_game": "Una ROM per gioco",
"web_filter_configure_priority": "Configura ordine di priorità delle regioni",
"filter_all": "Seleziona tutto",
"filter_none": "Deseleziona tutto",
"filter_apply": "Applica filtro",
"filter_back": "Indietro"
}

View File

@@ -89,7 +89,7 @@
"popup_restarting": "Reiniciando...",
"controls_action_clear_history": "Limpar histórico",
"controls_action_history": "Histórico / Downloads",
"controls_action_close_history": "Fechar Histórico",
"controls_action_close_history": "Fechar Histórico",
"controls_action_delete": "Deletar",
"controls_action_space": "Espaço",
"controls_action_start": "Ajuda / Configurações",
@@ -128,7 +128,13 @@
"download_in_progress": "Download em andamento...",
"download_queued": "Na fila de download",
"download_started": "Download iniciado",
"accessibility_font_size": "Tamanho da fonte: {0}",
"network_download_already_queued": "Este download já está em andamento",
"utils_extracted": "Extraído: {0}",
"utils_corrupt_zip": "Arquivo ZIP corrupto: {0}",
"utils_permission_denied": "Permissão negada durante a extração: {0}",
"utils_extraction_failed": "Falha na extração: {0}",
"utils_unrar_unavailable": "Comando unrar não disponível",
"utils_rar_list_failed": "Falha ao listar arquivos RAR: {0}",
"confirm_cancel_download": "Cancelar download atual?",
"controls_help_title": "Ajuda de Controles",
"controls_category_navigation": "Navegação",
@@ -169,172 +175,191 @@
"api_key_empty_suffix": "vazio",
"menu_hide_premium_systems": "Ocultar sistemas Premium",
"popup_hide_premium_on": "Sistemas Premium ocultos",
"popup_hide_premium_off": "Sistemas Premium visíveis"
,"submenu_display_font_family": "Fonte"
,"popup_font_family_changed": "Fonte alterada: {0}"
,"instruction_pause_language": "Alterar o idioma da interface"
,"instruction_pause_controls": "Ver esquema de controles ou iniciar remapeamento"
,"instruction_pause_display": "Configurar layout, fontes e visibilidade de sistemas"
,"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"
,"instruction_generic_back": "Voltar ao menu anterior"
,"instruction_display_layout": "Alternar dimensões da grade (colunas × linhas)"
,"instruction_display_font_size": "Ajustar tamanho do texto para legibilidade"
,"instruction_display_font_family": "Alternar entre famílias de fontes disponíveis"
,"instruction_display_show_unsupported": "Mostrar/ocultar sistemas não definidos em es_systems.cfg"
,"instruction_display_unknown_ext": "Ativar/desativar aviso para extensões ausentes em es_systems.cfg"
,"instruction_display_hide_premium": "Ocultar sistemas que exigem acesso premium via API: {providers}"
,"instruction_display_filter_platforms": "Escolher manualmente quais sistemas são visíveis"
,"instruction_games_history": "Listar downloads anteriores e status"
,"instruction_games_source_mode": "Alternar entre lista RGSX ou fonte personalizada"
,"instruction_games_update_cache": "Baixar novamente e atualizar a lista de jogos"
,"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"
,"controls_desc_confirm": "Confirmar (ex. A/Cruz)"
,"controls_desc_cancel": "Cancelar/Voltar (ex. B/Círculo)"
,"controls_desc_up": "UP ↑"
,"controls_desc_down": "DOWN ↓"
,"controls_desc_left": "LEFT ←"
,"controls_desc_right": "RIGHT →"
,"controls_desc_page_up": "Rolagem rápida para cima (ex. LT/L2)"
,"controls_desc_page_down": "Rolagem rápida para baixo (ex. RT/R2)"
,"controls_desc_history": "Abrir histórico (ex. Y/Triângulo)"
,"controls_desc_clear_history": "Downloads: Seleção múltipla / Histórico: Limpar (ex. X/Quadrado)"
,"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ex. Select)"
,"controls_desc_delete": "Modo filtro: Deletar caractere (ex. LB/L1)"
,"controls_desc_space": "Modo filtro: Adicionar espaço (ex. RB/R1)"
,"controls_desc_start": "Abrir menu pausa (ex. Start)"
,"controls_mapping_title": "Mapeamento de controles"
,"controls_mapping_instruction": "Mantenha para confirmar o mapeamento:"
,"controls_mapping_waiting": "Aguardando uma tecla ou botão..."
,"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)"
"popup_hide_premium_off": "Sistemas Premium visíveis",
"submenu_display_font_family": "Fonte",
"popup_font_family_changed": "Fonte alterada: {0}",
"instruction_pause_language": "Alterar o idioma da interface",
"instruction_pause_controls": "Ver esquema de controles ou iniciar remapeamento",
"instruction_pause_display": "Configurar layout, fontes e visibilidade de sistemas",
"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",
"instruction_generic_back": "Voltar ao menu anterior",
"instruction_display_layout": "Alternar dimensões da grade (colunas × linhas)",
"instruction_display_font_size": "Ajustar tamanho do texto para legibilidade",
"instruction_display_font_family": "Alternar entre famílias de fontes disponíveis",
"instruction_display_show_unsupported": "Mostrar/ocultar sistemas não definidos em es_systems.cfg",
"instruction_display_unknown_ext": "Ativar/desativar aviso para extensões ausentes em es_systems.cfg",
"instruction_display_hide_premium": "Ocultar sistemas que exigem acesso premium via API: {providers}",
"instruction_display_filter_platforms": "Escolher manualmente quais sistemas são visíveis",
"instruction_games_history": "Listar downloads anteriores e status",
"instruction_games_source_mode": "Alternar entre lista RGSX ou fonte personalizada",
"instruction_games_update_cache": "Baixar novamente e atualizar a lista de jogos",
"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 ↑",
"controls_desc_down": "DOWN ↓",
"controls_desc_left": "LEFT ←",
"controls_desc_right": "RIGHT →",
"controls_desc_page_up": "Rolagem rápida para cima (ex. LT/L2)",
"controls_desc_page_down": "Rolagem rápida para baixo (ex. RT/R2)",
"controls_desc_history": "Abrir histórico (ex. Y/Triângulo)",
"controls_desc_clear_history": "Downloads: Seleção múltipla / Histórico: Limpar (ex. X/Quadrado)",
"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ex. Select)",
"controls_desc_delete": "Modo filtro: Deletar caractere (ex. LB/L1)",
"controls_desc_space": "Modo filtro: Adicionar espaço (ex. RB/R1)",
"controls_desc_start": "Abrir menu pausa (ex. Start)",
"controls_mapping_title": "Mapeamento de controles",
"controls_mapping_instruction": "Mantenha para confirmar o mapeamento:",
"controls_mapping_waiting": "Aguardando uma tecla ou botão...",
"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)",
"accessibility_font_size": "Tamanho da fonte: {0}",
"web_filter_region": "Região",
"web_filter_hide_non_release": "Ocultar Demos/Betas/Protos",
"web_filter_regex_mode": "Ativar pesquisa Regex",
"web_filter_one_rom_per_game": "Uma ROM por jogo",
"web_filter_configure_priority": "Configurar ordem de prioridade das regiões",
"filter_all": "Marcar tudo",
"filter_none": "Desmarcar tudo",
"filter_apply": "Aplicar filtro",
"filter_back": "Voltar"
}

View File

@@ -338,7 +338,7 @@ def download_1fichier_free_mode(url, dest_dir, session, log_callback=None, progr
return (True, filepath, None)
except Exception as e:
error_msg = f"Error downloading with free mode: {str(e)}"
error_msg = f"Error Downloading with free mode: {str(e)}"
_log(error_msg)
logger.error(error_msg, exc_info=True)
return (False, None, error_msg)
@@ -455,10 +455,15 @@ async def check_for_updates():
response = requests.get(OTA_VERSION_ENDPOINT, timeout=5)
response.raise_for_status()
if response.headers.get("content-type") != "application/json":
# Accepter différents content-types (application/json, text/plain, text/html)
content_type = response.headers.get("content-type", "")
allowed_types = ["application/json", "text/plain", "text/html"]
if not any(allowed in content_type for allowed in allowed_types):
raise ValueError(
f"Le fichier version.json n'est pas un JSON valide (type de contenu : {response.headers.get('content-type')})"
f"Le fichier version.json n'est pas un JSON valide (type de contenu : {content_type})"
)
version_data = response.json()
latest_version = version_data.get("version")
logger.debug(f"Version distante : {latest_version}, version locale : {config.app_version}")
@@ -679,7 +684,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
def download_thread():
try:
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "downloading"
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "Downloading"
# pour que l'interface web puisse afficher le téléchargement en cours
# TOUJOURS charger l'historique existant depuis le fichier pour éviter d'écraser les anciennes entrées
@@ -690,8 +695,8 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
for entry in config.history:
if entry.get("url") == url:
entry_exists = True
# Réinitialiser le status à "downloading"
entry["status"] = "downloading"
# Réinitialiser le status à "Downloading"
entry["status"] = "Downloading"
entry["progress"] = 0
entry["downloaded_size"] = 0
entry["platform"] = platform
@@ -706,7 +711,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
"platform": platform,
"game_name": game_name,
"url": url,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"downloaded_size": 0,
"total_size": 0,
@@ -1126,7 +1131,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
try:
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["status"] = "Extracting"
entry["progress"] = 0
entry["message"] = "Préparation de l'extraction..."
@@ -1212,7 +1217,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
@@ -1252,7 +1257,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
# NOTE: On ne touche PAS au timestamp qui doit rester celui de création
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["downloaded_size"] = downloaded
entry["total_size"] = total_size
entry["speed"] = speed
@@ -1284,7 +1289,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
logger.debug(f"[DRAIN_QUEUE] Processing final message: success={success}, message={message[:100] if message else 'None'}")
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
@@ -1406,7 +1411,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
link = url.split('&af=')[0]
logger.debug(f"URL nettoyée: {link}")
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "downloading"
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "Downloading"
# pour que l'interface web puisse afficher le téléchargement en cours
# Charger l'historique existant depuis le fichier
@@ -1418,8 +1423,8 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
for entry in config.history:
if entry.get("url") == url:
entry_exists = True
# Réinitialiser le status à "downloading"
entry["status"] = "downloading"
# Réinitialiser le status à "Downloading"
entry["status"] = "Downloading"
entry["progress"] = 0
entry["downloaded_size"] = 0
entry["platform"] = platform
@@ -1434,7 +1439,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
"platform": platform,
"game_name": game_name,
"url": url,
"status": "downloading",
"status": "Downloading",
"progress": 0,
"downloaded_size": 0,
"total_size": 0,
@@ -1857,7 +1862,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
with free_lock:
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
if "url" in entry and entry["url"] == url and entry["status"] == "Downloading":
entry["progress"] = int(pct) if pct else 0
entry["downloaded_size"] = downloaded
entry["total_size"] = total
@@ -1928,7 +1933,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
else:
logger.error(f"Échec téléchargement gratuit: {error_msg}")
result[0] = False
result[1] = f"Error downloading with free mode: {error_msg}"
result[1] = f"Error Downloading with free mode: {error_msg}"
return
except Exception as e:
@@ -2061,7 +2066,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
with lock:
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
if "url" in entry and entry["url"] == url and entry["status"] == "Downloading":
entry["total_size"] = total_size
config.needs_redraw = True
break
@@ -2099,7 +2104,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
with lock:
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
if "url" in entry and entry["url"] == url and entry["status"] == "Downloading":
progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
entry["progress"] = progress_percent
@@ -2226,7 +2231,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
success, message = data[1], data[2]
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message
@@ -2251,7 +2256,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
entry["progress"] = progress_percent
entry["status"] = "Téléchargement"
entry["downloaded_size"] = downloaded
@@ -2281,7 +2286,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
logger.debug(f"[1F_DRAIN_QUEUE] Processing final message: success={success}, message={message[:100] if message else 'None'}")
if isinstance(config.history, list):
for entry in config.history:
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
entry["status"] = "Download_OK" if success else "Erreur"
entry["progress"] = 100 if success else 0
entry["message"] = message

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

@@ -69,7 +69,8 @@ def load_rgsx_settings():
},
"show_unsupported_platforms": False,
"allow_unknown_extensions": False,
"roms_folder": ""
"roms_folder": "",
"web_service_at_boot": False
}
try:

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
"""
Module de scraping pour récupérer les métadonnées des jeux depuis TheGamesDB.net
Module de scraping pour récupérer les métadonnées des jeux depuis TheGamesDB.net API v1
"""
import logging
import requests
@@ -9,138 +9,167 @@ 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
# Clé API publique pour TheGamesDB
API_KEY = "bdbb4a1ce5f1c12c1bcc119aeb4d4923d3887e22ad336d576e9b9e5da5ecaa3c"
API_BASE_URL = "https://api.thegamesdb.net/v1"
# Mapping des noms de plateformes vers leurs IDs sur TheGamesDB API
# Documentation: https://api.thegamesdb.net/#/Platforms
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",
"3DO Interactive Multiplayer": 25,
"3DS": 4912,
"Adventure Vision": 4974,
"Amiga CD32": 4947,
"Amiga CDTV": 4947,
"Amiga OCS ECS": 4911,
"Apple II": 4942,
"Apple IIGS": 4942,
"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,
"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,
"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,
"Naomi": 23,
"Naomi 2": 23,
"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,
"Saturn": 17,
"ScummVM": 1,
"Sega 32X": 33,
"Sega Chihiro": 23,
"Sega Pico": 4958,
"SG-1000": 4949,
"Sharp X1": 4977,
"SuFami Turbo": 6,
"Super A'Can": 4918,
"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"
"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 clean_game_name(game_name):
"""
Nettoie le nom du jeu en supprimant les extensions et tags
Args:
game_name (str): Nom brut du jeu
Returns:
str: Nom nettoyé
"""
clean_name = game_name
# Supprimer les extensions communes
extensions = ['.zip', '.7z', '.rar', '.iso', '.chd', '.cue', '.bin', '.gdi', '.cdi',
'.nsp', '.xci', '.wbfs', '.rvz', '.gcz', '.wad', '.3ds', '.cia']
for ext in extensions:
if clean_name.lower().endswith(ext):
clean_name = clean_name[:-len(ext)]
# Supprimer les tags entre parenthèses et crochets
clean_name = re.sub(r'\s*[\(\[].*?[\)\]]', '', clean_name)
return clean_name.strip()
def get_game_metadata(game_name, platform_name):
"""
Récupère les métadonnées complètes d'un jeu depuis TheGamesDB.net
Récupère les métadonnées complètes d'un jeu depuis TheGamesDB.net API
Args:
game_name (str): Nom du jeu à rechercher
@@ -150,100 +179,128 @@ def get_game_metadata(game_name, platform_name):
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}'")
clean_name = clean_game_name(game_name)
logger.info(f"Recherche métadonnées pour: '{clean_name}' sur plateforme '{platform_name}'")
# Obtenir l'ID de la plateforme
platform_id = PLATFORM_MAPPING.get(platform_name)
if not platform_id:
logger.warning(f"Plateforme '{platform_name}' non trouvée dans le mapping")
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)
# Endpoint: Games/ByGameName
# Documentation: https://api.thegamesdb.net/#/Games/GamesbyName
url = f"{API_BASE_URL}/Games/ByGameName"
params = {
"apikey": API_KEY,
"name": clean_name,
"filter[platform]": platform_id,
"fields": "players,publishers,genres,overview,last_updated,rating,platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates",
"include": "boxart"
}
logger.debug(f"Requête API: {url} avec name='{clean_name}', platform={platform_id}")
response = requests.get(url, params=params, timeout=15)
if response.status_code != 200:
logger.error(f"Erreur HTTP {response.status_code}: {response.text}")
return {"error": f"Erreur HTTP {response.status_code}"}
html_content = response.text
data = response.json()
# Trouver la première carte avec class 'card border-primary'
card_start = html_content.find('div class="card border-primary"')
if card_start == -1:
# Vérifier si des résultats ont été trouvés
if "data" not in data or "games" not in data["data"] or not data["data"]["games"]:
logger.warning(f"Aucun résultat trouvé pour '{clean_name}'")
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}")
# Prendre le premier résultat (meilleure correspondance)
games = data["data"]["games"]
game = games[0]
game_id = game.get("id")
# 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}")
logger.info(f"Jeu trouvé: '{game.get('game_title')}' (ID: {game_id})")
# 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}")
# Construire l'URL de la page du jeu
game_page_url = f"https://thegamesdb.net/game.php?id={game_id}"
# Si on a l'URL de la page, récupérer la description et le genre
description = None
# Extraire les métadonnées de base
description = game.get("overview", "").strip() or None
release_date = game.get("release_date", "").strip() or None
# Extraire les genres
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}")
if "genres" in game and game["genres"]:
genre_ids = game["genres"]
# Les noms des genres sont dans data.genres
if "genres" in data["data"]:
genre_names = []
for gid in genre_ids:
if str(gid) in data["data"]["genres"]:
genre_names.append(data["data"]["genres"][str(gid)]["name"])
if genre_names:
genre = ", ".join(genre_names)
# Extraire l'image de couverture (boxart)
# Utiliser l'endpoint dédié /v1/Games/Images pour récupérer les images du jeu
image_url = None
try:
images_url = f"{API_BASE_URL}/Games/Images"
images_params = {
"apikey": API_KEY,
"games_id": game_id,
"filter[type]": "boxart"
}
except Exception as e:
logger.warning(f"Erreur lors de la récupération de la page du jeu: {e}")
logger.debug(f"Récupération des images pour game_id={game_id}")
images_response = requests.get(images_url, params=images_params, timeout=10)
if images_response.status_code == 200:
images_data = images_response.json()
# Récupérer l'URL de base
base_url_original = ""
if "data" in images_data and "base_url" in images_data["data"]:
base_url_original = images_data["data"]["base_url"].get("original", "")
# Parcourir les images
if "data" in images_data and "images" in images_data["data"]:
images_dict = images_data["data"]["images"]
# Les images sont organisées par game_id
if str(game_id) in images_dict:
game_images = images_dict[str(game_id)]
# Chercher front boxart en priorité
for img in game_images:
if img.get("type") == "boxart" and img.get("side") == "front":
filename = img.get("filename")
if filename:
image_url = f"{base_url_original}{filename}"
logger.info(f"Image front trouvée: {image_url}")
break
# Si pas de front, prendre n'importe quelle boxart
if not image_url:
for img in game_images:
if img.get("type") == "boxart":
filename = img.get("filename")
if filename:
image_url = f"{base_url_original}{filename}"
logger.info(f"Image boxart trouvée: {image_url}")
break
# Si toujours rien, prendre la première image
if not image_url and game_images:
filename = game_images[0].get("filename")
if filename:
image_url = f"{base_url_original}{filename}"
logger.info(f"Première image trouvée: {image_url}")
else:
logger.warning(f"Erreur lors de la récupération des images: HTTP {images_response.status_code}")
except Exception as img_error:
logger.warning(f"Erreur lors de la récupération des images: {img_error}")
# Construire le résultat
result = {
@@ -254,15 +311,16 @@ def get_game_metadata(game_name, platform_name):
"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"
logger.info(f"Métadonnées récupérées: image={bool(image_url)}, desc={bool(description)}, genre={bool(genre)}, date={bool(release_date)}")
return result
except requests.RequestException as e:
logger.error(f"Erreur lors de la requête: {str(e)}")
logger.error(f"Erreur lors de la requête API: {str(e)}")
return {"error": f"Erreur réseau: {str(e)}"}
except Exception as e:
logger.error(f"Erreur inattendue: {str(e)}", exc_info=True)
return {"error": f"Erreur: {str(e)}"}
def download_image_to_surface(image_url):

View File

@@ -0,0 +1,241 @@
/* ===== ACCESSIBILITY & FOCUS MANAGEMENT ===== */
/* Focus visible - show focus for keyboard navigation only */
:focus {
outline: 2px solid transparent;
outline-offset: 2px;
}
:focus-visible {
outline: 3px solid var(--color-primary);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
/* Skip to main content link (keyboard accessible) */
.skip-to-main {
position: absolute;
top: -40px;
left: 0;
background: var(--color-primary);
color: white;
padding: var(--spacing-md) var(--spacing-lg);
text-decoration: none;
z-index: var(--z-tooltip);
border-radius: var(--radius-sm);
}
.skip-to-main:focus {
top: 0;
}
/* Visually hidden but accessible to screen readers */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* ===== LOADING SKELETONS ===== */
@keyframes shimmer {
0% {
background-position: -1000px 0;
}
100% {
background-position: 1000px 0;
}
}
.skeleton {
background: linear-gradient(
90deg,
var(--color-bg-secondary) 0%,
var(--color-bg-tertiary) 50%,
var(--color-bg-secondary) 100%
);
background-size: 1000px 100%;
animation: shimmer 2s infinite;
}
.skeleton-card {
background: var(--color-bg-primary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
border: 1px solid var(--color-border-light);
}
.skeleton-card::before {
content: '';
display: block;
width: 100%;
height: 200px;
background: var(--color-bg-secondary);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-md);
animation: shimmer 2s infinite;
}
.skeleton-card::after {
content: '';
display: block;
width: 80%;
height: 16px;
background: var(--color-bg-secondary);
border-radius: var(--radius-sm);
animation: shimmer 2s infinite;
}
.skeleton-text {
height: 1em;
background: var(--color-bg-secondary);
border-radius: var(--radius-sm);
margin: var(--spacing-md) 0;
animation: shimmer 2s infinite;
}
.skeleton-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--color-bg-secondary);
display: inline-block;
animation: shimmer 2s infinite;
}
/* Grid of skeleton cards */
.skeleton-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-2xl);
}
/* ===== BUTTON & LINK ACCESSIBILITY ===== */
button, a[role="button"], input[type="button"], input[type="submit"] {
position: relative;
transition: all var(--transition-base);
border: none;
font-family: inherit;
font-size: inherit;
cursor: pointer;
}
/* Disabled state */
button:disabled,
[role="button"][aria-disabled="true"],
input[type="button"]:disabled,
input[type="submit"]:disabled {
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
}
/* Ensure buttons have minimum touch target (44x44px WCAG) */
button, a[role="button"] {
min-height: 44px;
min-width: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--spacing-md) var(--spacing-lg);
}
/* ===== FORM ACCESSIBILITY ===== */
label {
display: block;
margin-bottom: var(--spacing-sm);
font-weight: 500;
color: var(--color-text-primary);
}
input, select, textarea {
font-family: inherit;
font-size: inherit;
padding: var(--spacing-md);
border: 2px solid var(--color-border);
border-radius: var(--radius-md);
transition: border-color var(--transition-base);
}
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
border-color: var(--color-primary);
outline: none;
}
/* Error state for inputs */
input[aria-invalid="true"],
select[aria-invalid="true"],
textarea[aria-invalid="true"] {
border-color: var(--color-danger);
}
/* ===== MODAL & DIALOG ACCESSIBILITY ===== */
[role="dialog"],
.modal {
z-index: var(--z-modal);
}
[role="dialog"]::backdrop,
.modal-backdrop {
background-color: var(--color-overlay-bg);
z-index: var(--z-modal-backdrop);
}
/* Ensure modal is positioned and styled appropriately */
[role="dialog"] {
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-bg-primary);
padding: var(--spacing-2xl);
max-height: 90vh;
overflow-y: auto;
box-shadow: var(--shadow-xl);
}
/* ===== HIGH CONTRAST SUPPORT ===== */
@media (prefers-contrast: more) {
button,
a[role="button"],
input[type="button"],
input[type="submit"] {
border: 3px solid currentColor;
font-weight: bold;
}
input,
select,
textarea {
border-width: 3px;
}
:focus-visible {
outline-width: 4px;
}
}
/* ===== REDUCED MOTION SUPPORT ===== */
@media (prefers-reduced-motion: reduce) {
.skeleton,
.skeleton-card::before,
.skeleton-card::after,
.skeleton-text,
.skeleton-avatar {
animation: none;
background: var(--color-bg-secondary);
}
}

View File

@@ -0,0 +1,426 @@
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 90%;
margin: 0 auto;
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
position: relative;
}
header h1 { font-size: 2.5em; margin-bottom: 10px; }
header p { opacity: 0.9; font-size: 1.1em; }
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Navigation mobile dans le header */
.mobile-tabs {
display: none;
justify-content: space-around;
padding: 15px 10px 10px 10px;
gap: 5px;
}
.mobile-tab {
flex: 1;
background: rgba(255,255,255,0.2);
border: none;
color: white;
padding: 12px 5px;
border-radius: 8px;
cursor: pointer;
font-size: 24px;
transition: all 0.3s;
backdrop-filter: blur(10px);
}
.mobile-tab:hover {
background: rgba(255,255,255,0.3);
}
.mobile-tab.active {
background: rgba(255,255,255,0.4);
transform: scale(1.1);
}
.tabs {
display: flex;
background: #f5f5f5;
border-bottom: 2px solid #ddd;
}
.tab {
flex: 1;
padding: 15px;
text-align: center;
cursor: pointer;
background: #f5f5f5;
border: none;
font-size: 16px;
color: #333;
transition: all 0.3s;
}
.tab:hover { background: #e0e0e0; }
.tab.active {
background: white;
border-bottom: 3px solid #667eea;
font-weight: bold;
}
.tab.support-btn {
margin-left: auto;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 20px;
padding: 8px 20px;
}
.tab.support-btn:hover {
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
transform: scale(1.05);
}
.content {
padding: 30px;
min-height: 400px;
}
.loading {
text-align: center;
padding: 50px;
font-size: 1.2em;
color: #666;
}
.platform-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.platform-card {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
padding: 20px;
border-radius: 12px;
cursor: pointer;
transition: transform 0.3s, box-shadow 0.3s;
text-align: center;
}
.platform-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.4);
}
.platform-card img {
width: 200px;
height: 200px;
object-fit: contain;
margin-bottom: 15px;
filter: drop-shadow(0 4px 6px rgba(0,0,0,0.3));
}
.platform-card h3 {
margin-bottom: 10px;
color: white;
font-size: 1.1em;
min-height: 2.5em;
display: flex;
align-items: center;
justify-content: center;
}
.platform-card .count {
background: #667eea;
color: white;
padding: 5px 15px;
border-radius: 20px;
display: inline-block;
margin-top: 10px;
}
.search-box {
margin-bottom: 20px;
position: relative;
}
.search-box input {
width: 100%;
padding: 12px 45px 12px 15px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
background: white;
color: #333;
}
.search-box input:focus {
outline: none;
border-color: #667eea;
}
.search-box .search-icon {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
color: #999;
font-size: 18px;
}
.search-box .clear-search {
position: absolute;
right: 45px;
top: 50%;
transform: translateY(-50%);
background: #dc3545;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
display: none;
font-size: 14px;
line-height: 1;
}
.search-box .clear-search:hover {
background: #c82333;
}
.sort-btn {
background: #e0e0e0;
color: #333;
border: 2px solid #999;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 0.9em;
font-weight: 500;
transition: all 0.2s;
}
.sort-btn:hover {
background: #d0d0d0;
border-color: #666;
}
.sort-btn.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.filter-section {
margin-top: 12px;
margin-bottom: 12px;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
}
.filter-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
margin-bottom: 8px;
}
.filter-row:last-child {
margin-bottom: 0;
}
.filter-label {
font-weight: bold;
margin-right: 4px;
}
.region-btn {
background: #e0e0e0;
color: #333;
border: 2px solid #999;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 0.85em;
font-weight: 500;
transition: all 0.2s;
}
.region-btn:hover {
background: #d0d0d0;
border-color: #666;
}
.region-btn.active {
background: #28a745;
color: white;
border-color: #28a745;
}
.region-btn.excluded {
background: #dc3545;
color: white;
border-color: #dc3545;
}
.filter-checkbox {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
font-size: 0.9em;
color: #333;
}
.filter-checkbox input[type="checkbox"] {
cursor: pointer;
width: 16px;
height: 16px;
}
.games-list {
max-height: 600px;
overflow-y: auto;
}
.game-item {
background: #f9f9f9;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.2s;
}
.game-item:hover { background: #f0f0f0; }
.game-name { font-weight: 500; flex: 1; }
.game-size {
background: #667eea;
color: white;
padding: 5px 10px;
border-radius: 5px;
font-size: 0.9em;
margin-right: 10px;
}
.download-btn {
background: transparent;
color: #28a745;
border: none;
padding: 8px;
border-radius: 5px;
cursor: pointer;
font-size: 1.5em;
transition: all 0.2s;
min-width: 40px;
}
.download-btn:hover {
background: rgba(40, 167, 69, 0.1);
transform: scale(1.1);
}
.download-btn:disabled {
color: #6c757d;
cursor: not-allowed;
}
.back-btn {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
margin-bottom: 20px;
font-size: 16px;
}
.back-btn:hover { background: #5568d3; }
.info-grid {
display: grid;
gap: 15px;
}
.info-item {
background: #f9f9f9;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.info-item strong { display: block; margin-bottom: 5px; color: #667eea; }
.history-item {
background: #f9f9f9;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
border-left: 4px solid #28a745;
}
.history-item.error { border-left-color: #dc3545; }
/* Media Queries pour responsive */
@media (max-width: 768px) {
body {
padding: 10px;
}
.container {
border-radius: 0;
}
header {
padding: 20px 20px 10px 20px;
}
header h1 {
font-size: 1.8em;
}
.tabs {
display: none;
}
.mobile-tabs {
display: flex;
}
.content {
padding: 15px;
}
.platform-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
}
.platform-card {
padding: 15px;
}
.platform-card img {
width: 80px;
height: 80px;
}
.platform-card h3 {
font-size: 0.9em;
min-height: 2em;
}
.game-item {
flex-wrap: wrap;
padding: 10px;
}
.game-name {
font-size: 0.9em;
flex: 1 1 100%;
margin-bottom: 8px;
}
.download-btn-group {
display: flex;
gap: 4px;
}
}
@media (max-width: 480px) {
header h1 {
font-size: 1.5em;
}
.platform-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
}
.platform-card {
padding: 10px;
}
.platform-card img {
width: 60px;
height: 60px;
}
.platform-card h3 {
font-size: 0.85em;
}
.platform-card .count {
font-size: 0.8em;
padding: 3px 10px;
}
}

View File

@@ -0,0 +1,120 @@
/* ===== THEMING SYSTEM WITH CSS CUSTOM PROPERTIES ===== */
/* This file defines the color scheme and can be easily swapped for alternative themes */
:root {
/* Primary Colors - Brand Identity */
--color-primary: #667eea;
--color-primary-dark: #764ba2;
--color-primary-light: #8b9dff;
--color-primary-rgb: 102, 126, 234;
/* Secondary Colors - Accents */
--color-success: #28a745;
--color-warning: #ffc107;
--color-danger: #dc3545;
--color-info: #17a2b8;
/* Neutral Colors - Layout & Typography */
--color-bg-primary: #ffffff;
--color-bg-secondary: #f5f5f5;
--color-bg-tertiary: #f9f9f9;
--color-text-primary: #333333;
--color-text-secondary: #666666;
--color-text-muted: #999999;
--color-text-inverse: #ffffff;
/* Border & Divider Colors */
--color-border: #dddddd;
--color-border-light: #eeeeee;
--color-divider: #e0e0e0;
/* Semantic Colors */
--color-card-bg: #2c3e50;
--color-card-border: #34495e;
--color-overlay-bg: rgba(0, 0, 0, 0.7);
--color-overlay-light: rgba(0, 0, 0, 0.3);
/* Gradients */
--gradient-primary: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
--gradient-reverse: linear-gradient(135deg, var(--color-primary-dark) 0%, var(--color-primary) 100%);
/* Shadows */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.4);
--shadow-xl: 0 20px 60px rgba(0, 0, 0, 0.3);
/* Spacing Scale */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 12px;
--spacing-lg: 16px;
--spacing-xl: 20px;
--spacing-2xl: 30px;
--spacing-3xl: 40px;
/* Border Radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
/* Transitions */
--transition-fast: 0.2s ease-in-out;
--transition-base: 0.3s ease-in-out;
--transition-slow: 0.5s ease-in-out;
/* Typography */
--font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-size-3xl: 2.5rem;
/* Z-index Scale */
--z-dropdown: 100;
--z-sticky: 200;
--z-fixed: 300;
--z-modal-backdrop: 400;
--z-modal: 500;
--z-notification: 600;
--z-tooltip: 700;
}
/* ===== DARK MODE SUPPORT ===== */
@media (prefers-color-scheme: dark) {
:root {
--color-bg-primary: #1a1a1a;
--color-bg-secondary: #2d2d2d;
--color-bg-tertiary: #3a3a3a;
--color-text-primary: #e0e0e0;
--color-text-secondary: #b0b0b0;
--color-text-muted: #808080;
--color-border: #404040;
--color-border-light: #3a3a3a;
}
}
/* ===== HIGH CONTRAST MODE SUPPORT ===== */
@media (prefers-contrast: more) {
:root {
--color-primary: #0043b3;
--color-primary-dark: #002e7a;
--color-text-primary: #000000;
--color-text-secondary: #333333;
--color-border: #000000;
--shadow-lg: 0 2px 4px rgba(0, 0, 0, 0.5);
}
}
/* ===== REDUCED MOTION SUPPORT ===== */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

View File

@@ -0,0 +1,194 @@
/**
* Accessibility Utilities Module
* Handles keyboard navigation, focus management, and ARIA updates
*/
const A11y = {
/**
* Manage focus trap for modals (keep focus within modal)
*/
trapFocus(element) {
const focusableElements = element.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
element.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
}
} else {
if (document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
});
// Set initial focus to first element
firstElement?.focus();
},
/**
* Restore focus to element after modal closes
*/
savedFocusElement: null,
saveFocus() {
this.savedFocusElement = document.activeElement;
},
restoreFocus() {
if (this.savedFocusElement && this.savedFocusElement.focus) {
this.savedFocusElement.focus();
}
},
/**
* Announce changes to screen readers with live regions
*/
announceToScreenReader(message, priority = 'polite') {
let liveRegion = document.querySelector(`[role="status"][aria-live="${priority}"]`);
if (!liveRegion) {
liveRegion = document.createElement('div');
liveRegion.setAttribute('role', 'status');
liveRegion.setAttribute('aria-live', priority);
liveRegion.className = 'sr-only';
document.body.appendChild(liveRegion);
}
liveRegion.textContent = message;
// Clear after announcement
setTimeout(() => {
liveRegion.textContent = '';
}, 3000);
},
/**
* Handle Enter/Space key on clickable elements
*/
makeKeyboardClickable(element, callback) {
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
callback();
}
});
},
/**
* Arrow key navigation for grids
*/
setupGridNavigation(gridSelector) {
const grid = document.querySelector(gridSelector);
if (!grid) return;
const items = Array.from(grid.children);
const colCount = Math.ceil(Math.sqrt(items.length));
items.forEach((item, index) => {
item.setAttribute('tabindex', index === 0 ? '0' : '-1');
item.addEventListener('keydown', (e) => {
let newIndex = index;
switch (e.key) {
case 'ArrowLeft':
newIndex = index === 0 ? items.length - 1 : index - 1;
e.preventDefault();
break;
case 'ArrowRight':
newIndex = index === items.length - 1 ? 0 : index + 1;
e.preventDefault();
break;
case 'ArrowUp':
newIndex = Math.max(0, index - colCount);
e.preventDefault();
break;
case 'ArrowDown':
newIndex = Math.min(items.length - 1, index + colCount);
e.preventDefault();
break;
case 'Home':
newIndex = 0;
e.preventDefault();
break;
case 'End':
newIndex = items.length - 1;
e.preventDefault();
break;
default:
return;
}
items[index].setAttribute('tabindex', '-1');
items[newIndex].setAttribute('tabindex', '0');
items[newIndex].focus();
});
});
},
/**
* Update ARIA attributes for dynamic content
*/
updateAriaLabel(element, label) {
element.setAttribute('aria-label', label);
},
updateAriaLive(element, region = 'polite') {
element.setAttribute('aria-live', region);
element.setAttribute('aria-atomic', 'true');
},
/**
* Set loading state with ARIA
*/
setLoadingState(element, isLoading) {
if (isLoading) {
element.setAttribute('aria-busy', 'true');
element.setAttribute('disabled', 'true');
} else {
element.removeAttribute('aria-busy');
element.removeAttribute('disabled');
}
},
/**
* Create accessible loading skeleton
*/
createSkeletonLoader(containerId, itemCount = 6) {
const container = document.getElementById(containerId);
if (!container) return;
container.innerHTML = '';
for (let i = 0; i < itemCount; i++) {
const skeleton = document.createElement('div');
skeleton.className = 'skeleton-card';
skeleton.setAttribute('aria-hidden', 'true');
container.appendChild(skeleton);
}
},
/**
* Remove skeleton loader
*/
removeSkeletonLoader(containerId) {
const container = document.getElementById(containerId);
if (!container) return;
const skeletons = container.querySelectorAll('.skeleton-card');
skeletons.forEach(s => s.remove());
}
};
// Export for use in app.js
if (typeof module !== 'undefined' && module.exports) {
module.exports = A11y;
}

2199
ports/RGSX/static/js/app.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ except Exception:
pygame = None # type: ignore
import glob
import threading
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
from rgsx_settings import load_rgsx_settings, save_rgsx_settings, get_allow_unknown_extensions
import zipfile
import time
import random
@@ -25,6 +25,7 @@ from history import save_history
from language import _
from datetime import datetime
import sys
import tempfile
logger = logging.getLogger(__name__)
@@ -85,9 +86,7 @@ def generate_support_zip():
Returns:
tuple: (success: bool, message: str, zip_path: str ou None)
"""
import zipfile
import tempfile
from datetime import datetime
try:
# Créer un fichier ZIP temporaire
@@ -162,6 +161,146 @@ DO NOT share this file publicly as it may contain sensitive information.
logger.error(f"Erreur lors de la génération du fichier de support: {e}")
return (False, str(e), None)
def toggle_web_service_at_boot(enable: bool):
"""Active ou désactive le service web au démarrage de Batocera.
Args:
enable: True pour activer, False pour désactiver
Returns:
tuple: (success: bool, message: str)
"""
try:
# Vérifier si on est sur un système compatible (Linux avec batocera-services)
if config.OPERATING_SYSTEM != "Linux":
return (False, "Web service auto-start is only available on Batocera/Linux systems")
services_dir = "/userdata/system/services"
service_file = os.path.join(services_dir, "rgsx_web")
source_file = os.path.join(config.APP_FOLDER, "assets", "progs", "rgsx_web")
if enable:
# Mode ENABLE
logger.debug("Activation du service web au démarrage...")
# 1. Créer le dossier services s'il n'existe pas
try:
os.makedirs(services_dir, exist_ok=True)
logger.debug(f"Dossier services vérifié/créé: {services_dir}")
except Exception as e:
error_msg = f"Failed to create services directory: {str(e)}"
logger.error(error_msg)
return (False, error_msg)
# 2. Copier le fichier rgsx_web
try:
if not os.path.exists(source_file):
error_msg = f"Source service file not found: {source_file}"
logger.error(error_msg)
return (False, error_msg)
shutil.copy2(source_file, service_file)
os.chmod(service_file, 0o755) # Rendre exécutable
logger.debug(f"Fichier service copié et rendu exécutable: {service_file}")
except Exception as e:
error_msg = f"Failed to copy service file: {str(e)}"
logger.error(error_msg)
return (False, error_msg)
# 3. Activer le service avec batocera-services
try:
result = subprocess.run(
['batocera-services', 'enable', 'rgsx_web'],
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0:
error_msg = f"batocera-services enable failed: {result.stderr}"
logger.error(error_msg)
return (False, error_msg)
logger.debug(f"Service activé: {result.stdout}")
except FileNotFoundError:
error_msg = "batocera-services command not found"
logger.error(error_msg)
return (False, error_msg)
except Exception as e:
error_msg = f"Failed to enable service: {str(e)}"
logger.error(error_msg)
return (False, error_msg)
# 4. Démarrer le service immédiatement
try:
result = subprocess.run(
['batocera-services', 'start', 'rgsx_web'],
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0:
# Le service peut ne pas démarrer si déjà en cours, ce n'est pas grave
logger.warning(f"batocera-services start warning: {result.stderr}")
else:
logger.debug(f"Service démarré: {result.stdout}")
except Exception as e:
logger.warning(f"Failed to start service (non-critical): {str(e)}")
success_msg = _("settings_web_service_success_enabled") if _ else "Web service enabled at boot"
logger.info(success_msg)
# Sauvegarder l'état dans rgsx_settings.json
settings = load_rgsx_settings()
settings["web_service_at_boot"] = True
save_rgsx_settings(settings)
return (True, success_msg)
else:
# Mode DISABLE
logger.debug("Désactivation du service web au démarrage...")
# 1. Désactiver le service avec batocera-services
try:
result = subprocess.run(
['batocera-services', 'disable', 'rgsx_web'],
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0:
error_msg = f"batocera-services disable failed: {result.stderr}"
logger.error(error_msg)
return (False, error_msg)
logger.debug(f"Service désactivé: {result.stdout}")
except FileNotFoundError:
error_msg = "batocera-services command not found"
logger.error(error_msg)
return (False, error_msg)
except Exception as e:
error_msg = f"Failed to disable service: {str(e)}"
logger.error(error_msg)
return (False, error_msg)
success_msg = _("settings_web_service_success_disabled") if _ else "✓ Web service disabled at boot"
logger.info(success_msg)
# Sauvegarder l'état dans rgsx_settings.json
settings = load_rgsx_settings()
settings["web_service_at_boot"] = False
save_rgsx_settings(settings)
return (True, success_msg)
except Exception as e:
error_msg = f"Unexpected error: {str(e)}"
logger.exception(error_msg)
return (False, error_msg)
_extensions_cache = None # type: ignore
_extensions_json_regenerated = False
@@ -385,7 +524,6 @@ def check_extension_before_download(url, platform, game_name):
# Autoriser si l'utilisateur a choisi d'autoriser les extensions inconnues
allow_unknown = False
try:
from rgsx_settings import get_allow_unknown_extensions
allow_unknown = get_allow_unknown_extensions()
except Exception:
allow_unknown = False
@@ -849,7 +987,7 @@ def _update_extraction_progress(url, extracted_size, total_size, lock, last_save
with lock:
if isinstance(config.history, list):
for entry in config.history:
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "downloading"]:
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "Downloading"]:
if "url" in entry and entry["url"] == url:
progress_percent = int(extracted_size / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
@@ -907,8 +1045,12 @@ def _handle_special_platforms(dest_dir, archive_path, before_dirs, iso_before=No
before_items: Set de tous les éléments (fichiers+dossiers) avant extraction (pour DOS)
"""
# Xbox: conversion ISO
xbox_dir = os.path.join(config.ROMS_FOLDER, "xbox")
if dest_dir == xbox_dir and iso_before is not None:
# Gérer les deux cas: symlink activé (xbox/xbox) ou désactivé (xbox)
xbox_dir_normal = os.path.join(config.ROMS_FOLDER, "xbox")
xbox_dir_symlink = os.path.join(config.ROMS_FOLDER, "xbox", "xbox")
is_xbox = (dest_dir == xbox_dir_normal or dest_dir == xbox_dir_symlink)
if is_xbox and iso_before is not None:
iso_after = set()
for root, dirs, files in os.walk(dest_dir):
for file in files:
@@ -963,6 +1105,16 @@ def _handle_special_platforms(dest_dir, archive_path, before_dirs, iso_before=No
if not success:
return False, error_msg
# ScummVM: organisation en dossiers + fichier .scummvm
scummvm_dir = os.path.join(config.ROMS_FOLDER, "scummvm")
if dest_dir == scummvm_dir:
expected_base = os.path.splitext(os.path.basename(archive_path))[0]
# Utiliser before_items si fourni, sinon before_dirs pour rétro-compatibilité
items_before = before_items if before_items is not None else before_dirs
success, error_msg = handle_scummvm(dest_dir, items_before, extracted_basename=expected_base)
if not success:
return False, error_msg
return True, None
def extract_zip(zip_path, dest_dir, url):
@@ -1052,7 +1204,6 @@ def extract_zip(zip_path, dest_dir, url):
if os.path.isdir(file_path):
logger.warning(f"Conflit: dossier existant avec le même nom que le fichier {file_path}, suppression du dossier")
try:
import shutil
shutil.rmtree(file_path)
except Exception as rm_err:
logger.error(f"Impossible de supprimer le dossier {file_path}: {rm_err}")
@@ -1547,6 +1698,88 @@ def handle_dos(dest_dir, before_items, extracted_basename=None):
return False, error_msg
def handle_scummvm(dest_dir, before_items, extracted_basename=None):
"""Gère l'organisation spécifique des jeux ScummVM extraits.
- Crée un sous-dossier avec le nom du jeu (sans extension)
- Extrait/déplace le contenu du ZIP dans ce dossier
- Crée un fichier .scummvm vide avec le même nom
Exemple: Freddi_fish_1.zip -> dossier Freddi_fish_1/ + fichier Freddi_fish_1.scummvm
Args:
dest_dir: Dossier de destination (scummvm)
before_items: Set des éléments présents avant extraction
extracted_basename: Nom de base du ZIP extrait (sans extension)
"""
logger.debug(f"Traitement spécifique ScummVM dans: {dest_dir}")
time.sleep(2) # Petite latence post-extraction
try:
# Déterminer les nouveaux éléments extraits
after_items = set(os.listdir(dest_dir))
except Exception:
after_items = set()
ignore_names = {"scummvm", "images", "videos", "manuals", "media"}
# Filtrer les nouveaux éléments (fichiers ou dossiers)
new_items = [item for item in (after_items - before_items)
if item not in ignore_names and not item.endswith('.scummvm')]
if not new_items:
logger.warning("Aucun nouveau contenu ScummVM détecté après extraction")
return True, None
if not extracted_basename:
logger.warning("Nom de base du ZIP non fourni pour le traitement ScummVM")
return True, None
# Nom du dossier et du fichier .scummvm
game_folder_name = extracted_basename
game_folder_path = os.path.join(dest_dir, game_folder_name)
scummvm_file_path = os.path.join(game_folder_path, f"{game_folder_name}.scummvm")
try:
# Créer le dossier du jeu s'il n'existe pas
if os.path.exists(game_folder_path):
logger.warning(f"Le dossier {game_folder_path} existe déjà, il sera utilisé")
else:
os.makedirs(game_folder_path, exist_ok=True)
logger.debug(f"Dossier créé: {game_folder_path}")
# Déplacer tous les nouveaux éléments dans le dossier du jeu
for item in new_items:
src_path = os.path.join(dest_dir, item)
dst_path = os.path.join(game_folder_path, item)
try:
if os.path.isdir(src_path):
shutil.move(src_path, dst_path)
else:
shutil.move(src_path, dst_path)
logger.debug(f"Déplacé: {item} -> {game_folder_name}/{item}")
except Exception as e:
logger.error(f"Erreur déplacement {item}: {e}")
return False, f"Erreur lors du déplacement de {item}: {str(e)}"
# Créer le fichier .scummvm vide dans le sous-dossier
try:
with open(scummvm_file_path, 'w', encoding='utf-8') as f:
pass # Fichier vide
logger.info(f"Fichier .scummvm créé: {scummvm_file_path}")
except Exception as e:
logger.error(f"Erreur création fichier .scummvm: {e}")
return False, f"Erreur lors de la création du fichier .scummvm: {str(e)}"
logger.info(f"Contenu ScummVM organisé avec succès: dossier {game_folder_name}/ avec fichier {game_folder_name}.scummvm à l'intérieur")
return True, None
except Exception as e:
error_msg = f"Erreur lors de l'organisation ScummVM dans {game_folder_path}: {str(e)}"
logger.error(error_msg)
return False, error_msg
def handle_xbox(dest_dir, iso_files, url=None):
"""Gère la conversion des fichiers Xbox extraits et met à jour l'UI (Converting)."""
logger.debug(f"Traitement spécifique Xbox dans: {dest_dir}")
@@ -1555,31 +1788,31 @@ def handle_xbox(dest_dir, iso_files, url=None):
time.sleep(2)
if config.OPERATING_SYSTEM == "Windows":
# Sur Windows; telecharger le fichier exe
XDVDFS_EXE = config.XDVDFS_EXE
xdvdfs_cmd = [XDVDFS_EXE, "pack"] # Liste avec 2 éléments
XISO_EXE = config.XISO_EXE
extract_xiso_cmd = [XISO_EXE, "-r"] # Liste avec 2 éléments
else:
# Linux/Batocera : télécharger le fichier xdvdfs
XDVDFS_LINUX = config.XDVDFS_LINUX
XISO_LINUX = config.XISO_LINUX
try:
stat_info = os.stat(XDVDFS_LINUX)
stat_info = os.stat(XISO_LINUX)
mode = stat_info.st_mode
logger.debug(f"Permissions de {XDVDFS_LINUX}: {oct(mode)}")
logger.debug(f"Permissions de {XISO_LINUX}: {oct(mode)}")
logger.debug(f"Propriétaire: {stat_info.st_uid}, Groupe: {stat_info.st_gid}")
# Vérifier si le fichier est exécutable
if not os.access(XDVDFS_LINUX, os.X_OK):
logger.error(f"Le fichier {XDVDFS_LINUX} n'est pas exécutable")
if not os.access(XISO_LINUX, os.X_OK):
logger.error(f"Le fichier {XISO_LINUX} n'est pas exécutable")
try:
os.chmod(XDVDFS_LINUX, 0o755)
logger.info(f"Permissions corrigées pour {XDVDFS_LINUX}")
os.chmod(XISO_LINUX, 0o755)
logger.info(f"Permissions corrigées pour {XISO_LINUX}")
except Exception as e:
logger.error(f"Impossible de modifier les permissions: {str(e)}")
return False, "Erreur de permissions sur xdvdfs"
except Exception as e:
logger.error(f"Erreur lors de la vérification des permissions: {str(e)}")
xdvdfs_cmd = [XDVDFS_LINUX, "pack"] # Liste avec 2 éléments
extract_xiso_cmd = [XISO_LINUX, "-r"] # Liste avec 2 éléments
try:
# Utiliser uniquement la liste fournie (nouveaux ISO extraits). Fallback scan uniquement si liste vide.
@@ -1617,7 +1850,7 @@ def handle_xbox(dest_dir, iso_files, url=None):
# Historique
if isinstance(config.history, list):
for entry in config.history:
if entry.get("url") == url and entry.get("status") in ["Extracting", "Téléchargement", "downloading"]:
if entry.get("url") == url and entry.get("status") in ["Extracting", "Téléchargement", "Downloading"]:
entry["status"] = "Converting"
entry["progress"] = 0
entry["message"] = "Xbox conversion in progress"
@@ -1629,16 +1862,21 @@ def handle_xbox(dest_dir, iso_files, url=None):
logger.info(f"Démarrage conversion Xbox: {total} ISO(s)")
for idx, iso_xbox_source in enumerate(iso_files, start=1):
logger.debug(f"Traitement de l'ISO Xbox: {iso_xbox_source}")
xiso_dest = os.path.splitext(iso_xbox_source)[0] + "_xbox.iso"
# Construction de la commande avec des arguments distincts
cmd = xdvdfs_cmd + [iso_xbox_source, xiso_dest]
logger.debug(f"Exécution de la commande: {' '.join(cmd)}")
# extract-xiso -r repackage l'ISO en place
# Il faut exécuter la commande depuis le dossier contenant l'ISO
iso_dir = os.path.dirname(iso_xbox_source)
iso_filename = os.path.basename(iso_xbox_source)
# Utiliser le nom de fichier relatif et définir le répertoire de travail
cmd = extract_xiso_cmd + [iso_filename]
logger.debug(f"Exécution de la commande: {' '.join(cmd)} (cwd: {iso_dir})")
process = subprocess.run(
cmd,
capture_output=True,
text=True
text=True,
cwd=iso_dir
)
if process.returncode != 0:
@@ -1650,27 +1888,34 @@ def handle_xbox(dest_dir, iso_files, url=None):
if url not in config.download_progress:
config.download_progress[url] = {}
config.download_progress[url]["status"] = "Error"
config.download_progress[url]["message"] = err_msg
config.download_progress[url]["message"] = process.stderr
config.download_progress[url]["progress_percent"] = 0
config.needs_redraw = True
if isinstance(config.history, list):
for entry in config.history:
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "downloading"):
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "Downloading"):
entry["status"] = "Error"
entry["message"] = err_msg
entry["message"] = process.stderr
save_history(config.history)
break
except Exception:
pass
return False, err_msg
# Vérifier que l'ISO converti a été créé
if os.path.exists(xiso_dest):
logger.info(f"ISO converti avec succès: {xiso_dest}")
# Remplacer l'ISO original par l'ISO converti
os.remove(iso_xbox_source)
os.rename(xiso_dest, iso_xbox_source)
logger.debug(f"ISO original remplacé par la version convertie")
# Vérifier que l'ISO existe toujours (extract-xiso le modifie en place)
if os.path.exists(iso_xbox_source):
logger.info(f"ISO repackagé avec succès: {iso_xbox_source}")
logger.debug(f"ISO converti au format XISO en place")
# Supprimer le fichier .old créé par extract-xiso (backup)
old_file = iso_xbox_source + ".old"
if os.path.exists(old_file):
try:
os.remove(old_file)
logger.debug(f"Fichier backup .old supprimé: {old_file}")
except Exception as e:
logger.warning(f"Impossible de supprimer le fichier .old: {e}")
# Mise à jour progression de conversion (coarse-grain)
try:
percent = int(idx / total * 100) if total > 0 else 100
@@ -1689,7 +1934,7 @@ def handle_xbox(dest_dir, iso_files, url=None):
except Exception:
pass
else:
err_msg = f"L'ISO converti n'a pas été créé: {xiso_dest}"
err_msg = f"L'ISO source a disparu après conversion: {iso_xbox_source}"
logger.error(err_msg)
try:
if url:
@@ -1701,7 +1946,7 @@ def handle_xbox(dest_dir, iso_files, url=None):
config.needs_redraw = True
if isinstance(config.history, list):
for entry in config.history:
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "downloading"):
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "Downloading"):
entry["status"] = "Error"
entry["message"] = err_msg
save_history(config.history)
@@ -1710,6 +1955,25 @@ def handle_xbox(dest_dir, iso_files, url=None):
pass
return False, "Échec de la conversion de l'ISO"
# Conversion terminée avec succès - mettre à jour le statut final
try:
if url:
if url not in config.download_progress:
config.download_progress[url] = {}
config.download_progress[url]["status"] = "Download_OK"
config.download_progress[url]["progress_percent"] = 100
config.needs_redraw = True
if isinstance(config.history, list):
for entry in config.history:
if entry.get("url") == url and entry.get("status") == "Converting":
entry["status"] = "Download_OK"
entry["progress"] = 100
entry["message"] = "Xbox conversion completed successfully"
save_history(config.history)
break
except Exception as e:
logger.debug(f"MAJ statut final conversion ignorée: {e}")
return True, "Conversion Xbox terminée avec succès"
except Exception as e:
@@ -1890,6 +2154,26 @@ def save_music_config():
except Exception as e:
logger.error(f"Erreur lors de la sauvegarde de la configuration musique: {str(e)}")
def check_web_service_status():
"""Vérifie si le service web est activé au démarrage.
Returns:
bool: True si activé, False sinon
"""
try:
if config.OPERATING_SYSTEM != "Linux":
return False
# Lire l'état depuis rgsx_settings.json
settings = load_rgsx_settings()
return settings.get("web_service_at_boot", False)
except Exception as e:
logger.debug(f"Failed to check web service status: {e}")
return False
def normalize_platform_name(platform):
"""Normalise un nom de plateforme en supprimant espaces et convertissant en minuscules."""

View File

@@ -1,406 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import time
import json
import re
import traceback
from typing import Any, Dict, Tuple, List, Optional
try:
import pygame # type: ignore
except Exception as e:
print("Pygame is required. Install with: pip install pygame")
raise
PROMPTS = [
# Face buttons
"SOUTH_BUTTON - CONFIRM", # A on Xbox
"EAST_BUTTON - CANCEL", # B on Xbox
"WEST_BUTTON - CLEAR HISTORY / SELECT GAMES", # X on Xbox
"NORTH_BUTTON - HISTORY", # Y on Xbox
# Meta
"START - PAUSE",
"SELECT - FILTER",
# D-Pad
"DPAD_UP - MOVE UP",
"DPAD_DOWN - MOVE DOWN",
"DPAD_LEFT - MOVE LEFT",
"DPAD_RIGHT - MOVE RIGHT",
# Bumpers
"LEFT_BUMPER - LB/L1 - Delete last char",
"RIGHT_BUMPER - RB/R1 - Add space",
# Triggers
"LEFT_TRIGGER - LT/L2 - Page +",
"RIGHT_TRIGGER - RT/R2 - Page -",
# Left stick directions
"JOYSTICK_LEFT_UP - MOVE UP",
"JOYSTICK_LEFT_DOWN - MOVE DOWN",
"JOYSTICK_LEFT_LEFT - MOVE LEFT",
"JOYSTICK_LEFT_RIGHT - MOVE RIGHT",
]
INPUT_TIMEOUT_SECONDS = 10 # Temps max par entrée avant "ignored"
# --- Minimal on-screen console (Pygame window) ---
SURFACE = None # type: ignore
FONT = None # type: ignore
LOG_LINES: List[str] = []
MAX_LOG = 300
def init_screen(width: int = 900, height: int = 600) -> None:
global SURFACE, FONT
try:
pygame.display.init()
SURFACE = pygame.display.set_mode((width, height))
pygame.display.set_caption("Controller Tester")
pygame.font.init()
FONT = pygame.font.SysFont("Consolas", 20) or pygame.font.Font(None, 20)
except Exception:
# If display init fails, stay headless but continue
SURFACE = None
FONT = None
def log(msg: str) -> None:
# Print to real console and on-screen log
try:
print(msg)
except Exception:
pass
LOG_LINES.append(str(msg))
if len(LOG_LINES) > MAX_LOG:
del LOG_LINES[: len(LOG_LINES) - MAX_LOG]
draw_log()
def draw_log() -> None:
if SURFACE is None or FONT is None:
return
try:
SURFACE.fill((12, 12, 12))
margin = 12
line_h = FONT.get_height() + 4
# Show the last N lines that fit on screen
max_lines = (SURFACE.get_height() - margin * 2) // line_h
to_draw = LOG_LINES[-max_lines:]
y = margin
for line in to_draw:
surf = FONT.render(line, True, (220, 220, 220))
SURFACE.blit(surf, (margin, y))
y += line_h
pygame.display.flip()
except Exception:
pass
def init_joystick() -> pygame.joystick.Joystick:
pygame.init()
pygame.joystick.init()
if pygame.joystick.get_count() == 0:
log("No joystick detected. Connect a controller and try again.")
sys.exit(1)
js = pygame.joystick.Joystick(0)
js.init()
name = js.get_name()
log(f"Using joystick 0: {name}")
log("")
log(f"Note: each input will auto-ignore after {INPUT_TIMEOUT_SECONDS}s if not present (e.g. missing L2/R2)")
return js
def wait_for_stable(js: pygame.joystick.Joystick, settle_ms: int = 250, deadband: float = 0.05, timeout_ms: int = 2000) -> bool:
"""Wait until axes stop moving (change < deadband) continuously for settle_ms.
Unlike a traditional neutral check, this doesn't assume axes center at 0.
Hats are required to be (0,0) to avoid capturing D-Pad releases.
Returns True if stability achieved, False on timeout.
"""
start = pygame.time.get_ticks()
last = [js.get_axis(i) for i in range(js.get_numaxes())]
stable_since = None
while True:
# Handle window close only (avoid quitting on keyboard here)
for event in pygame.event.get():
if event.type == pygame.QUIT:
log("Window closed. Exiting.")
sys.exit(0)
moved = False
for i in range(js.get_numaxes()):
cur = js.get_axis(i)
if abs(cur - last[i]) > deadband:
moved = True
last[i] = cur
hats_ok = all(js.get_hat(i) == (0, 0) for i in range(js.get_numhats()))
if not moved and hats_ok:
if stable_since is None:
stable_since = pygame.time.get_ticks()
elif pygame.time.get_ticks() - stable_since >= settle_ms:
return True
else:
stable_since = None
if pygame.time.get_ticks() - start > timeout_ms:
return False
draw_log()
pygame.time.wait(10)
def wait_for_event(js: pygame.joystick.Joystick, logical_name: str, axis_threshold: float = 0.6, timeout_sec: int = INPUT_TIMEOUT_SECONDS) -> Tuple[str, Any]:
"""Wait for a joystick event for the given logical control.
Returns a tuple of (kind, data):
- ("button", button_index)
- ("hat", (x, y)) where x,y in {-1,0,1}
- ("axis", {"axis": index, "direction": -1|1})
"""
# Ensure prior motion has settled to avoid capturing a release
wait_for_stable(js)
log("")
deadline = time.time() + max(1, int(timeout_sec))
log(f"Press {logical_name} (Wait {timeout_sec}s to skip/ignore) if not present")
# Flush old events
pygame.event.clear()
while True:
# Update window title with countdown if we have a surface
try:
remaining = int(max(0, deadline - time.time()))
if SURFACE is not None:
pygame.display.set_caption(f"Controller Tester — {logical_name} — {remaining}s left")
except Exception:
pass
for event in pygame.event.get():
# Keyboard helpers
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
log(f"Skipped {logical_name}")
return ("skipped", None)
# No keyboard quit here to avoid accidental exits when using controllers
if event.type == pygame.QUIT:
log("Window closed. Exiting.")
sys.exit(0)
# Buttons
if event.type == pygame.JOYBUTTONDOWN:
log(f"Captured {logical_name}: BUTTON {event.button}")
return ("button", event.button)
# D-Pad (HAT)
if event.type == pygame.JOYHATMOTION:
val = event.value # (x, y)
if val != (0, 0):
log(f"Captured {logical_name}: HAT {val}")
return ("hat", val)
# Axes (sticks, triggers)
if event.type == pygame.JOYAXISMOTION:
axis = event.axis
value = float(event.value)
if abs(value) >= axis_threshold:
direction = 1 if value > 0 else -1
log(f"Captured {logical_name}: AXIS {axis} dir {direction} (raw {value:.2f})")
return ("axis", {"axis": axis, "direction": direction, "raw": value})
draw_log()
# Timeout?
if time.time() >= deadline:
log(f"Ignored {logical_name} (timeout {timeout_sec}s)")
return ("ignored", None)
time.sleep(0.005)
def write_log(path: str, mapping: Dict[str, Tuple[str, Any]], device_name: str) -> None:
lines = []
lines.append("# Controller mapping log\n")
lines.append(f"# Device: {device_name}\n\n")
for name, (kind, data) in mapping.items():
if kind == "button":
lines.append(f"{name} = BUTTON {data}\n")
elif kind == "hat":
lines.append(f"{name} = HAT {data}\n")
elif kind == "axis":
ax = data.get("axis")
direction = data.get("direction")
lines.append(f"{name} = AXIS {ax} dir {direction}\n")
elif kind == "skipped":
lines.append(f"{name} = SKIPPED\n")
elif kind == "ignored":
lines.append(f"{name} = IGNORED\n")
else:
lines.append(f"{name} = UNKNOWN {data}\n")
with open(path, "w", encoding="utf-8") as f:
f.writelines(lines)
log("")
log(f"Saved mapping to: {path}")
# --- JSON preset generation ---
def sanitize_device_name(name: str) -> str:
s = name.strip().lower()
# Replace non-alphanumeric with underscore
s = re.sub(r"[^a-z0-9]+", "_", s)
s = re.sub(r"_+", "_", s).strip("_")
return s or "controller"
def to_json_binding(kind: str, data: Any, display: Optional[str] = None) -> Optional[Dict[str, Any]]:
if kind == "button" and isinstance(data, int):
return {"type": "button", "button": data, **({"display": display} if display else {})}
if kind == "hat" and isinstance(data, (tuple, list)) and len(data) == 2:
val = list(data)
return {"type": "hat", "value": val, **({"display": display} if display else {})}
if kind == "axis" and isinstance(data, dict):
axis = data.get("axis")
direction = data.get("direction")
if isinstance(axis, int) and direction in (-1, 1):
return {"type": "axis", "axis": axis, "direction": int(direction), **({"display": display} if display else {})}
return None
def build_controls_json(mapping: Dict[str, Tuple[str, Any]]) -> Dict[str, Any]:
# Map logical prompts to action keys and preferred display labels
prompt_map = {
"SOUTH_BUTTON - CONFIRM": ("confirm", "A"),
"EAST_BUTTON - CANCEL": ("cancel", "B"),
"WEST_BUTTON - CLEAR HISTORY / SELECT GAMES": ("clear_history", "X"),
"NORTH_BUTTON - HISTORY": ("history", "Y"),
"START - PAUSE": ("start", "Start"),
"SELECT - FILTER": ("filter", "Select"),
"DPAD_UP - MOVE UP": ("up", "↑"),
"DPAD_DOWN - MOVE DOWN": ("down", "↓"),
"DPAD_LEFT - MOVE LEFT": ("left", "←"),
"DPAD_RIGHT - MOVE RIGHT": ("right", "→"),
"LEFT_BUMPER - LB/L1 - Delete last char": ("delete", "LB"),
"RIGHT_BUMPER - RB/R1 - Add space": ("space", "RB"),
# Triggers per prompts: LEFT=page_up, RIGHT=page_down
"LEFT_TRIGGER - LT/L2 - Page +": ("page_up", "LT"),
"RIGHT_TRIGGER - RT/R2 - Page -": ("page_down", "RT"),
# Left stick directions (fallbacks for arrows)
"JOYSTICK_LEFT_UP - MOVE UP": ("up", "J↑"),
"JOYSTICK_LEFT_DOWN - MOVE DOWN": ("down", "J↓"),
"JOYSTICK_LEFT_LEFT - MOVE LEFT": ("left", "J←"),
"JOYSTICK_LEFT_RIGHT - MOVE RIGHT": ("right", "J→"),
}
result: Dict[str, Any] = {}
# First pass: take direct DPAD/face/meta/bumper/trigger bindings
for prompt, (action, disp) in prompt_map.items():
if prompt not in mapping:
continue
kind, data = mapping[prompt]
if kind in ("ignored", "skipped"):
continue
# Prefer DPAD over JOYSTICK for directions: handle fallback later
if action in ("up", "down", "left", "right"):
if prompt.startswith("DPAD_"):
b = to_json_binding(kind, data, disp)
if b:
result[action] = b
# Joystick handled as fallback if DPAD missing
else:
b = to_json_binding(kind, data, disp)
if b:
result[action] = b
# Second pass: fallback to joystick directions if arrows missing
fallbacks = [
("JOYSTICK_LEFT_UP - MOVE UP", "up", "J↑"),
("JOYSTICK_LEFT_DOWN - MOVE DOWN", "down", "J↓"),
("JOYSTICK_LEFT_LEFT - MOVE LEFT", "left", "J←"),
("JOYSTICK_LEFT_RIGHT - MOVE RIGHT", "right", "J→"),
]
for prompt, action, disp in fallbacks:
if action in result:
continue
if prompt in mapping:
kind, data = mapping[prompt]
if kind in ("ignored", "skipped"):
continue
b = to_json_binding(kind, data, disp)
if b:
result[action] = b
return result
def write_controls_json(device_name: str, controls: Dict[str, Any]) -> str:
"""Write the generated controls preset JSON in the same folder as this script.
Also embeds a JSON-safe comment with the device name under the _comment key.
"""
# Same folder as the launched script
base_dir = os.path.dirname(os.path.abspath(__file__))
fname = f"{sanitize_device_name(device_name)}_controller.json"
out_path = os.path.join(base_dir, fname)
# Include the detected device name for auto-preset matching
payload = {"device": device_name}
payload.update(controls)
try:
with open(out_path, "w", encoding="utf-8") as f:
json.dump(payload, f, ensure_ascii=False, indent=4)
return out_path
except Exception:
return out_path
def main() -> None:
init_screen()
js = init_joystick()
# Print device basics
try:
log(f"Buttons: {js.get_numbuttons()} | Axes: {js.get_numaxes()} | Hats: {js.get_numhats()}")
except Exception:
pass
mapping: Dict[str, Tuple[str, Any]] = {}
for logical in PROMPTS:
kind, data = wait_for_event(js, logical)
mapping[logical] = (kind, data)
# Short, consistent debounce for all inputs
pygame.event.clear()
pygame.time.wait(150)
log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "controller_mapping.log")
write_log(log_path, mapping, js.get_name())
# Build and write ready-to-use JSON controls preset
controls = build_controls_json(mapping)
if controls:
out_json = write_controls_json(js.get_name(), controls)
log(f"Saved JSON preset to: {out_json}")
else:
log("No usable inputs captured to build a JSON preset.")
log("Done. Press Q or close the window to exit.")
if __name__ == "__main__":
try:
main()
except SystemExit:
# Allow intentional exits
pass
except Exception:
# Show traceback on screen and wait for window close
tb = traceback.format_exc()
try:
log("")
log("An error occurred:")
for line in tb.splitlines():
log(line)
# Idle until window is closed
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
raise SystemExit(1)
draw_log()
pygame.time.wait(50)
except Exception:
pass
finally:
try:
pygame.joystick.quit()
pygame.quit()
except Exception:
pass

3
version.json Normal file
View File

@@ -0,0 +1,3 @@
{
"version": "2.3.1.7"
}

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.