Close

Procedimiento completo y final (RPi + Windows/RetroBat)

A project log for THE ULTIMATE MINI ARCADE

Classic arcade with all the latest technological advancements, ready for the classics. Enjoy!

srrubioSrRubio 08/28/2025 at 10:160 Comments

Con TODOS los códigos enteros listos para copiar/pegar. No tendrás que “reemplazar trozos”; simplemente pega cada bloque donde se indica.

A) Raspberry Pi — Dependencias y preparación

# A.1 Actualiza con full-upgrade (tu receta “que funcionó”)
sudo apt update
sudo apt full-upgrade -y

# A.2 Instala paquetes exactos que usas
sudo apt install -y mpv socat netcat-openbsd dos2unix

# A.3 Prepara logs y permisos
sudo touch /home/arcade/mostrar_rom_debug.log
sudo chown -R $USER:$USER /home/arcade
# (si $USER no es 'arcade', usa:)  sudo chown -R arcade:arcade /home/arcade

# A.4 (Recomendado) añade al usuario 'arcade' a grupos de vídeo/audio para KMS/DRM
sudo usermod -aG video,render,audio arcade

# A.5 Comprobaciones rápidas
which mpv && mpv --version | head -n 1
which nc  && nc -h 2>&1 | head -n 1
which socat && socat -V | head -n 1


B) Raspberry Pi — Arranque completamente en negro

# B.1 Silencia arco iris y avisos del firmware
sudo awk '
/^\[all\]/ {inall=1}
inall && $0 ~ /^disable_splash=/ { $0="disable_splash=1"; seen1=1 }
inall && $0 ~ /^avoid_warnings=/ { $0="avoid_warnings=1"; seen2=1 }
{ print }
END {  if (!seen1) print "disable_splash=1";  if (!seen2) print "avoid_warnings=1";
}
' /boot/firmware/config.txt | sudo tee /boot/firmware/config.txt >/dev/null

# B.2 Quita mensajes del kernel y el cursor (cmdline.txt = UNA sola línea)
FILE=/boot/firmware/cmdline.txt
LINE=$(cat "$FILE")
add_arg(){ ARG="$1"; echo "$LINE" | grep -qw "$ARG" || LINE="$LINE $ARG"; }
add_arg console=tty3
add_arg quiet
add_arg splash
add_arg loglevel=0
add_arg vt.global_cursor_default=0
printf '%s\n' "$LINE" | sudo tee "$FILE" >/dev/null

# B.3 Evita “flash” del login en TTY1
sudo systemctl mask getty@tty1.service

# B.4 (Opcional) Plymouth “casi vacío”
sudo apt install -y plymouth
sudo plymouth-set-default-theme text

# B.5 Verificaciones
grep -Eo 'console=tty3|quiet|splash|loglevel=0|vt\.global_cursor_default=0' /boot/firmware/cmdline.txt
grep -E 'disable_splash=|avoid_warnings=' /boot/firmware/config.txt
systemctl is-enabled getty@tty1.service


C) Raspberry Pi — Estructura de carpetas

mkdir -p /home/arcade/{default,sistemas,mame,megadrive}
# (opcional) logo de fallback:
# cp /ruta/a/tu/logo.mp4 /home/arcade/default/logo.mp4

ls -ld /home/arcade/{default,sistemas,mame,megadrive}


D) Raspberry Pi — Wrapper IPC (BASH)

Archivo: /usr/local/bin/mostrar_rom_ipc.sh

#!/bin/bash
set -euo pipefail

# ============================
# mostrar_rom_ipc.sh (con alias + separador ":" + normalización ROM)
# ============================
# Entrada:
#   - "sistema"               (ej: mame)
#   - "sistema:rom"           (o con '|' '/' '\')
# Búsqueda por ROM:
#   1) /home/arcade/<sistema>/<rom>.(mp4|gif|jpg|png)
#   2) si hay alias en /home/arcade/system_aliases.conf -> /home/arcade/<alias>>
#   3) /home/arcade/sistemas/<sistema>.(mp4|gif|jpg|png)
#   4) fallback logo: /home/arcade/default/logo.*  ->  /home/arcade/sistemas/lo>
# Prioridad de formatos: mp4 > gif > jpg > png

