lundi 16 mars 2020

Retrogaming: Script "bluetooth" Recalbox (et bon deal sur les manettes Nintendo ;-)

En fait, il y a un bon deal en ce moment pour les manettes NES (-50% jusqu'à fin mars): https://mynintendostore.nintendo.fr/catalog/product/view/id/616/


mais je trouve aussi la manette SNES sympa : https://mynintendostore.nintendo.fr/super-nintendo-entertainment-system-controller-for-nintendo-switch.html

Et j'ai compris que c'est possible de l'avoir sur Recalbox ;-) et j'ai réussi en utilisant ce tuto pour joycon qui est similaire à ce type de manette: https://github.com/recalbox/recalbox-os/wiki/Connecter-Nintendo-joycon-(FR)

Parce que par le menu de recalbox, je n'y arrivais pas :-(
Et dans tous les cas, il faut associer la manette à chaque fois et à chaque allumage....
Donc j'ai voulu automatiser cela.

Voici donc le script que j'ai fait pour automatiser cela sur ma Recalbox:


#!/bin/sh

#player 1 : 48:A5:E7:5D:AF:EB

# run bluetothctl

bluetoothctl <<EOF
agent on
default-agent
power on
scan on
pair 48:A5:E7:5D:AF:EB
connect 48:A5:E7:5D:AF:EB
trust 48:A5:E7:5D:AF:EB
EOF

Remarque: attention, il faut refaire un appui long sur le bouton "sync" de la manette avant lancement de ce script.

Pour le lancement, je le fais par la command "sh" parce que sinon, j'ai une erreur du au EOF dans le script je pense... (à vous me dire dans les commentaires si vous savez pourquoi ?!):


# sh ./connect_snes_controllers.sh
[NEW] Controller B8:27:EB:B2:8A:6B Recalbox [default]
[NEW] Device 48:A5:E7:5D:AF:EB SNES Controller
[bluetooth]# agent on
[bluetooth]# default-agent
No agent is registered
[bluetooth]# power on
[bluetooth]# scan on
[bluetooth]# pair 48:A5:E7:5D:AF:EB
Attempting to pair with 48:A5:E7:5D:AF:EB
[bluetooth]# connect 48:A5:E7:5D:AF:EB
Attempting to connect to 48:A5:E7:5D:AF:EB
[bluetooth]# trust 48:A5:E7:5D:AF:EB
[bluetooth]# quit
No agent is registered
[DEL] Controller B8:27:EB:B2:8A:6B Recalbox [default]
#

Maintenant, je vais réfléchir pour pouvoir lancer l'appairage (d'une ou plusieurs manettes...) et trouver un moyen pour que cela soit plus simples qu'avec le menu de la Recalbox... par Alexa ? Xiaomi magic cube ? combinaison de touches de mon panel ? autre ?...

Sinon... Enjoy ! ;-)

mercredi 4 mars 2020

Retrogaming & Domotique: Un script pour tous les allumer ;-)

En fait, maintenant je veux pouvoir allumer ou éteindre les boutons de chaque joueur du panel en fonction du nombre de joueurs maximum pour un jeu et en fonction des boutons vraiment utilisable pour un système donné...

Voici comment j'ai procédé et donc comment vous pouvez reproduire chez vous...

1) en terme de matos et câblage, je dois vous en parler avant de vous parler du script :

J'ai donc pensé cela ainsi (voir dessin ci-après) en terme de câblage, j'ai mis le bon nombre de relais pour pouvoir gérer les 40 boutons du panel (et oui, cela fait pas mal ;-) et j'ai trouvé une astuce pour utiliser que 12 relais et pas 40 !


Vous avez compris l'astuce ? sûrement !  En fait, je contrôle les boutons "player" & "coin" (et en fait aussi les boutons A/B/X/Y/L1/R1/L2/R2) par la masse pour allumer ou éteindre les boutons d'un joueur complet avec un seul relais (donc que 4 entre tous sont nécessaires dans ce cas ;-). Et les 8 autres relais peuvent gérer tout les boutons de jeux de tous les joueurs. Donc on ne le voit pas sur le croquis mais tout les boutons A sont reliés ensemble , les B aussi et ainsi de suite pour tous.

J'ai donc du allonger les câbles que l'on a par défaut pour alimenter chaque led de chaque bouton, j'ai du passer en tout 4h pour tout câbler :-(.
Voici une photo de l'intérieur pour vous montrer l'impact sur le câblage si on compare à un câblage classique:


J'ai donc rajouter une board 8 relais et une de 4 relais pour faire le boulot:


Et pour les contrôler, je n'ai pas pris le rapsberry pi et ses GPIOs, parce que je ne voulais pas un cablâge lourds entre mon Rpi 3 et le panel, aujourd'hui, j'ai juste l'USB entre le panel et le Rpi 3, je ne veux pas plus.

