File: README.md
A Steam Deck controller-friendly tool for downloading and converting original Xb
- On-screen keyboard for search
- Automatic XISO conversion for xemu compatibility
- Progress tracking with download/extraction status
+- **Disk space monitor** - Visual indicator in top-right corner (turns yellow/red when low)
+- **Completion sound** - Audio feedback when downloads finish
- Fullscreen pygame UI optimized for Steam Deck
## Requirements
roms/ports/
βββ xbox-downloader/
β βββ xbox_downloader.py
β βββ xbox_extractor.py
+β βββ tada.mp3 # Completion sound
β βββ downloads/ # For manual ZIP downloads (extractor)
β βββ venv/ # Auto-created Python virtual environment
βββ Xbox Game Downloader.sh
File: xbox_downloader.py
BASE_URL = "https://myrient.erista.me/files/Redump/Microsoft%20-%20Xbox/"
XBOX_ROM_DIR = "/run/media/deck/SK256/Emulation/roms/xbox"
EXTRACT_XISO = "/run/media/deck/SK256/Emulation/tools/chdconv/extract-xiso"
+SOUND_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tada.mp3")
+
+# Disk space thresholds (in GB)
+DISK_SPACE_WARNING = 20 # Yellow when below 20GB
+DISK_SPACE_CRITICAL = 10 # Red when below 10GB
# Colors
BLACK = (0, 0, 0)
class XboxDownloaderUI:
def __init__(self):
pygame.init()
pygame.joystick.init()
+ pygame.mixer.init()
+
+ # Load completion sound
+ self.completion_sound = None
+ if os.path.exists(SOUND_FILE):
+ try:
+ self.completion_sound = pygame.mixer.Sound(SOUND_FILE)
+ except:
+ pass
# Fullscreen
info = pygame.display.Info()
def __init__(self):
self.font_large = pygame.font.Font(None, 48)
self.font_medium = pygame.font.Font(None, 36)
self.font_small = pygame.font.Font(None, 28)
+ self.font_tiny = pygame.font.Font(None, 22)
# Controller
self.joystick = None
def filter_games(self):
self.selected_index = 0
self.scroll_offset = 0
+ def get_disk_space(self):
+ """Get free disk space in GB for the Xbox ROM directory"""
+ try:
+ stat = os.statvfs(XBOX_ROM_DIR)
+ free_bytes = stat.f_bavail * stat.f_frsize
+ total_bytes = stat.f_blocks * stat.f_frsize
+ free_gb = free_bytes / (1024 ** 3)
+ total_gb = total_bytes / (1024 ** 3)
+ return free_gb, total_gb
+ except:
+ return 0, 0
+
+ def draw_disk_space(self):
+ """Draw disk space indicator in top-right corner"""
+ free_gb, total_gb = self.get_disk_space()
+ if total_gb == 0:
+ return
+
+ # Dimensions
+ bar_width = 120
+ bar_height = 16
+ padding = 15
+ x = self.width - bar_width - padding
+ y = padding
+
+ # Calculate fill percentage
+ used_gb = total_gb - free_gb
+ fill_pct = used_gb / total_gb if total_gb > 0 else 0
+
+ # Determine color based on free space
+ if free_gb < DISK_SPACE_CRITICAL:
+ fill_color = RED
+ elif free_gb < DISK_SPACE_WARNING:
+ fill_color = YELLOW
+ else:
+ fill_color = GREEN
+
+ # Draw background
+ pygame.draw.rect(self.screen, DARK_GRAY, (x, y, bar_width, bar_height), border_radius=3)
+
+ # Draw fill (shows used space)
+ fill_width = int(bar_width * fill_pct)
+ if fill_width > 0:
+ pygame.draw.rect(self.screen, fill_color, (x, y, fill_width, bar_height), border_radius=3)
+
+ # Draw border
+ pygame.draw.rect(self.screen, GRAY, (x, y, bar_width, bar_height), width=1, border_radius=3)
+
+ # Draw label
+ label = f"{free_gb:.0f}GB free"
+ label_surf = self.font_tiny.render(label, True, WHITE if free_gb >= DISK_SPACE_CRITICAL else fill_color)
+ self.screen.blit(label_surf, (x, y + bar_height + 3))
+
+ def play_completion_sound(self):
+ """Play the completion sound if available"""
+ if self.completion_sound:
+ try:
+ self.completion_sound.play()
+ except:
+ pass
+
def draw_message(self, title, message):
self.screen.fill(BLACK)
title_surf = self.font_large.render(title, True, GREEN)
def draw_message(self, title, message):
def draw_main_menu(self):
self.screen.fill(BLACK)
+ # Disk space indicator
+ self.draw_disk_space()
+
# Header
title = self.font_large.render("XBOX GAME DOWNLOADER", True, GREEN)
self.screen.blit(title, (self.width//2 - title.get_width()//2, 20))
def draw_keyboard(self):
def draw_download_progress(self):
self.screen.fill(BLACK)
+ # Disk space indicator
+ self.draw_disk_space()
+
title = self.font_large.render("DOWNLOADING", True, GREEN)
self.screen.blit(title, (self.width//2 - title.get_width()//2, self.height//2 - 120))
def download_game(self, game_name, href):
debug.write("Download complete!\n")
debug.flush()
+ # Play completion sound
+ self.play_completion_sound()
+
except Exception as e:
debug.write(f"Exception: {e}\n")
debug.flush()