MSG="${1:-}"
SOCK="/run/mpv-marquee.sock"
LOG="/home/arcade/mostrar_rom_debug.log"
ROOT="/home/arcade"
SYS_DIR="$ROOT/sistemas"
ALIAS_FILE="$ROOT/system_aliases.conf"

# Si quieres búsqueda global opcional por cualquier sistema (último recurso), p>
SEARCH_ANY_SYSTEM=1

# ---------- Utilidades ----------
ts() { date +"%Y-%m-%d %H:%M:%S,%3N"; }
log() { printf "%s %s\n" "$(ts)" "$*" >> "$LOG"; }

ensure_socket() {
  for i in $(seq 1 50); do [[ -S "$SOCK" ]] && return 0; sleep 0.2; done
  systemctl start marquee-mpv.service >/dev/null 2>&1 || true
  for i in $(seq 1 50); do [[ -S "$SOCK" ]] && return 0; sleep 0.2; done
  return 1
}

send_ipc_once() { local json="$1"; printf '%s\n' "$json" | socat - "$SOCK" >/de>
send_ipc() {      local json="$1"; send_ipc_once "$json" || { ensure_socket && >

# Devuelve primer asset existente según prioridad
pick_asset() {
  local base="$1" f
  for ext in mp4 gif jpg png; do
    f="${base}.${ext}"
    [[ -f "$f" ]] && { echo "$f"; return 0; }
  done
  return 1
}

mpv_load() {
  local media="$1" esc
  esc=$(printf '%s' "$media" | sed 's/\\/\\\\/g; s/"/\\"/g')
  send_ipc '{"command":["set","loop-file","inf"]}' || true
  send_ipc "{\"command\":[\"loadfile\",\"$esc\",\"replace\"]}" || true
}

play_fallback_logo() {
local asset=""
  if asset=$(pick_asset "$ROOT/default/logo"); then
    log "[PLAY] fallback default: $asset"; mpv_load "$asset"; return
  fi
  if asset=$(pick_asset "$SYS_DIR/logo"); then
    log "[PLAY] fallback sistemas: $asset"; mpv_load "$asset"; return
  fi
  log "[WARN] Sin logo de fallback disponible."
}

# Leer alias: devuelve el sistema canónico para $1 si existe (case-insensitive)
map_alias() {
  local sys_in="$1"
  local sys_l="${sys_in,,}"
  local k v kl vl
  [[ -f "$ALIAS_FILE" ]] || return 1
  while IFS='=' read -r k v || [[ -n "$k" ]]; do
    # --- único cambio: tolerar CRLF en el .conf ---
    k="${k//$'\r'/}"; v="${v//$'\r'/}"
    [[ -z "${k//[[:space:]]/}" ]] && continue
    [[ "$k" =~ ^[[:space:]]*# ]] && continue
    k="$(printf '%s' "$k" | awk '{$1=$1;print}')"
    v="$(printf '%s' "$v" | awk '{$1=$1;print}')"
    kl="${k,,}"; vl="${v,,}"
    if [[ "$kl" == "$sys_l" ]]; then printf '%s' "$vl"; return 0; fi
  done < "$ALIAS_FILE"
  return 1
}

# Búsqueda global opcional por cualquier sistema (excluye 'sistemas' y 'default>
find_any_system_asset() {
  local rom="$1" asset=""
  if asset=$(pick_asset "$ROOT/mame/$rom"); then echo "$asset"; return 0; fi
  local d base
  for d in "$ROOT"/*; do
    [[ -d "$d" ]] || continue
    base="${d##*/}"
    [[ "$base" == "sistemas" || "$base" == "default" || "$base" == "mame" ]] &&>
    if asset=$(pick_asset "$d/$rom"); then echo "$asset"; return 0; fi
  done
  return 1
}

# ---------- Entrada / Parse ----------
if [[ -z "${MSG// }" ]]; then IFS= read -r MSG || true; fi
MSG=${MSG//\"/}
MSG="$(printf '%s' "$MSG" | tr -d '\r\n' | awk '{$1=$1;print}')"
[[ -z "$MSG" ]] && { log "[RX] Mensaje vacío, nada que hacer."; exit 0; }

log "[RX] raw=<${MSG}>"

shopt -s nocasematch
if [[ "$MSG" == "logo" || "$MSG" == "default" || "$MSG" == "marquee" ]]; then
  play_fallback_logo; exit 0
fi
shopt -u nocasematch
# --- Separador: prioriza ":" (lo que envías desde Windows); si no hay, prueba >
sep=""
if   [[ "$MSG" == *:*  ]]; then sep=":"
elif [[ "$MSG" == *"|"* ]]; then sep="|"
elif [[ "$MSG" == *"/"* ]]; then sep="/"
elif [[ "$MSG" == *\\*  ]]; then sep="\\"
fi
log "[SEP] sep='<${sep}>' msg='<${MSG}>'"

system=""
rom=""
if [[ -z "$sep" ]]; then
  system="$MSG"
else
  system="${MSG%%"$sep"*}"
  rom="${MSG#*"$sep"}"
fi

# Limpieza básica
system="$(printf '%s' "$system" | awk '{$1=$1;print}')"
rom="$(printf '%s' "$rom" | awk '{$1=$1;print}')"

# --- Normalización ROM (mínimos cambios) ---
if [[ -n "$rom" ]]; then
  rom_orig="$rom"
  rom="${rom//|/,}"      # '|' -> ','
  rom="${rom//,v /, }"   # ',v ' -> ', '
  rom="${rom//,v/, }"    # ',v'  -> ', ' (por si no vino el espacio)
  rom="${rom//,  /, }"   # colapsar doble espacio tras coma
  if [[ "$rom" != "$rom_orig" ]]; then
    log "[NORM] rom '<$rom_orig>' -> '<$rom>'"
  fi
fi

log "[PARSE] system=<${system}> rom=<${rom}>"

# ---------- Resolución ----------
asset=""
if [[ -n "$rom" ]]; then
  # 1) /home/arcade/<sistema>/<rom>.*
  if asset=$(pick_asset "$ROOT/$system/$rom"); then
    log "[PLAY] rom asset: $asset"; mpv_load "$asset"; exit 0
  fi
  # 2) alias -> sistema canónico (ej.: arcade -> mame)
  alias_sys="$(map_alias "$system" || true)"
  if [[ -n "${alias_sys:-}" ]]; then
    if asset=$(pick_asset "$ROOT/$alias_sys/$rom"); then
      log "[PLAY] alias asset: $system -> $alias_sys : $asset"; mpv_load "$asse>
    fi
  fi
  # 3) (opcional) búsqueda global por cualquier sistema
  if [[ "$SEARCH_ANY_SYSTEM" -eq 1 ]]; then
    if asset=$(find_any_system_asset "$rom"); then
      log "[PLAY] any-system asset: $asset"; mpv_load "$asset"; exit 0
    fi
  fi
  # 4) Fallback al arte del sistema</rom></sistema>
  if asset=$(pick_asset "$SYS_DIR/$system"); then
    log "[PLAY] fallback system asset: $asset"; mpv_load "$asset"; exit 0
  fi
  # 5) Logo
  log "[MISS] Sin arte de ROM (incl. alias) ni de sistema."; play_fallback_logo>
else
  # Solo sistema
  if asset=$(pick_asset "$SYS_DIR/$system"); then
    log "[PLAY] system asset: $asset"; mpv_load "$asset"; exit 0
  fi
  log "[MISS] Sin arte del sistema."; play_fallback_logo; exit 0
fi

E) Raspberry Pi — Alias de sistemas

Archivo: /home/arcade/system_aliases.conf

# alias_de_sistema = sistema_canonico
mame      = mame
arcade    = mame
vertical  = mame
favorites = mame
recent    = mame
megadrive = megadrive
# all → (mejor sin alias, ya buscamos global)

# Normaliza finales de línea si editaste en Windows
sudo dos2unix /home/arcade/system_aliases.conf 2>/dev/null || sudo sed -i 's/\r$//' /home/arcade/system_aliases.conf


(Puedes añadir más alias/sistemas cuando quieras.)

F) Raspberry Pi — Servicio MPV (DRM/KMS, el tuyo “tal cual”)

Archivo: /etc/systemd/system/marquee-mpv.service

sudo tee /etc/systemd/system/marquee-mpv.service >/dev/null <<'EOF'
[Unit]
Description=Persistent mpv for marquee display (DRM/KMS + IPC)
After=local-fs.target multi-user.target
Wants=local-fs.target

[Service]
Type=simple
User=root
Environment=TERM=linux


Si el conector HDMI cambia (p.ej. HDMI-A-2), ajusta --drm-connector=.

G) Raspberry Pi — Servicio listener TCP (nc → wrapper)

Archivo: /etc/systemd/system/recibir_rom.service

sudo tee /etc/systemd/system/recibir_rom.service >/dev/null <<'EOF'
[Unit]
Description=Persistent mpv for marquee display (DRM/KMS + IPC)
After=local-fs.target multi-user.target
Wants=local-fs.target

[Service]
Type=simple
User=root
Environment=TERM=linux

# Espera "best effort" a /dev/dri y /dev/tty1 (no falla si no están)
ExecStartPre=/bin/sh -c 'for i in $(seq 1 50); do [ -e /dev/dri/card0 ] && [ -c>
# Cambia a VT1 si existe chvt
ExecStartPre=/bin/sh -c 'if command -v chvt >/dev/null 2>&1; then chvt 1 || tru>
# Limpia socket previo
ExecStartPre=/bin/rm -f /run/mpv-marquee.sock

# ¡OJO! Ajusta el conector si no es HDMI-A-1
ExecStart=/usr/bin/mpv \
  --no-config --no-terminal \
  --fs --vo=drm --hwdec=auto --no-audio \
  --image-display-duration=inf --keep-open=yes \
  --idle=yes \
  --input-ipc-server=/run/mpv-marquee.sock \
  --drm-connector=HDMI-A-1 -v

# 1) Permisos al socket cuando aparezca (hasta 5 s)
ExecStartPost=/bin/sh -c 'for i in $(seq 1 50); do [ -S /run/mpv-marquee.sock ]>
# 2) Cargar el logo en cuanto el socket esté disponible (espera hasta 60 s)
ExecStartPost=/bin/sh -c '\
  for i in $(seq 1 120); do \
    [ -S /run/mpv-marquee.sock ] && break; \
    sleep 0.5; \
  done; \
  /usr/local/bin/mostrar_rom_ipc.sh logo || true \
'

StandardOutput=journal
StandardError=journal
Restart=always
RestartSec=0.2

[Install]
WantedBy=multi-user.target


H) Windows/RetroBat — Instala el plugin Marquee Manager

  1. Abre RetroBat

  2. Ve a Updates & downloads → Content downloads → Medias & Tools → Marquee Manager y descárgalo/instálalo.

  3. Verifica que exista la carpeta:
    C:\RetroBat\plugins\MarqueeManager\

I) Windows/RetroBat — config.ini del plugin