Donc j'ai choisi de mettre 2 Wemos D1 Mini (à base de ESP8266) qui seront contrôlable en wifi et pilotable par les scripts de l'EmulationStation présent dans RecalBox. J'en ai pris 2 pour adresser les 2 boards. J'aurais pu en utiliser un seul, mais j'aurai du utiliser un multiplexeur pour adresser 12 relais. Avec 2 Wemos D1 Mini, je peux adresser 16 facilement sans rajouter d'électronique supplémentaire:


Les 2 boards de relais sont alimentés par les Wemos D1 Mini eux mêmes via l'alimentation distincte en microUSB Qualcomm en 5V/2A (j'ai pris large ;-):



mais je ne voulais pas passer par les hubs USB présents sur la photo qui alimentent déjà les cartes des joysticks et la lumière des boutons.

2) Voici d'abord les programmes internes en LUA des Wemos D1 Mini à base du firmware NODEMCU, pour plus d'information sur ce type de device et ses scripts, allez voir ces articles que j'ai déjà publié:
https://bozothegeek.blogspot.com/search?q=nodemcu

Voici le set de script pour la version 4 relais depuis le drive.

Voici le set de script pour la version 8 relais depuis le drive (ce qui change est le fichier de conf du node en fait).

3) Donc voici ce que j'ai commencé de faire, pour l'instant, j'ai intégré que quelques systèmes... (nes, super nintendo, vectrex, megadrive, seg32x, neogeo, mame, pcengine, nintendo 64, atari 2600, atari 7800, master system), pour les autres systèmes, je laisse donc tout les boutons de jeux allumés par défaut.

Voici les boutons que je considère actif pour l'instant par système (et il y a d'autres systèmes ;-) :








Et donc avec le script suivant, je peux les contrôles en fonction du système et de la rom selectionnée via des arguments soumis à ce script , exemple en ligne de commande:

python PanelLights.py -system snes -rom "/recalbox/share/roms/snes/Aladdin (France).zip")

C'est un script basé sur gamerlistpower donc je l'ai mis dans : /recalbox/scripts/gamelistpower/ pour que cela fonctionne.
On verra ensuite comment on l’intègre à EmulationStation:


#!/usr/bin/env python
#IMPORT STD---------------------------------------------------------------------
import os.path
import sys
import time
import shutil
import argparse
from os.path import basename, splitext
from threading import Thread
# importing the requests library 
import requests 

#IMPORT NORDICPOWER------------------------------------------------------------                             
from glplib import *

#CONSTANTS---------------------------------------------------------------------                             
VERSION='0.0.1 BETA 26/01/2020'
SOURCE_NAME='BozoTheGeek'
URL_ESP8266_1 = "http://192.168.0.106/"
URL_ESP8266_2 = "http://192.168.0.107/"

SYSTEMS_BUTTONS_MAPPING =\
{
#   SYSTEM            A B X Y R1L1R2L2
#   6 button console    
    'snes'         : [1,1,1,1,1,1,0,0],
    'sega32x'      : [1,1,1,1,1,1,0,0],
#   2 buttons console    
    'nes'          : [1,1,0,0,0,0,0,0],
    'mastersystem' : [1,1,0,0,0,0,0,0],
    'pcengine'     : [1,1,0,0,0,0,0,0],
    'atari7800'    : [1,1,0,0,0,0,0,0],
#   1 button console    
    'atari2600'    : [0,1,0,0,0,0,0,0],
#   3 buttons console 
    'megadrive'    : [1,1,0,0,1,0,0,0],
#   4 buttons console 
    'neogeo'       : [1,1,0,0,1,0,1,0],
    'vectrex'      : [1,1,0,0,1,0,1,0],
#   All buttons
    'mame'         : [1,1,1,1,1,1,1,1],
    'n64'          : [1,1,1,1,1,1,1,1],
}
def buttons_light(system):
  # api-endpoint (using of dedicated ESP8266 (D0 to D8 on Wemos D1 Mini))
  URL = URL_ESP8266_2
  for x in range(0, 8):
    if system in SYSTEMS_BUTTONS_MAPPING:
    # defining a params dict for the parameters to be sent to the API 
      if SYSTEMS_BUTTONS_MAPPING[system][x] == 1:
        state = 'on'
      else:
        state = 'off'
      PARAMS = {'D'+ str(x):state}
    else:  
      # defining a params dict for the parameters to be sent to the API 
      PARAMS = {'D'+ str(x):'on'}
    #display PARAMS  
    logger.info('Parameters : ' + json.dumps(PARAMS))
    # sending get request and saving the response as response object 
    r = requests.get(url = URL, params = PARAMS)
    #extracting response text  
    logger.info('Result : ' + r.text)

