#!/usr/bin/env bash clear # Gum theme export GUM_THEME="\ [\"ui\"] = { background = \"#000000\", foreground = \"#FFA500\" }\ [\"ui.focused\"] = { background = \"#FFA500\", foreground = \"#000000\" }\ [\"ui.text\"] = { foreground = \"#FFA500\" }\ [\"box\"] = { background = \"#000000\", foreground = \"#FFA500\", border = \"#FFA500\" }\ [\"box.border\"] = { foreground = \"#FFA500\" }\ " # FZF colors (orange highlight) export FZF_DEFAULT_OPTS="--color=fg:#FFA500,bg:#000000,hl:#FFA500,fg+:#000000,bg+:#FFA500,hl+:#000000,info:#FFA500,prompt:#FFA500,pointer:#FFA500,marker:#FFA500,spinner:#FFA500" title() { gum style \ --foreground="#FFA500" \ --background="#000000" \ --border-foreground="#FFA500" \ --border="double" \ --align="center" \ --width="50" \ --margin="1" \ --padding="2 4" \ "MEGADRIVE CART FLASH" } # ------------------------- # Dependencies # ------------------------- check_dependencies() { # Ensure ~/.local/bin is in PATH (for pip installs) export PATH="$HOME/.local/bin:$PATH" # flashkit if ! command -v flashkit &> /dev/null; then gum confirm "flashkit is not installed. Install it now?" || exit 1 gum spin --spinner="line" --title="Installing flashkit..." -- \ python3 -m pip install --user flashkit # Re-check after install if ! command -v flashkit &> /dev/null; then gum style --foreground="#FFA500" "flashkit install failed or not in PATH." gum style --foreground="#FFA500" "Try: export PATH=\$HOME/.local/bin:\$PATH" exit 1 fi fi # pyserial if ! python3 -c "import serial" &> /dev/null; then gum confirm "pyserial is not installed. Install it now?" || exit 1 gum spin --spinner="line" --title="Installing pyserial..." -- \ python3 -m pip install --user pyserial if ! python3 -c "import serial" &> /dev/null; then gum style --foreground="#FFA500" "pyserial install failed." exit 1 fi fi # fzf if ! command -v fzf &> /dev/null; then gum confirm "fzf is not installed. Install it now?" || exit 1 gum spin --spinner="line" --title="Installing fzf..." -- \ sudo apt-get update && sudo apt-get install -y fzf fi # md5sum (usually present, but just in case) if ! command -v md5sum &> /dev/null; then gum style --foreground="#FFA500" "md5sum not found. Please install coreutils." exit 1 fi } # ------------------------- # Detect FTDI port # ------------------------- detect_port() { gum style --foreground="#FFA500" "Detecting flash cartridge..." if [ -d /dev/serial/by-id ]; then matches=($(ls /dev/serial/by-id/*FTDI* 2>/dev/null)) fi if [ ${#matches[@]} -eq 0 ]; then for dev in /dev/ttyUSB*; do [ -e "$dev" ] || continue if udevadm info --query=property --name="$dev" | grep -q "ID_VENDOR_ID=0403"; then matches+=("$dev") fi done fi if [ ${#matches[@]} -eq 0 ]; then gum style --foreground="#FFA500" "No FTDI flash device detected." exit 1 fi selected_port=$(readlink -f "${matches[0]}") gum style --foreground="#FFA500" "Using port: $selected_port" } # ------------------------- # Read cart # ------------------------- read_cart() { clear title gum style --foreground="#FFA500" "Checking current cartridge content..." check_output=$(flashkit --port "$selected_port" check 2>&1) gum style \ --foreground="#FFA500" \ --border="rounded" \ --border-foreground="#FFA500" \ --padding="1 2" \ "$check_output" } # ------------------------- # FZF file browser # ------------------------- browse_files() { search_path="$1" find "$search_path" -type f -name "*.md" -size +256k 2>/dev/null \ | fzf \ --height=40% \ --reverse \ --border \ --prompt="Select ROM > " \ --preview 'echo Size: $(du -h {} | awk "{print \$1}"); echo MD5: $(md5sum {} | awk "{print \$1}")' } # ------------------------- # Write ROM # ------------------------- write_rom() { clear title # Build menu dynamically options=() if [ -d "/mnt/nas/Games/ROMs/megadrive" ]; then options+=("NAS (/mnt/nas/Games/ROMs/megadrive)") fi options+=("Search Home (~)") source_choice=$(printf "%s\n" "${options[@]}" | gum choose --header="Select ROM source:") case "$source_choice" in "NAS (/mnt/nas/Games/ROMs/megadrive)") if [ ! -d "/mnt/nas/Games/ROMs/megadrive" ]; then gum style --foreground="#FFA500" "NAS path not available." return fi rom_file=$(browse_files "/mnt/nas/Games/ROMs/megadrive") ;; "Search Home (~)") rom_file=$(browse_files "$HOME") ;; *) return ;; esac [ -z "$rom_file" ] && { gum style --foreground="#FFA500" "No file selected." return } # File info rom_size=$(du -h "$rom_file" | awk '{print $1}') rom_md5=$(md5sum "$rom_file" | awk '{print $1}') gum style \ --foreground="#FFA500" \ --border="rounded" \ --border-foreground="#FFA500" \ --padding="1 2" \ "File: $(basename "$rom_file") Size: $rom_size MD5: $rom_md5" gum confirm "Write this ROM to $selected_port?" && { gum spin --spinner="line" --title="Writing ROM..." -- flashkit --port "$selected_port" write-rom -i "$rom_file" gum style --foreground="#FFA500" "Done! ROM written successfully." read_cart } || { gum style --foreground="#FFA500" "Cancelled." } } # ------------------------- # Main # ------------------------- check_dependencies detect_port read_cart while true; do choice=$(gum choose \ --header="Select an option:" \ "Read Cart" \ "Write ROM" \ "Exit") case "$choice" in "Read Cart") read_cart ;; "Write ROM") write_rom ;; "Exit") gum style --foreground="#FFA500" "Goodbye." exit 0 ;; esac done