Archivo: C:\RetroBat\plugins\MarqueeManager\config.ini
(Este es un ejemplo funcional completo; pégalo tal cual. Si usas otro ScreenNumber, cámbialo.)

[Settings]
Language = en
MarqueeWidth = 1920
MarqueeHeight = 480
MarqueeBorder = 0
MarqueeBackgroundColor = Black
MarqueeBackgroundCodeColor = #000000
MarqueeWhiteTextAlternativNameSuffix = -w
MarqueeAutoConvert = false
MarqueeAutoGeneration = false
MarqueeCompose = false
MarqueeMameOutput = true
MarqueeSupermodelOutput = true
MarqueeRetroAchievements = true
MarqueePinballDMD = true
MarqueeAutoScraping = false
MarqueeAutoScrapingDebug = false
ActiveDMD = false
ActiveSystemsDMD = fpinball, pinballfx, pinballfx2, pinballfx3, pinballfm, vpinball, zaccariapinball
ESCloseAllIfStop = true
AcceptedFormats = mp4, gif, jpg, png
#RetroBatPath = C:\RetroBat\
#RomsPath = C:\RetroBat\roms\
#DefaultImagePath = C:\RetroBat\plugins\MarqueeManager\images\default.png
MarqueeImagePath = C:\RetroBat\plugins\MarqueeManager\images\
MarqueeFilePath = {system_name}\{game_name}
#MarqueeImagePathDefault = C:\RetroBat\roms\
MarqueeFilePathDefault = {system_name}\images\{game_name}-marquee
FanartSystemFilePath = {system_name}
FanartGameFilePath = {system_name}\images\{game_name}-fanart
#SystemMarqueePath = C:\RetroBat\emulationstation\.emulationstation\themes\es-theme-carbon\art\logos
SystemFilePath = {system_name}
#CollectionMarqueePath = C:\RetroBat\emulationstation\.emulationstation\themes\es-theme-carbon\art\logos
CollectionFilePath = auto-{collection_name}
CollectionAlternativNames = custom-, arcade
CollectionCorrelation = recent:lastplayed, all:allgames, 2players:at2players, 4players:at4players, collections:custom-collections
IPCChannel = \\.\pipe\mpv-pipe
IPCChannelDMD = \\.\pipe\dmd-pipe
ScreenNumber = 1
MPVPath = C:\RetroBat\plugins\MarqueeManager\mpv\mpv.exe
MPVLaunchCommand = "{MPVPath}" "{DefaultImagePath}" --script=ra.lua --no-taskbar-progress --input-gamepad=yes --no-osc --loop-file=inf --alpha=yes --no-audio --no-input-cursor --no-input-default-bindings --idle --player-operation-mode=pseudo-gui --fs --fs-screen={ScreenNumber} --background={MarqueeBackgroundCodeColor} --screen={ScreenNumber} --keep-open=yes --input-ipc-server={IPCChannel} --geometry=1920x480 --no-keepaspect
#MPVLaunchCommand = "{MPVPath}" "{DefaultImagePath}" --script=ra.lua --no-taskbar-progress --input-gamepad=yes --no-osc --loop-file=inf --alpha=yes --no-audio --no-input-cursor --no-input-default-bindings --idle --player-operation-mode=pseudo-gui --fs --fs-screen={ScreenNumber} --background={MarqueeBackgroundCodeColor} --screen={ScreenNumber} --keep-open=yes --input-ipc-server={IPCChannel}
MPVKillCommand = taskkill /IM mpv.exe /F
MPVTestCommand = echo get_property idle>{IPCChannel}
MPVShowText = echo show-text '{message}'>{IPCChannel}
MPVPushRetroAchievementsDatas = echo {"command":["script-message","push-ra","{data}"]}>{IPCChannel}
IMPath = C:\RetroBat\plugins\MarqueeManager\imagemagick\convert.exe
IMConvertCommand = {IMPath} -background {MarqueeBackgroundColor} -alpha background -size x{MarqueeHeightBorderLess} -gravity center  "{ImgPath}"  -interpolate nearest-neighbor -geometry {MarqueeWidthBorderLess}x{MarqueeHeightBorderLess} -gravity center -extent {MarqueeWidth}x{MarqueeHeight} "{ImgTargetPath}"
IMConvertCommandMarqueeGen = {IMPath} "{FanartPath}" -resize {MarqueeWidth}x -gravity {FanartGravity} -background {MarqueeBackgroundColor} -extent {MarqueeWidth}x{MarqueeHeight}+0+{DecyOffset} "{IntermediateImgPath}"
IMConvertCommandMarqueeGenGradientLogo = {IMPath} "{IntermediateImgPath}" "{GradientPath}" -antialias -filter Lanczos -resize {LogoMaxWidth}x{LogoMaxHeight}^> -gravity {GradientGravity} -geometry {GradientPosition} -composite  "{IntermediateImgPath}"
IMConvertCommandMarqueeGenLogo = {IMPath} "{IntermediateImgPath}" "{LogoPath}" -antialias -filter Lanczos -resize {LogoMaxWidth}x{LogoMaxHeight}^> -gravity {LogoGravity} -geometry {LogoPosition} -composite "{ImgTargetPath}"
IMConvertCommandSVG = {IMPath} -background {MarqueeBackgroundColor} -alpha background -size {MarqueeWidthBorderLess}^>x{MarqueeHeightBorderLess} -gravity center  "{ImgPath}"  -interpolate nearest-neighbor -geometry {MarqueeWidthBorderLess}x{MarqueeHeightBorderLess} -gravity center -extent {MarqueeWidth}x{MarqueeHeight} "{ImgTargetPath}"
logFile = false