def players_light(nbplayers,off=False):
  # api-endpoint (using of dedicated ESP8266 (D1 to D4 on Wemos D1 Mini))
  URL = URL_ESP8266_1
  if off == False:
    for x in range(1, 5):
      if nbplayers >= x:
        state = 'on'
      else:
        state = 'off'
      # defining a params dict for the parameters to be sent to the API 
      PARAMS = {'D'+ str(x):state}
      #display PARAMS  
      logger.info('Parameters : ' + json.dumps(PARAMS)) 
      # sending get request and saving the response as response object 
      r = requests.get(url = URL, params = PARAMS)
      #extracting response text  
      logger.info('Result : ' + r.text)
  else:
    for x in range(1, 5):
      # defining a params dict for the parameters to be sent to the API 
      PARAMS = {'D'+ str(x):'off'}
      #display PARAMS  
      logger.info('Parameters : ' + json.dumps(PARAMS)) 
      # sending get request and saving the response as response object 
      r = requests.get(url = URL, params = PARAMS)
      #extracting response text  
      logger.info('Result : ' + r.text)

#---------------------------------------------------------------------------------------------------
def get_args():
  #example of command: python PanelLights.py -system mame -rom /recalbox/share/roms/mame/outrun.zip
  #example of command: python PanelLights.py -system snes -rom "/recalbox/share/roms/snes/Aladdin (France).zip"
  #example of command: python PanelLights.py -off true
  #example of command: python PanelLights.py -system atari2600 -rom "/recalbox/share/roms/atari2600/Arcade Pong (Flashback 2 Version).a26"
  parser = argparse.ArgumentParser(description='Pilotage lumieres boutons PANEL ARCADE 4 joueurs',epilog='(C) BOZO THE GEEK')
  parser.add_argument("-off", help="to poweroff the panel", type=bool, required=False)
  parser.add_argument("-system", help="select the system to launch", type=str, required=False)
  parser.add_argument("-rom", help="rom absolute path", type=str, required=False)
  parser.add_argument("-emulator", help="force emulator", type=str, required=False)
  parser.add_argument("-core", help="force emulator core", type=str, required=False)
  parser.add_argument("-ratio", help="force game ratio", type=str, required=False)
  parser.add_argument("-demo", help="mode demo", type=bool, required=False)
  return parser.parse_args()
#------------------------------------------------------------------------------
#---------------------------------------- MAIN --------------------------------                      
#------------------------------------------------------------------------------

def main():
  #Test Arg
  args=get_args()
  
  #Init Log
  logger=Logger.logr
  #if args.log==ARG_LOG_DEBUG:
  Logger.setLevel(logging.DEBUG)
  #elif args.log==ARG_LOG_INFO:
  #       Logger.setLevel(logging.INFO)
  #elif args.log==ARG_LOG_ERROR:
  #       Logger.setLevel(logging.ERROR)
  Logger.add_handler_console()
  
  if args.off:
    players_light(4,True)
    sys.exit(0)  
  
  logger.info('System paraemter :' + args.system)
  System = args.system
  logger.info('Rom parameter :' + args.rom)
  FullRomLocation = args.rom
  
  #Lecture Configuration
  #config.load_from_file()
  
  #Execution des patchs
  #gameListPatch = GameListPatcher(config,'console',args.mode)
  #gameListPatch.start()
  #gameListPatch.join()
  
  #Chargement XML avec MiniDom :-<
  #test 1 OK
  #System = 'mame'
  #FullRomLocation = '/recalbox/share/roms/mame/outrun.zip'
  #test 2 OK
  #System = 'n64'
  #FullRomLocation = '/recalbox/share/roms/n64/Legend\ of\ Zelda,\ The\ -\ Majora\'s\ Mask.z64'
  #test 3 OK
  #System = 'megadrive'
  #FullRomLocation = '/recalbox/share/roms/megadrive/Sonic\ \&\ Knuckles\ \(World\).zip'
  #test 4 OK
  #System = 'snes'
  #FullRomLocation = '/recalbox/share/roms/snes/Tiny\ Toon\ Adventures\ -\ Wild\ \&\ Wacky\ Sports\ \(Europe\)\ \(Beta\).zip'
  #test 5 OK
  #System = 'neogeo'
  #FullRomLocation = '/recalbox/share/roms/neogeo/mslug3b6.zip'    
  RomsDirectory = FullRomLocation.split(System)[0]
  Rom = FullRomLocation.split(System)[1]
  #to remove \ if necessary
  Rom = Rom.replace("\\","")
  gamesList = GamesList()
  #try:
  gamesList.import_xml_file(RomsDirectory + System + os.sep + NOM_GAMELIST_XML,True)
  logger.info('Gamelist file imported:' + RomsDirectory + System + os.sep + NOM_GAMELIST_XML)
  logger.info('Rom to find : ' + Rom)
  game = gamesList.search_game_by_path ('.' + Rom)
  
  if game.name != '':
    logger.info('file found, the game name is :' + game.name)
    logger.info('the number of player is : ' + game.players)
  
  if game.players != '':
    nbplayers = int(game.players[-1])
  else:
    nbplayers = 1
  logger.info('the max number of player is : ' + str(nbplayers))
  
  players_light(nbplayers)
      
  buttons_light(System)
  
  sys.exit(0)

#---------------------------------------------------------------------------------------------------
if __name__ == '__main__':
  main()

4) Ensuite il faut aussi modifier le fichier python de EmulationStation pour lancer le script précédent quand on lance un jeu :

le script python en question est ici : /usr/lib/python2.7/site-packages/configgen/emulatorlauncher.py
que l'on devra recompiler en .pyc aussi.

Il faut donc le modifier ainsi... regardez la partie en jaune qu'il faut rajouter :

#!/usr/bin/env python
import argparse
from Emulator import Emulator
import utils.runner as runner
import recalboxFiles
#added by B0ZOTHEGEEK : manage light of arcade panel
import subprocess

from controllersConfig import Controller
from settings.configOverriding import buildOverrideChain
from settings.keyValueSettings import keyValueSettings

lineAppleGeneratorOverride = None

def getGenerator(emulator):

    # We use a if...elif...else structure in order to instantiate the strict minimum required for an emulator
    # and minimize static imports that are very time consuming, specialy on low-end boards
    if emulator == "libretro":
        module = __import__("generators.libretro.libretroGenerator", fromlist=["LibretroGenerator"])
        generatorClass = getattr(module, "LibretroGenerator")
        return generatorClass()
    elif emulator == "mupen64plus":
        module = __import__("generators.mupen.mupenGenerator", fromlist=["MupenGenerator"])
        generatorClass = getattr(module, "MupenGenerator")
        return generatorClass()
    elif emulator == "reicast":
        module = __import__("generators.reicast.reicastGenerator", fromlist=["ReicastGenerator"])
        generatorClass = getattr(module, "ReicastGenerator")
        return generatorClass()
    elif emulator == "dolphin":
        module = __import__("generators.dolphin.dolphinGenerator", fromlist=["DolphinGenerator"])
        generatorClass = getattr(module, "DolphinGenerator")
        return generatorClass()
    elif emulator == "ppsspp":
        module = __import__("generators.ppsspp.ppssppGenerator", fromlist=["PPSSPPGenerator"])
        generatorClass = getattr(module, "PPSSPPGenerator")
        return generatorClass()
    elif emulator == "amiberry":
        module = __import__("generators.amiberry.amiberryGenerator", fromlist=["AmiberryGenerator"])
        generatorClass = getattr(module, "AmiberryGenerator")
        return generatorClass()
    elif emulator == "daphne":
        module = __import__("generators.daphne.daphneGenerator", fromlist=["DaphneGenerator"])
        generatorClass = getattr(module, "DaphneGenerator")
        return generatorClass()
    elif emulator == "scummvm":
        module = __import__("generators.scummvm.scummvmGenerator", fromlist=["ScummVMGenerator"])
        generatorClass = getattr(module, "ScummVMGenerator")
        return generatorClass()
    elif emulator == "dosbox":
        module = __import__("generators.dosbox.dosboxGenerator", fromlist=["DosBoxGenerator"])
        generatorClass = getattr(module, "DosBoxGenerator")
        return generatorClass()
    elif emulator == "residualvm":
        module = __import__("generators.residualvm.residualvmGenerator", fromlist=["ResidualVMGenerator"])
        generatorClass = getattr(module, "ResidualVMGenerator")
        return generatorClass()
    elif emulator == "advancemame":
        module = __import__("generators.advancemame.advMameGenerator", fromlist=["AdvMameGenerator"])
        generatorClass = getattr(module, "AdvMameGenerator")
        return generatorClass()
    elif emulator == "simcoupe":
        module = __import__("generators.simcoupe.simcoupeGenerator", fromlist=["SimCoupeGenerator"])
        generatorClass = getattr(module, "SimCoupeGenerator")
        return generatorClass()
    elif emulator == "gsplus":
        module = __import__("generators.gsplus.gsplusGenerator", fromlist=["GSplusGenerator"])
        generatorClass = getattr(module, "GSplusGenerator")
        return generatorClass()
    elif emulator == "oricutron":
        module = __import__("generators.oricutron.oricutronGenerator", fromlist=["OricutronGenerator"])
        generatorClass = getattr(module, "OricutronGenerator")
        return generatorClass()
    elif emulator == "linapple":
        if lineAppleGeneratorOverride is not None:
            return lineAppleGeneratorOverride
        module = __import__("generators.linapple.linappleGenerator", fromlist=["LinappleGenerator"])
        generatorClass = getattr(module, "LinappleGenerator")
        os = __import__("os")
        return generatorClass(os.path.join(recalboxFiles.HOME_INIT, '.linapple'),
                              os.path.join(recalboxFiles.HOME     , '.linapple'))
    elif emulator == "kodi":
        module = __import__("generators.kodi.kodiGenerator", fromlist=["KodiGenerator"])
        generatorClass = getattr(module, "KodiGenerator")
        return generatorClass()
    elif emulator == "fba2x":
        module = __import__("generators.fba2x.fba2xGenerator", fromlist=["Fba2xGenerator"])
        generatorClass = getattr(module, "Fba2xGenerator")
        return generatorClass()
    elif emulator == "moonlight":
        module = __import__("generators.moonlight.moonlightGenerator", fromlist=["MoonlightGenerator"])
        generatorClass = getattr(module, "MoonlightGenerator")
        return generatorClass()
    elif emulator == "vice":
        module = __import__("generators.vice.viceGenerator", fromlist=["ViceGenerator"])
        generatorClass = getattr(module, "ViceGenerator")
        return generatorClass()
    elif emulator == "pcsx_rearmed":
        module = __import__("generators.pcsx.pcsxGenerator", fromlist=["PcsxGenerator"])
        generatorClass = getattr(module, "PcsxGenerator")
        return generatorClass()
    elif emulator == "pisnes":
        module = __import__("generators.pisnes.pisnesGenerator", fromlist=["PisnesGenerator"])
        generatorClass = getattr(module, "PisnesGenerator")
        return generatorClass()
    else:
        print("Missing generator for {}".format(emulator))
        raise ValueError
        pass