[Commands]
quit = taskkill /IM mpv.exe /F
reboot = echo Rebooting
shutdown = echo Shutdown
config-changed = echo Config changed
controls-changed = echo Controls changed
settings-changed = echo Settings changed
theme-changed = echo Theme changed from "{old_theme}" to "{new_theme}"
game-start = echo loadfile "{marquee_file}" replace>{IPCChannel}
game-end = echo End
sleep = echo Sleeping
wake = echo Waking up
screensaver-start = echo Screensaver started
screensaver-stop = echo Screensaver stopped
screensaver-game-select = echo Screensaver
system-select = echo loadfile "{marquee_file}" replace>{IPCChannel}
system-selected = echo loadfile "{marquee_file}" replace>{IPCChannel}
game-select = echo loadfile "{marquee_file}" replace>{IPCChannel}
game-selected = echo loadfile "{marquee_file}" replace>{IPCChannel}
game-forceupdate = echo loadfile "{marquee_file}" replace>{IPCChannel}
marquee-commpose = echo {"command":["script-message","change-img","{data}"]}>{IPCChannel}
marquee-pushtodmd = echo {"command":["script-message","pushtodmd-img","{data}"]}>{IPCChannel}
marquee-mame = echo {"command":["script-message","mame-action","{data}"]}>{IPCChannel}