# List emulators with their cores rest mupen64, scummvm
def getDefaultEmulator(targetSystem):
    emulators = \
    {
        # Nintendo
        "snes"          : Emulator(name='snes', emulator='libretro', core='snes9x2002'),
        "nes"           : Emulator(name='nes', emulator='libretro', core='fceunext'),
        "n64"           : Emulator(name='n64', emulator='mupen64plus', core='gliden64'),
        "gba"           : Emulator(name='gba', emulator='libretro', core='mgba'),
        "gb"            : Emulator(name='gb', emulator='libretro', core='gambatte'),
        "gbc"           : Emulator(name='gbc', emulator='libretro', core='gambatte'),
        "fds"           : Emulator(name='fds', emulator='libretro', core='nestopia'),
        "virtualboy"    : Emulator(name='virtualboy', emulator='libretro', core='mednafen_vb'),
        "gamecube"      : Emulator(name='gamecube', emulator='dolphin'),
        "wii"           : Emulator(name='wii', emulator='dolphin'),
        "nds"           : Emulator(name='nds', emulator='libretro', core='desmume'),
        "pokemini"      : Emulator(name='pokemini', emulator='libretro', core='pokemini'),
        "satellaview"   : Emulator(name='satellaview', emulator='libretro', core='snes9x'),
        "sufami"        : Emulator(name='sufami', emulator='libretro', core='snes9x'),
        "gw"            : Emulator(name='gw', emulator='libretro', core='gw'),
        "3ds"           : Emulator(name='3ds', emulator='libretro', core='citra_canary'),

        # Sega
        "sg1000"        : Emulator(name='sg1000', emulator='libretro', core='genesisplusgx'),
        "mastersystem"  : Emulator(name='mastersystem', emulator='libretro', core='picodrive'),
        "megadrive"     : Emulator(name='megadrive', emulator='libretro', core='picodrive'),
        "gamegear"      : Emulator(name='gamegear', emulator='libretro', core='genesisplusgx'),
        "sega32x"       : Emulator(name='sega32x', emulator='libretro', core='picodrive'),
        "segacd"        : Emulator(name='segacd', emulator='libretro', core='picodrive'),
        "dreamcast"     : Emulator(name='dreamcast', emulator='reicast', core='reicast'),
        "saturn"        : Emulator(name='saturn', emulator='libretro', core='mednafen_saturn'),

        # Arcade
        "neogeo"        : Emulator(name='neogeo', emulator='fba2x'),
        "mame"          : Emulator(name='mame', emulator='libretro', core='mame2003_plus'),
        "fba"           : Emulator(name='fba', emulator='fba2x'),
        "fba_libretro"  : Emulator(name='fba_libretro', emulator='libretro', core='fbneo'),
        "advancemame"   : Emulator(name='advancemame', emulator='advmame'),
        "daphne"        : Emulator(name='daphne', emulator='daphne'),
        "neogeocd"      : Emulator(name='neogeocd', emulator='libretro', core='fbneo'),
        "atomiswave"    : Emulator(name='atomiswave', emulator='libretro', core='flycast'),
        "naomi"         : Emulator(name='naomi', emulator='libretro', core='flycast'),

        # Atari
        "atari2600"     : Emulator(name='atari2600', emulator='libretro', core='stella'),
        "atari5200"     : Emulator(name='atari5200', emulator='libretro', core='atari800'),
        "atari7800"     : Emulator(name='atari7800', emulator='libretro', core='prosystem'),
        "lynx"          : Emulator(name='lynx', emulator='libretro', core='handy'),
        "jaguar"        : Emulator(name='jaguar', emulator='libretro', core='virtualjaguar'),

        # Computers
        "amiga600"      : Emulator(name='amiga600', emulator='amiberry', core='amiberry'),
        "amiga1200"     : Emulator(name='amiga1200', emulator='amiberry', core='amiberry'),
        "msx1"          : Emulator(name='msx1', emulator='libretro', core='bluemsx'),
        "msx2"          : Emulator(name='msx2', emulator='libretro', core='bluemsx'),
        "msxturbor"     : Emulator(name='msxturbor', emulator='libretro', core='bluemsx'),
        "spectravideo"  : Emulator(name='spectravideo', emulator='libretro', core='bluemsx'),
        "amstradcpc"    : Emulator(name='amstradcpc', emulator='libretro', core='cap32'),
        "apple2"        : Emulator(name='apple2', emulator='linapple', videomode='default'),
        "apple2gs"      : Emulator(name='apple2gs', emulator='gsplus', videomode='default'),
        "atarist"       : Emulator(name='atarist', emulator='libretro', core='hatari'),
        "zxspectrum"    : Emulator(name='zxspectrum', emulator='libretro', core='fuse'),
        "o2em"          : Emulator(name='odyssey2', emulator='libretro', core='o2em'),
        "zx81"          : Emulator(name='zx81', emulator='libretro', core='81'),
        "dos"           : Emulator(name='dos', emulator='dosbox', videomode='default'),
        "c128"          : Emulator(name='c128', emulator='libretro', core='vice_x128'),
        "c64"           : Emulator(name='c64', emulator='libretro', core='vice_x64'),
        "pet"           : Emulator(name='pet', emulator='libretro', core='vice_xpet'),
        "plus4"         : Emulator(name='plus4', emulator='libretro', core='vice_xplus4'),
        "vic20"         : Emulator(name='vic20', emulator='libretro', core='vice_xvic'),
        "x1"            : Emulator(name='x1', emulator='libretro', core='xmil'),
        "x68000"        : Emulator(name='x68000', emulator='libretro', core='px68k'),
        "thomson"       : Emulator(name='thomson', emulator='libretro', core='theodore'),
        "atari800"      : Emulator(name='atari800', emulator='libretro', core='atari800'),
        "oricatmos"     : Emulator(name='oricatmos', emulator='oricutron', videomode='default'),
        "samcoupe"      : Emulator(name='samcoupe', emulator='simcoupe', videomode='default'),
        "pc88"          : Emulator(name='pc88', emulator='libretro', core='quasi88'),
        "pc98"          : Emulator(name='pc98', emulator='libretro', core='np2kai'),
        "macintosh"     : Emulator(name='macintosh', emulator='libretro', core='minivmac'),
        "tic80"         : Emulator(name='tic80', emulator='libretro', core='tic80'),

        # Game engines
        "scummvm"       : Emulator(name='scummvm', emulator='scummvm', videomode='default'),
        "prboom"        : Emulator(name='prboom', emulator='libretro', core='prboom'),
        "lutro"         : Emulator(name='lutro', emulator='libretro', core='lutro'),
        "cavestory"     : Emulator(name='cavestory', emulator='libretro', core='nxengine'),

        # Consoles
        "psp"           : Emulator(name='psp', emulator='ppsspp'),
        "ngp"           : Emulator(name='ngp', emulator='libretro', core='mednafen_ngp'),
        "ngpc"          : Emulator(name='ngpc', emulator='libretro', core='mednafen_ngp'),
        "vectrex"       : Emulator(name='vectrex', emulator='libretro', core='vecx'),
        "wswan"         : Emulator(name='wswan', emulator='libretro', core='mednafen_wswan', ratio='16/10'),
        "wswanc"        : Emulator(name='wswanc', emulator='libretro', core='mednafen_wswan', ratio='16/10'),
        "pcengine"      : Emulator(name='pcengine', emulator='libretro', core='mednafen_supergrafx'),
        "pcenginecd"    : Emulator(name='pcenginecd', emulator='libretro', core='mednafen_supergrafx'),
        "pcfx"          : Emulator(name='pcfx', emulator='libretro', core='mednafen_pcfx'),
        "supergrafx"    : Emulator(name='supergrafx', emulator='libretro', core='mednafen_supergrafx'),
        "psx"           : Emulator(name='psx', emulator='libretro', core='pcsx_rearmed'),
        "intellivision" : Emulator(name='intellivision', emulator='libretro', core='freeintv'),
        "channelf"      : Emulator(name='channelf', emulator='libretro', core='freechaf'),
        "colecovision"  : Emulator(name='colecovision', emulator='libretro', core='bluemsx'),
        "3do"           : Emulator(name='3do', emulator='libretro', core='4do'),
        "amigacd32"     : Emulator(name='amigacd32', emulator='amiberry', core='amiberry'),
        "amigacdtv"     : Emulator(name='amigacdtv', emulator='amiberry', core='amiberry'),
        "gx4000"        : Emulator(name='gx4000', emulator='libretro', core='cap32'),
        "uzebox"        : Emulator(name='uzebox', emulator='libretro', core='uzem'),
        "palm"          : Emulator(name='palm', emulator='libretro', core='mu'),
        "multivision"   : Emulator(name='multivision', emulator='libretro', core='gearsystem'),

        # Miscellaneous
        "imageviewer"   : Emulator(name='imageviewer', emulator='libretro', core='imageviewer'),
        "kodi"          : Emulator(name='kodi', emulator='kodi', videomode='default'),
        "moonlight"     : Emulator(name='moonlight', emulator='moonlight'),
    }

    if targetSystem in emulators:
        return emulators[targetSystem]

    return None