Nota: para nuestra solución con RPi, el plugin solo nos interesa por ESEvents.exe (que genera ESEvent.arg). No necesitamos que MPV se muestre en Windows; nuestro Start.bat (abajo) ya mata mpv.exe para evitar duplicar pantallas.

J) Windows/RetroBat — Monitor inteligente (envío a RPi)

Archivo: C:\RetroBat\plugins\MarqueeManager\marquee_monitor.bat

@echo off
title RetroBat_MarqueeMonitor
setlocal enabledelayedexpansion

REM ================== CONFIG ==================
set "ESEVENT_FILE=C:\RetroBat\plugins\MarqueeManager\ESEvent.arg"
set "NCAT_EXE=C:\Program Files (x86)\Nmap\ncat.exe"
set "RPI_IP=192.168.1.138"
set "RPI_PORT=12345"
set "LOG=C:\temp\marquee_debug.log"
REM Separador que entiende la RPi (mejor ":" para evitar problemas de pipe en CMD)
set "SEPARATOR=:"
REM ============================================

if not exist "C:\temp" mkdir "C:\temp"

set "LAST_ROM="
set "LAST_SYS="
set "LAST_COMBO="
set "CURRENT_SYSTEM="
set "INITIALIZED=0"

echo Iniciando monitor inteligente de selección (sistema + juegos)...
echo ---------------------------------------------------------------

:loop
timeout /t 1 >nul

if not exist "%ESEVENT_FILE%" goto loop

REM === Tomar la ULTIMA línea del archivo ===
set "LINE="
for /f "usebackq delims=" %%A in ("%ESEVENT_FILE%") do (    set "LINE=%%A"
)

REM =======================
REM 1) EVENTO DE JUEGO
REM =======================
echo !LINE! | findstr /I "event=game-selected" >nul
if not errorlevel 1 (    REM Extraer param2 (ruta ROM) -> 3er token separado por &    for /f "tokens=3 delims=&" %%a in ("!LINE!") do (        set "ROM_PATH=%%~a"    )    REM Limpiar comillas y normalizar separadores    set "ROM_PATH=!ROM_PATH:"=!"    set "ROM_PATH=!ROM_PATH:/=\!"
    REM Nombre base sin extensión (entre comillas para conservar espacios/paréntesis)    for %%F in ("!ROM_PATH!") do (        set "ROM_NAME=%%~nF"    )
    REM Si no tenemos CURRENT_SYSTEM aún, usar el último sistema conocido    if "!CURRENT_SYSTEM!"=="" if not "!LAST_SYS!"=="" set "CURRENT_SYSTEM=!LAST_SYS!"
    REM Intento extra: si sigue vacío, sacar param1=<sistema> de esta misma línea    if "!CURRENT_SYSTEM!"=="" (        set "P1TMP="        for /f "tokens=1-10 delims=&" %%i in ("!LINE!") do (            for %%t in (%%i %%j %%k %%l %%m %%n %%o %%p %%q %%r) do (                echo %%t | findstr /I /B "param1=" >nul && set "P1TMP=%%t"            )        )        if defined P1TMP (            for /f "tokens=2 delims==" %%u in ("!P1TMP!") do set "CURRENT_SYSTEM=%%~u"            set "CURRENT_SYSTEM=!CURRENT_SYSTEM:"=!"            for /f "tokens=* delims= " %%z in ("!CURRENT_SYSTEM!") do set "CURRENT_SYSTEM=%%z"        )    )
    REM Debug    echo [%time%] Juego detectado: ROM="!ROM_NAME!" SYS="!CURRENT_SYSTEM!" >> "%LOG%"
    REM Inicialización: no enviamos el primer evento para evitar repeticiones en arranque    if "!INITIALIZED!"=="0" (        set "LAST_ROM=!ROM_NAME!"        if not "!CURRENT_SYSTEM!"=="" set "LAST_SYS=!CURRENT_SYSTEM!"        set "LAST_COMBO=!CURRENT_SYSTEM!!SEPARATOR!!ROM_NAME!"        set "INITIALIZED=1"        echo [%time%] Inicializado con COMBO: !LAST_COMBO! >> "%LOG%"        goto loop    )
    if "!ROM_NAME!"=="" goto loop    if "!CURRENT_SYSTEM!"=="" (        echo [%time%] Aviso: sin sistema resuelto para la ROM "!ROM_NAME!", no se envía. >> "%LOG%"        goto loop    )
    set "COMBO=!CURRENT_SYSTEM!!SEPARATOR!!ROM_NAME!"    if /I "!COMBO!"=="!LAST_COMBO!" goto loop
    echo [%time%] Enviando COMBO !COMBO! a la Raspberry Pi... >> "%LOG%"    echo "!COMBO!" | "!NCAT_EXE!" %RPI_IP% %RPI_PORT%
    set "LAST_ROM=!ROM_NAME!"    set "LAST_SYS=!CURRENT_SYSTEM!"    set "LAST_COMBO=!COMBO!"    goto loop
)

REM =======================
REM 2) EVENTO DE SISTEMA
REM    (admite system-selected y system-changed)
REM =======================
echo !LINE! | findstr /I "event=system-selected event=system-changed" >nul
if not errorlevel 1 (    REM param1="nombre_del_sistema" -> 2º token separado por &    for /f "tokens=2 delims=&" %%a in ("!LINE!") do (        set "SYS_KV=%%~a"    )    for /f "tokens=2 delims==" %%b in ("!SYS_KV!") do (        set "SYSTEM_RAW=%%~b"    )    REM Quitar comillas y recortar espacios iniciales    set "SYSTEM=!SYSTEM_RAW:"=!"    for /f "tokens=* delims= " %%z in ("!SYSTEM!") do set "SYSTEM=%%z"
    REM Debug    echo [%time%] Sistema detectado: "!SYSTEM!" >> "%LOG%"
    if "!INITIALIZED!"=="0" (        set "CURRENT_SYSTEM=!SYSTEM!"        set "LAST_SYS=!SYSTEM!"        set "INITIALIZED=1"        echo [%time%] Inicializado con SISTEMA: !SYSTEM! >> "%LOG%"        goto loop    )
    if "!SYSTEM!"=="" goto loop    if /I "!SYSTEM!"=="!LAST_SYS!" goto loop
    echo [%time%] Enviando SISTEMA !SYSTEM! a la Raspberry Pi... >> "%LOG%"    echo "!SYSTEM!" | "!NCAT_EXE!" %RPI_IP% %RPI_PORT%
    set "CURRENT_SYSTEM=!SYSTEM!"    set "LAST_SYS=!SYSTEM!"    REM opcional: resetea combo al cambiar de sistema    set "LAST_COMBO="    goto loop
)

goto loop


K) Windows/RetroBat — Start.bat del plugin (sustituye al lanzamiento “Start.exe”)