def loadRecalboxSettings(rom, systemname):

    os = __import__("os")

    # Save dir
    dirname = os.path.join(recalboxFiles.savesDir, systemname)
    if not os.path.exists(dirname):
        os.makedirs(dirname)

    # Load global settings
    recalboot = keyValueSettings("/boot/recalbox-boot.conf", False)
    recalboot.loadFile(True)
    fixedScreenSize = recalboot.getOption("case", "") in ("GPiV1", "GPiV2", "GPiV3")
    del recalboot

    # Load global settings
    settings = keyValueSettings(recalboxFiles.recalboxConf, False)
    settings.loadFile(True)
    
    if rom is not None:
        # build file names
        pathSettings = buildOverrideChain(rom, ".recalbox.conf")
        # Override with path settings
        for pathSetting in pathSettings:
            settings.changeSettingsFile(pathSetting)
            settings.loadFile(False)

    return settings, fixedScreenSize


def main(arguments):
    if not arguments.demo:
        #added by B0ZOTHEGEEK : manage light of arcade panel
        subprocess.call("python /recalbox/scripts/gamelistpower/PanelLights.py -system " + arguments.system + " -rom " + '"' + arguments.rom + '"', shell=True)    
        demoStartButtons = dict()
        # Read the controller configuration
        playersControllers = Controller.loadControllerConfig(arguments.p1index, arguments.p1guid, arguments.p1name, arguments.p1devicepath, arguments.p1nbaxes,
                                                             arguments.p2index, arguments.p2guid, arguments.p2name, arguments.p2devicepath, arguments.p2nbaxes,
                                                             arguments.p3index, arguments.p3guid, arguments.p3name, arguments.p3devicepath, arguments.p3nbaxes,
                                                             arguments.p4index, arguments.p4guid, arguments.p4name, arguments.p4devicepath, arguments.p4nbaxes,
                                                             arguments.p5index, arguments.p5guid, arguments.p5name, arguments.p5devicepath, arguments.p5nbaxes)
    else:
        playersControllers = dict()
        demoStartButtons = Controller.loadDemoConfig(arguments.p1index, arguments.p1guid, arguments.p1name, arguments.p1devicepath, arguments.p1nbaxes,
                                                     arguments.p2index, arguments.p2guid, arguments.p2name, arguments.p2devicepath, arguments.p2nbaxes,
                                                     arguments.p3index, arguments.p3guid, arguments.p3name, arguments.p3devicepath, arguments.p3nbaxes,
                                                     arguments.p4index, arguments.p4guid, arguments.p4name, arguments.p4devicepath, arguments.p4nbaxes,
                                                     arguments.p5index, arguments.p5guid, arguments.p5name, arguments.p5devicepath, arguments.p5nbaxes)

    systemName = arguments.system

    # Main Program
    # A generator will configure its emulator, and return a command
    system = getDefaultEmulator(systemName)
    if system is not None:
        # Load recalbox.conf
        recalboxSettings, fixedScreenSize = loadRecalboxSettings(arguments.rom, system.name)

        # Configure attributes
        system.configure(recalboxSettings, arguments.emulator, arguments.core, arguments.ratio, arguments.netplay, arguments.netplay_ip, arguments.netplay_port, arguments.hash, arguments.extra)

        # Wrong way?
        if system.config['emulator'] not in recalboxFiles.recalboxBins:
            strErr = "ERROR : {} is not a known emulator".format(system.config['emulator'])
            sys = __import__("sys")
            print >> sys.stderr, strErr
            exit(2)

        # Generate all the config files required by the selected emulator
        command = getGenerator(system.config['emulator']).generate(system, arguments.rom, playersControllers, arguments.demo, recalboxSettings)

        # The next line is commented and will eventually be used instead of the previous one
        # if we even want the binary to be set from here rather than from the generator
        # command.array.insert(0, recalboxFiles.recalboxBins[system.config['emulator']])
        print(command)
        returnCode = runner.runCommand(command, arguments, demoStartButtons, recalboxSettings, fixedScreenSize)

        # Rerun emulator in play mode
        if returnCode == runner.USERWANNAPLAY:
            print("User wanna play!")
            arguments.demo = False
            arguments.demoduration = 0
            arguments.demoinfoduration = 0
            main(arguments)
            returnCode = runner.USERQUIT

        return returnCode, not fixedScreenSize
    
    else:
        sys = __import__("sys")
        sys.stderr.write("Unknown system: {}".format(systemName))
        return 1, True


'''
Upgrade all generators user's configuration files with new values added
to their system configuration file upgraded by S11Share:do_upgrade()

Args: 
    version (str): New Recalbox version

Returns (bool):
    Returns True if all generators sucessfully handled the upgraded.
'''
def config_upgrade(version):
    emulatorList = \
    (
     "libretro",
     "mupen64plus",
     "reicast",
     "dolphin",
     "ppsspp",
     "amiberry",
     "daphne",
     "scummvm",
     "dosbox",
     "residualvm",
     "advancemame",
     "simcoupe",
     "gsplus",
     "oricutron",
     "linapple",
     "kodi",
     "fba2x",
     "moonlight",
     "vice",
     "pcsx_rearmed",
     "pisnes"
    )

    res = True
    for emulator in emulatorList:
        res &= getGenerator(emulator).config_upgrade(version)
    return res


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='emulator-launcher script')
    parser.add_argument("-p1index", help="player1 controller index", type=int, required=False)
    parser.add_argument("-p1guid", help="player1 controller SDL2 guid", type=str, required=False)
    parser.add_argument("-p1name", help="player1 controller name", type=str, required=False)
    parser.add_argument("-p1devicepath", help="player1 controller device", type=str, required=False)
    parser.add_argument("-p1nbaxes", help="player1 controller number of axes", type=str, required=False)
    parser.add_argument("-p2index", help="player2 controller index", type=int, required=False)
    parser.add_argument("-p2guid", help="player2 controller SDL2 guid", type=str, required=False)
    parser.add_argument("-p2name", help="player2 controller name", type=str, required=False)
    parser.add_argument("-p2devicepath", help="player2 controller device", type=str, required=False)
    parser.add_argument("-p2nbaxes", help="player2 controller number of axes", type=str, required=False)
    parser.add_argument("-p3index", help="player3 controller index", type=int, required=False)
    parser.add_argument("-p3guid", help="player3 controller SDL2 guid", type=str, required=False)
    parser.add_argument("-p3name", help="player3 controller name", type=str, required=False)
    parser.add_argument("-p3devicepath", help="player3 controller device", type=str, required=False)
    parser.add_argument("-p3nbaxes", help="player3 controller number of axes", type=str, required=False)
    parser.add_argument("-p4index", help="player4 controller index", type=int, required=False)
    parser.add_argument("-p4guid", help="player4 controller SDL2 guid", type=str, required=False)
    parser.add_argument("-p4name", help="player4 controller name", type=str, required=False)
    parser.add_argument("-p4devicepath", help="player4 controller device", type=str, required=False)
    parser.add_argument("-p4nbaxes", help="player4 controller number of axes", type=str, required=False)
    parser.add_argument("-p5index", help="player5 controller index", type=int, required=False)
    parser.add_argument("-p5guid", help="player5 controller SDL2 guid", type=str, required=False)
    parser.add_argument("-p5name", help="player5 controller name", type=str, required=False)
    parser.add_argument("-p5devicepath", help="player5 controller device", type=str, required=False)
    parser.add_argument("-p5nbaxes", help="player5 controller number of axes", type=str, required=False)
    parser.add_argument("-system", help="select the system to launch", type=str, required=True)
    parser.add_argument("-rom", help="rom absolute path", type=str, required=False)
    parser.add_argument("-emulator", help="force emulator", type=str, required=False)
    parser.add_argument("-core", help="force emulator core", type=str, required=False)
    parser.add_argument("-ratio", help="force game ratio", type=str, required=False)
    parser.add_argument("-demo", help="mode demo", type=bool, required=False)
    parser.add_argument("-demoduration", help="mode demo duration in second", type=int, required=False)
    parser.add_argument("-demoinfoduration", help="mode demo outscreen duration in second", type=int, required=False)
    parser.add_argument("-netplay", help="host/client", type=str, required=False)
    parser.add_argument("-netplay_ip", help="host IP", type=str, required=False)
    parser.add_argument("-netplay_port", help="host port (not used in client mode)", type=str, required=False)
    parser.add_argument("-hash", help="force rom crc", type=str, required=False)
    parser.add_argument("-extra", help="pass extra argument", type=str, required=False)

    args = parser.parse_args()
    exitcode, waitNeeded = main(args)
    # Investigate : is this delay still required?
    if waitNeeded:
        __import__("time").sleep(0.5)
    exit(exitcode)

et pour recompiler, il faudra recompiler avec la ligne de commande suivante :

python -m py_compile emulatorlauncher.py

N'oubliez pas aussi qu'avant toutes choses il faudra rendre le file system éditable de la recalbox comme d'hab ;-) :


mount -o remount,rw /

5) Et pour finir, Je ferais une vidéo bientôt sur ma chaîne pour faire une petite démo ;-)  et j'ai même encore des idées d'améliorations ! (je vous en parlerais ... peut être ;-)

Enjoy !