Archivo: C:\RetroBat\plugins\MarqueeManager\Start.bat

Uso recomendado: en lugar de abrir Start.exe del plugin, ejecuta este Start.bat. Si quieres, renombra el Start.exe original a Start.exe.bak para no usarlo por error.
@echo off
setlocal enabledelayedexpansion

:: === CONFIGURAR RUTA Y TÍTULO DEL MONITOR ===
set "MONITOR_BAT=C:\RetroBat\plugins\MarqueeManager\marquee_monitor.bat"
set "MONITOR_TITLE=RetroBat_MarqueeMonitor"

cd /d "%~dp0"

:: --- Cerrar procesos de RetroBat previos (incluye mpv para no duplicar pantalla en Windows) ---
for %%P in (  ESEvents.exe  ESEventsScrapTopper.exe  ESRetroAchievements.exe  VPListenerWS.exe  MAMEListenerWS.exe  SUPERMODELListenerWS.exe  retroarch.exe  mpv.exe  dmd.exe  emulationstation.exe
) do (  tasklist | find /I "%%P" > NUL && taskkill /IM %%P /F
)

:: --- Limpiar tmp del MarqueeManager ---
rd /s /q ".\.tmp" 2>NUL

:: === INICIAR EL MONITOR EN CONSOLA PROPIA ===
start "%MONITOR_TITLE%" cmd /k ""%MONITOR_BAT%""

:: --- Iniciar ESEvents.exe (genera ESEvent.arg que lee el monitor) ---
start "" ESEvents.exe
timeout /t 1 /nobreak >NUL

:: --- Iniciar RetroBat ---
start "" ..\..\retrobat.exe
timeout /t 2 /nobreak >NUL

:: === Vigilar hasta que RetroBat se cierre ===
:wait_rb
tasklist | find /I "retrobat.exe" >NUL
if not errorlevel 1 (    timeout /t 2 >NUL    goto wait_rb
)
tasklist | find /I "emulationstation.exe" >NUL
if not errorlevel 1 (    timeout /t 2 >NUL    goto wait_rb
)

:: === CERRAR EL MONITOR ===
taskkill /FI "WINDOWTITLE eq %MONITOR_TITLE%" /T /F >NUL 2>&1

endlocal


L) Dónde van las imágenes/vídeos

M) Activación y pruebas

En la RPi:

sudo systemctl daemon-reload
sudo systemctl enable --now marquee-mpv.service recibir_rom.service
systemctl status marquee-mpv.service --no-pager
systemctl status recibir_rom.service --no-pager
ls -l /run/mpv-marquee.sock
tail -n 50 /home/arcade/recibir_rom.log
tail -n 100 /home/arcade/mostrar_rom_debug.log


En Windows (CMD):

REM Pruebas directas al listener
echo "mame:1942" | "C:\Program Files (x86)\Nmap\ncat.exe" 192.168.1.138 12345
echo "vertical:1942" | "C:\Program Files (x86)\Nmap\ncat.exe" 192.168.1.138 12345
echo "megadrive:FIFA International Soccer (USA|v Europe)" | "C:\Program Files (x86)\Nmap\ncat.exe" 192.168.1.138 12345
echo "all:FIFA International Soccer (USA|v Europe)" | "C:\Program Files (x86)\Nmap\ncat.exe" 192.168.1.138 12345
echo "logo" | "C:\Program Files (x86)\Nmap\ncat.exe" 192.168.1.138 12345


Lanzamiento normal desde Windows:

¡Listo!

Con esto queda:

Discussions