From 96c0abcfec33a55d8c5c734b4b4b4f60a07259d1 Mon Sep 17 00:00:00 2001 From: Lx32 Date: Thu, 28 Sep 2023 15:29:33 +0200 Subject: [PATCH 1/3] Update configurator.sh Updated the configurator with the add to steam function --- tools/configurator.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/configurator.sh b/tools/configurator.sh index 175a7174..3038f9b6 100644 --- a/tools/configurator.sh +++ b/tools/configurator.sh @@ -83,6 +83,7 @@ source /app/libexec/global.sh # - Full changelog # - Version-specific changelogs # - RetroDECK Credits +# - Add to Steam # - Developer Options (Hidden) # - Change Multi-user mode # - Change Update channel @@ -99,13 +100,15 @@ configurator_welcome_dialog() { "RetroDECK: Tools" "Compress games, move RetroDECK and install optional features" \ "RetroDECK: Troubleshooting" "Backup data, perform BIOS / multi-disc file checks checks and emulator resets" \ "RetroDECK: About" "Show additional information about RetroDECK" \ + "Add to Steam" "Add to Steam all the favorite games, it will not remove added games" \ "Developer Options" "Welcome to the DANGER ZONE") else welcome_menu_options=("Presets & Settings" "Here you find various presets, tweaks and settings to customize your RetroDECK experience" \ "Open Emulator" "Launch and configure each emulators settings (for advanced users)" \ "RetroDECK: Tools" "Compress games, move RetroDECK and install optional features" \ "RetroDECK: Troubleshooting" "Backup data, perform BIOS / multi-disc file checks checks and emulator resets" \ - "RetroDECK: About" "Show additional information about RetroDECK" ) + "RetroDECK: About" "Show additional information about RetroDECK" \ + "Add to Steam" "Add to Steam all the favorite games, it will not remove added games") fi choice=$(zenity --list --title="RetroDECK Configurator Utility" --cancel-label="Quit" \ @@ -135,6 +138,10 @@ configurator_welcome_dialog() { configurator_about_retrodeck_dialog ;; + "Add to Steam" ) + configurator_add_steam + ;; + "Developer Options" ) configurator_generic_dialog "RetroDECK Configurator - Developer Options" "The following features and options are potentially VERY DANGEROUS for your RetroDECK install!\n\nThey should be considered the bleeding-edge of upcoming RetroDECK features, and never used when you have important saves/states/roms that are not backed up!\n\nYOU HAVE BEEN WARNED!" configurator_developer_dialog @@ -1075,6 +1082,11 @@ configurator_about_retrodeck_dialog() { esac } +configurator_add_steam() { + python3 /app/tools/Lutris/shortcut.py + configurator_welcome_dialog +} + configurator_version_history_dialog() { local version_array=($(xml sel -t -v '//component/releases/release/@version' -n $rd_appdata)) local all_versions_list=() From 317bb5048d47c4f9bff38fd92c4052efda7f9b24 Mon Sep 17 00:00:00 2001 From: Lx32 Date: Thu, 28 Sep 2023 15:31:05 +0200 Subject: [PATCH 2/3] Uploaded files Uploaded files for the managing of vdf file of steam shortcut --- .../__pycache__/shortcut.cpython-310.pyc | Bin 0 -> 2995 bytes tools/Lutris/shortcut.py | 259 ++++++++++ tools/Lutris/vdf/__init__.py | 467 ++++++++++++++++++ .../vdf/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 11545 bytes tools/Lutris/vdf/vdict.py | 219 ++++++++ 5 files changed, 945 insertions(+) create mode 100644 tools/Lutris/__pycache__/shortcut.cpython-310.pyc create mode 100644 tools/Lutris/shortcut.py create mode 100644 tools/Lutris/vdf/__init__.py create mode 100644 tools/Lutris/vdf/__pycache__/__init__.cpython-310.pyc create mode 100644 tools/Lutris/vdf/vdict.py diff --git a/tools/Lutris/__pycache__/shortcut.cpython-310.pyc b/tools/Lutris/__pycache__/shortcut.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b8cfb8d7b74ae059e895b419ac0c47b8ecf1db2 GIT binary patch literal 2995 zcmZuzO>Y~=8Q$41E-6x$WLL3MBV`je2@P13V_K36SBY#2jZs$)EFnRN8pLvEC~dUd zrDukcOi)1q`P?3QbPwXO{TclinQKo5dh0D0^_ihWiBfkk-|u|9@B6&ZY*s22EPVgT z|3vSg^>5v|_&Ml&g)IJpf?5rW5^5);K}>BoY*RZ8$JB1aHMQ69Ozk&(Q|B5vQwNQJ zI*mNNL0#&-C5;00Y3{Ann4$sAqb||{okCrrMOs2VO{eKK)Ys??okcxE=je6Rv((%+!_HUq-WKoz@#ZJgs)vUQPx~Wg;PoJ$imAZ%iUdXFqw_A;}4%*2c z6IomCW2XhPrb3^5T@T1Ld-Wb-3Qc6hHs7x|4Mne35jrNq4sah;F!RT$nPsz?tM3 zWzp_=t9r1q)Lg2;iD4^Y)!O~A_Mp|O(dAmi==}##t<_#`MJp}V3KwbZ(cCsBK{E&w9(c;5eZJDhsGP+c4#|aZvnPrKnJ~wCvCPwgG=)O^f$25*4;M86H zwKjnEzi&+w|D^v(#91g=b1eSYKu4@DoUc*#eycLA=C_#~%ZcV5Z8y82+);KWlxtc( zJBWouFYB^YIgn7rR-A_XO*6?tiU}M>l@Cz&7!x(Fdg&y_j%E6;0* zYnQAtudVixwi+oS-6Xe9>_5BC4Lfjr;-0uCMQ@ILe8PVPkqCMGX$}Ti|6#kPssx63^kkD z8~w4V#R5EEam|*zgq3_6g>rw$;uII1x^Rz6_vA$gE+Ct`!L&%z#Eedavj-6kVsp1A zXn<^pwCdKY56@`K+z%u+Bm-+`8xr#d&Jcw2DtG7(2q?|fiF zRe8O@z}p!1vJJ~PpoT28)m78OneS9d$h90=4gH>dq!@{=%#1HIyCHem~k`u^v^a^BSZ|x;YjzIyiw{y$d23mn*a%@ZI z7^tM~5F^xkg2x%pvA=53+!IX}!3M8lt;)aDZ%cjs=^u>c`(97B@2w>I5dGQK`!cFn zH!I!7B!pvD-0PJ;a7wyPwXW4aqz`XPsR!VOi#|Dz|X}S!GEK>`n?#@ yq`sMnP6HXEvC+jG^M9`uJnm$)m#|fBRB;D|3pSrju3eZ4<^mt75X=T%(fJ=Rz~S=% literal 0 HcmV?d00001 diff --git a/tools/Lutris/shortcut.py b/tools/Lutris/shortcut.py new file mode 100644 index 00000000..350092d9 --- /dev/null +++ b/tools/Lutris/shortcut.py @@ -0,0 +1,259 @@ +"""Export lutris games to steam shortcuts""" +import binascii +import os +import re +import shlex +import shutil +import glob +import vdf +import sys + +import xml.etree.ElementTree as ET + +command_list_default={ +"3do": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/opera_libretro.so", +"amiga": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/puae_libretro.so", +"amiga1200": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/puae_libretro.so", +"amiga600": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/puae_libretro.so", +"amigacd32": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/puae_libretro.so", +"amstradcpc": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/cap32_libretro.so", +"arcade": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mame_libretro.so", +"arduboy": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/arduous_libretro.so", +"astrocde": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mame_libretro.so", +"atari2600": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/stella_libretro.so", +"atari5200": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/a5200_libretro.so", +"atari7800": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/prosystem_libretro.so", +"atari800": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/atari800_libretro.so", +"atarijaguar": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/virtualjaguar_libretro.so", +"atarijaguarcd": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/virtualjaguar_libretro.so", +"atarilynx": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/handy_libretro.so", +"atarist": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/hatari_libretro.so", +"atarixe": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/atari800_libretro.so", +"atomiswave": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/flycast_libretro.so", +"c64": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/vice_x64sc_libretro.so", +"cavestory": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/nxengine_libretro.so", +"cdimono1": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/same_cdi_libretro.so", +"cdtv": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/puae_libretro.so", +"chailove": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/chailove_libretro.so", +"channelf": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/freechaf_libretro.so", +"colecovision": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/bluemsx_libretro.so", +"cps": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mame_libretro.so", +"cps1": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mame_libretro.so", +"cps2": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mame_libretro.so", +"cps3": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mame_libretro.so", +"doom": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/prboom_libretro.so", +"dos": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/dosbox_pure_libretro.so", +"dreamcast": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/flycast_libretro.so", +"easyrpg": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/easyrpg_libretro.so", +"famicom": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mesen_libretro.so", +"fba": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/fbalpha2012_libretro.so", +"fbneo": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/fbneo_libretro.so", +"fds": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mesen_libretro.so", +"gameandwatch": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/gw_libretro.so", +"gamegear": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/genesis_plus_gx_libretro.so", +"gb": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/gambatte_libretro.so", +"gba": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mgba_libretro.so", +"gbc": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/gambatte_libretro.so", +"genesis": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/genesis_plus_gx_libretro.so", +"gx4000": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/cap32_libretro.so", +"intellivision": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/freeintv_libretro.so", +"j2me": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/squirreljme_libretro.so", +"lcdgames": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/gw_libretro.so", +"lutro": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/lutro_libretro.so", +"mame": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mame_libretro.so", +"mastersystem": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/genesis_plus_gx_libretro.so", +"megacd": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/genesis_plus_gx_libretro.so", +"megacdjp": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/genesis_plus_gx_libretro.so", +"megadrive": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/genesis_plus_gx_libretro.so", +"megaduck": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/sameduck_libretro.so", +"mess": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mess2015_libretro.so", +"model2": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mame_libretro.so", +"moto": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/theodore_libretro.so", +"msx": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/bluemsx_libretro.so", +"msx1": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/bluemsx_libretro.so", +"msx2": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/bluemsx_libretro.so", +"msxturbor": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/bluemsx_libretro.so", +"multivision": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/gearsystem_libretro.so", +"n64": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mupen64plus_next_libretro.so", +"n64dd": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/parallel_n64_libretro.so", +"naomi": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/flycast_libretro.so", +"naomigd": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/flycast_libretro.so", +"nds": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/desmume_libretro.so", +"neogeo": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/fbneo_libretro.so", +"neogeocd": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/neocd_libretro.so", +"neogeocdjp": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/neocd_libretro.so", +"nes": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mesen_libretro.so", +"ngp": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_ngp_libretro.so", +"ngpc": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_ngp_libretro.so", +"odyssey2": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/o2em_libretro.so", +"palm": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mu_libretro.so", +"pc88": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/quasi88_libretro.so", +"pc98": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/np2kai_libretro.so", +"pcengine": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_pce_libretro.so", +"pcenginecd": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_pce_libretro.so", +"pcfx": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_pcfx_libretro.so", +"pokemini": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/pokemini_libretro.so", +"psx": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/swanstation_libretro.so", +"quake": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/tyrquake_libretro.so", +"satellaview": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/snes9x_libretro.so", +"saturn": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_saturn_libretro.so", +"saturnjp": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_saturn_libretro.so", +"scummvm": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/scummvm_libretro.so", +"sega32x": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/picodrive_libretro.so", +"sega32xjp": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/picodrive_libretro.so", +"sega32xna": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/picodrive_libretro.so", +"segacd": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/genesis_plus_gx_libretro.so", +"sfc": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/snes9x_libretro.so", +"sg-1000": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/genesis_plus_gx_libretro.so", +"sgb": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mesen-s_libretro.so", +"snes": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/snes9x_libretro.so", +"snesna": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/snes9x_libretro.so", +"spectravideo": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/bluemsx_libretro.so", +"sufami": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/snes9x_libretro.so", +"supergrafx": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_supergrafx_libretro.so", +"supervision": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/potator_libretro.so", +"tg16": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_pce_libretro.so", +"tg-cd": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_pce_libretro.so", +"tic80": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/tic80_libretro.so", +"to8": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/theodore_libretro.so", +"uzebox": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/uzem_libretro.so", +"vectrex": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/vecx_libretro.so", +"vic20": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/vice_xvic_libretro.so", +"videopac": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/o2em_libretro.so", +"virtualboy": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_vb_libretro.so", +"wasm4": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/wasm4_libretro.so", +"wonderswan": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_wswan_libretro.so", +"wonderswancolor": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/mednafen_wswan_libretro.so", +"x1": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/x1_libretro.so", +"x68000": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/px68k_libretro.so", +"zx81": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/81_libretro.so", +"zxspectrum": "flatpak run --command=retroarch net.retrodeck.retrodeck -L /var/config/retroarch/cores/fuse_libretro.so", +"switch": "flatpak run --command=yuzu net.retrodeck.retrodeck -f -g", +"n3ds": "flatpak run --command=citra net.retrodeck.retrodeck", +"ps2": "flatpak run --command=pcsx2-qt net.retrodeck.retrodeck -batch", +"wiiu": "flatpak run --command=Cemu-wrapper net.retrodeck.retrodeck -g", +"gc": "flatpak run --command=dolphin-emu-wrapper net.retrodeck.retrodeck -b -e", +"wii": "flatpak run --command=dolphin-emu-wrapper net.retrodeck.retrodeck -b -e", +"xbox": "flatpak run --command=xemu net.retrodeck.retrodeck -dvd_path", +"ps3": "flatpak run --command=pcsx3 net.retrodeck.retrodeck --no-gui", +"psp": "flatpak run --command=PPSSPPSDL net.retrodeck.retrodeck" +} + +STEAM_DATA_DIRS = ( + "~/.steam/debian-installation", + "~/.steam", + "~/.local/share/steam", + "~/.local/share/Steam", + "~/.steam/steam", + "~/.var/app/com.valvesoftware.Steam/data/steam", + "/usr/share/steam", + "/usr/local/share/steam", +) + +def create_shortcut(games, launch_config_name=None): + shortcut_path = get_shortcuts_vdf_path() + if os.path.exists(shortcut_path): + with open(shortcut_path, "rb") as shortcut_file: + shortcuts = vdf.binary_loads(shortcut_file.read())['shortcuts'].values() + else: + shortcuts = [] + + new_shortcuts=[] + for game in games: + new_shortcuts=new_shortcuts+ [generate_shortcut(game, launch_config_name)] + + shortcuts = list(shortcuts) + new_shortcuts + + updated_shortcuts = { + 'shortcuts': { + str(index): elem for index, elem in enumerate(shortcuts) + } + } + with open(shortcut_path, "wb") as shortcut_file: + shortcut_file.write(vdf.binary_dumps(updated_shortcuts)) + +def get_config_path(): + config_paths = search_recursive_in_steam_dirs("userdata/**/config/") + if not config_paths: + return None + return config_paths[0] + +def get_shortcuts_vdf_path(): + config_path = get_config_path() + if not config_path: + return None + return os.path.join(config_path, "shortcuts.vdf") + +def search_recursive_in_steam_dirs(path_suffix): + """Perform a recursive search based on glob and returns a + list of hits""" + results = [] + for candidate in STEAM_DATA_DIRS: + glob_path = os.path.join(os.path.expanduser(candidate), path_suffix) + for path in glob.glob(glob_path): + results.append(path) + return results + +def generate_shortcut(game, launch_config_name): + return { + 'appid': generate_shortcut_id(game), + 'AppName': f'{game[0]}', + 'Exe': f'{game[1]}', + 'StartDir': f'{os.path.expanduser("~")}', + 'icon': "", + 'LaunchOptions': "", + 'IsHidden': 0, + 'AllowDesktopConfig': 1, + 'AllowOverlay': 1, + 'OpenVR': 0, + 'Devkit': 0, + 'DevkitOverrideAppID': 0, + 'LastPlayTime': 0, + } + +def generate_preliminary_id(game): + unique_id = ''.join(["RetroDECK", game[0]]) + top = binascii.crc32(str.encode(unique_id, 'utf-8')) | 0x80000000 + return (top << 32) | 0x02000000 + +def generate_shortcut_id(game): + return (generate_preliminary_id(game) >> 32) - 0x100000000 + +def addToSteam(): + fl=open(os.path.expanduser("~/.var/app/net.retrodeck.retrodeck/config/retrodeck/retrodeck.cfg"),"r") + lines=fl.readlines() + for line in lines: + if "rdhome" in line: + rdhome=line[7:-1] + elif "roms_folder" in line: + roms_folder=line[12:-1] + fl.close() + games=[] + + for system in os.listdir(rdhome+"/gamelists/"): + tree=ET.parse(rdhome+"/gamelists/"+system+"/gamelist.xml") + root=tree.getroot() + + for game in root: + path="" + name="" + favorite="" + altemulator="" + for tag in game: + if tag.tag=="path": + path=tag.text + elif tag.tag=="name": + name=tag.text + elif tag.tag=="favorite": + favorite=tag.text + elif tag.tag=="altemulator": + altemulator=tag.text + + if favorite=="true" and altemulator="": + games.append((name,command_list_default[system]+" "+roms_folder+"/"+system+path[1:])) + + create_shortcut(games) + +if __name__=="__main__": + addToSteam() + #create_shortcut([sys.argv[1],sys.argv[2]]) diff --git a/tools/Lutris/vdf/__init__.py b/tools/Lutris/vdf/__init__.py new file mode 100644 index 00000000..6e7f136b --- /dev/null +++ b/tools/Lutris/vdf/__init__.py @@ -0,0 +1,467 @@ +""" +Module for deserializing/serializing to and from VDF + +https://github.com/ValvePython/vdf + +MIT License +""" +# pylint: disable=raise-missing-from + +__version__ = "3.2" +__author__ = "Rossen Georgiev" + +import re +import struct +from binascii import crc32 +from io import StringIO as unicodeIO + +string_type = str +int_type = int +BOMS = '\ufffe\ufeff' + + +def strip_bom(line): + return line.lstrip(BOMS) + + +# string escaping +_unescape_char_map = { + r"\n": "\n", + r"\t": "\t", + r"\v": "\v", + r"\b": "\b", + r"\r": "\r", + r"\f": "\f", + r"\a": "\a", + r"\\": "\\", + r"\?": "?", + r"\"": "\"", + r"\'": "\'", +} +_escape_char_map = {v: k for k, v in _unescape_char_map.items()} + + +def _re_escape_match(m): + return _escape_char_map[m.group()] + + +def _re_unescape_match(m): + return _unescape_char_map[m.group()] + + +def _escape(text): + return re.sub(r"[\n\t\v\b\r\f\a\\\?\"']", _re_escape_match, text) + + +def _unescape(text): + return re.sub(r"(\\n|\\t|\\v|\\b|\\r|\\f|\\a|\\\\|\\\?|\\\"|\\')", _re_unescape_match, text) + +# parsing and dumping for KV1 + + +def parse(fp, mapper=dict, merge_duplicate_keys=True, escaped=True): + """ + Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a VDF) + to a Python object. + + ``mapper`` specifies the Python object used after deserializetion. ``dict` is + used by default. Alternatively, ``collections.OrderedDict`` can be used if you + wish to preserve key order. Or any object that acts like a ``dict``. + + ``merge_duplicate_keys`` when ``True`` will merge multiple KeyValue lists with the + same key into one instead of overwriting. You can se this to ``False`` if you are + using ``VDFDict`` and need to preserve the duplicates. + """ + if not issubclass(mapper, dict): + raise TypeError("Expected mapper to be subclass of dict, got %s" % type(mapper)) + if not hasattr(fp, 'readline'): + raise TypeError("Expected fp to be a file-like object supporting line iteration") + + lineno = 0 + stack = [mapper()] + expect_bracket = False + + re_keyvalue = re.compile(r'^("(?P(?:\\.|[^\\"])+)"|(?P#?[a-z0-9\-\_\\\?]+))' + r'([ \t]*(' + r'"(?P(?:\\.|[^\\"])*)(?P")?' + r'|(?P[a-z0-9\-\_\\\?\*\.]+)' + r'))?', + flags=re.I) + + for lineno, line in enumerate(fp, 1): + if lineno == 1: + line = strip_bom(line) + + line = line.lstrip() + + # skip empty and comment lines + if line == "" or line[0] == '/': + continue + + # one level deeper + if line[0] == "{": + expect_bracket = False + continue + + if expect_bracket: + raise SyntaxError("vdf.parse: expected openning bracket", + (getattr(fp, 'name', '<%s>' % fp.__class__.__name__), lineno, 1, line)) + + # one level back + if line[0] == "}": + if len(stack) > 1: + stack.pop() + continue + + raise SyntaxError("vdf.parse: one too many closing parenthasis", + (getattr(fp, 'name', '<%s>' % fp.__class__.__name__), lineno, 0, line)) + + # parse keyvalue pairs + while True: + match = re_keyvalue.match(line) + + if not match: + try: + line += next(fp) + continue + except StopIteration: + raise SyntaxError("vdf.parse: unexpected EOF (open key quote?)", + (getattr(fp, 'name', '<%s>' % fp.__class__.__name__), lineno, 0, line)) + + key = match.group('key') if match.group('qkey') is None else match.group('qkey') + val = match.group('val') if match.group('qval') is None else match.group('qval') + + if escaped: + key = _unescape(key) + + # we have a key with value in parenthesis, so we make a new dict obj (level deeper) + if val is None: + if merge_duplicate_keys and key in stack[-1]: + _m = stack[-1][key] + else: + _m = mapper() + stack[-1][key] = _m + + stack.append(_m) + expect_bracket = True + + # we've matched a simple keyvalue pair, map it to the last dict obj in the stack + else: + # if the value is line consume one more line and try to match again, + # until we get the KeyValue pair + if match.group('vq_end') is None and match.group('qval') is not None: + try: + line += next(fp) + continue + except StopIteration: + raise SyntaxError("vdf.parse: unexpected EOF (open quote for value?)", + (getattr(fp, 'name', '<%s>' % fp.__class__.__name__), lineno, 0, line)) + + stack[-1][key] = _unescape(val) if escaped else val + + # exit the loop + break + + if len(stack) != 1: + raise SyntaxError("vdf.parse: unclosed parenthasis or quotes (EOF)", + (getattr(fp, 'name', '<%s>' % fp.__class__.__name__), lineno, 0, line)) + + return stack.pop() + + +def loads(s, **kwargs): + """ + Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON + document) to a Python object. + """ + if not isinstance(s, string_type): + raise TypeError("Expected s to be a str, got %s" % type(s)) + fp = unicodeIO(s) + return parse(fp, **kwargs) + + +def load(fp, **kwargs): + """ + Deserialize ``fp`` (a ``.readline()``-supporting file-like object containing + a JSON document) to a Python object. + """ + return parse(fp, **kwargs) + + +def dumps(obj, pretty=False, escaped=True): + """ + Serialize ``obj`` to a VDF formatted ``str``. + """ + if not isinstance(obj, dict): + raise TypeError("Expected data to be an instance of``dict``") + if not isinstance(pretty, bool): + raise TypeError("Expected pretty to be of type bool") + if not isinstance(escaped, bool): + raise TypeError("Expected escaped to be of type bool") + + return ''.join(_dump_gen(obj, pretty, escaped)) + + +def dump(obj, fp, pretty=False, escaped=True): + """ + Serialize ``obj`` as a VDF formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + """ + if not isinstance(obj, dict): + raise TypeError("Expected data to be an instance of``dict``") + if not hasattr(fp, 'write'): + raise TypeError("Expected fp to have write() method") + if not isinstance(pretty, bool): + raise TypeError("Expected pretty to be of type bool") + if not isinstance(escaped, bool): + raise TypeError("Expected escaped to be of type bool") + + for chunk in _dump_gen(obj, pretty, escaped): + fp.write(chunk) + + +def _dump_gen(data, pretty=False, escaped=True, level=0): + indent = "\t" + line_indent = "" + + if pretty: + line_indent = indent * level + + for key, value in data.items(): + if escaped and isinstance(key, string_type): + key = _escape(key) + + if isinstance(value, dict): + yield '%s"%s"\n%s{\n' % (line_indent, key, line_indent) + for chunk in _dump_gen(value, pretty, escaped, level + 1): + yield chunk + yield "%s}\n" % line_indent + else: + if escaped and isinstance(value, string_type): + value = _escape(value) + + yield '%s"%s" "%s"\n' % (line_indent, key, value) + + +# binary VDF +class BASE_INT(int_type): + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, self) + + +class UINT_64(BASE_INT): + pass + + +class INT_64(BASE_INT): + pass + + +class POINTER(BASE_INT): + pass + + +class COLOR(BASE_INT): + pass + + +BIN_NONE = b'\x00' +BIN_STRING = b'\x01' +BIN_INT32 = b'\x02' +BIN_FLOAT32 = b'\x03' +BIN_POINTER = b'\x04' +BIN_WIDESTRING = b'\x05' +BIN_COLOR = b'\x06' +BIN_UINT64 = b'\x07' +BIN_END = b'\x08' +BIN_INT64 = b'\x0A' +BIN_END_ALT = b'\x0B' + + +def binary_loads(s, mapper=dict, merge_duplicate_keys=True, alt_format=False): + """ + Deserialize ``s`` (``bytes`` containing a VDF in "binary form") + to a Python object. + + ``mapper`` specifies the Python object used after deserializetion. ``dict` is + used by default. Alternatively, ``collections.OrderedDict`` can be used if you + wish to preserve key order. Or any object that acts like a ``dict``. + + ``merge_duplicate_keys`` when ``True`` will merge multiple KeyValue lists with the + same key into one instead of overwriting. You can se this to ``False`` if you are + using ``VDFDict`` and need to preserve the duplicates. + """ + if not isinstance(s, bytes): + raise TypeError("Expected s to be bytes, got %s" % type(s)) + if not issubclass(mapper, dict): + raise TypeError("Expected mapper to be subclass of dict, got %s" % type(mapper)) + + # helpers + int32 = struct.Struct(' idx: + t = s[idx:idx + 1] + idx += 1 + + if t == CURRENT_BIN_END: + if len(stack) > 1: + stack.pop() + continue + break + + key, idx = read_string(s, idx) + + if t == BIN_NONE: + if merge_duplicate_keys and key in stack[-1]: + _m = stack[-1][key] + else: + _m = mapper() + stack[-1][key] = _m + stack.append(_m) + elif t == BIN_STRING: + stack[-1][key], idx = read_string(s, idx) + elif t == BIN_WIDESTRING: + stack[-1][key], idx = read_string(s, idx, wide=True) + elif t in (BIN_INT32, BIN_POINTER, BIN_COLOR): + val = int32.unpack_from(s, idx)[0] + + if t == BIN_POINTER: + val = POINTER(val) + elif t == BIN_COLOR: + val = COLOR(val) + + stack[-1][key] = val + idx += int32.size + elif t == BIN_UINT64: + stack[-1][key] = UINT_64(uint64.unpack_from(s, idx)[0]) + idx += uint64.size + elif t == BIN_INT64: + stack[-1][key] = INT_64(int64.unpack_from(s, idx)[0]) + idx += int64.size + elif t == BIN_FLOAT32: + stack[-1][key] = float32.unpack_from(s, idx)[0] + idx += float32.size + else: + raise SyntaxError("Unknown data type at offset %d: %s" % (idx - 1, repr(t))) + + if len(s) != idx or len(stack) != 1: + raise SyntaxError("Binary VDF ended at offset %d, but length is %d" % (idx, len(s))) + + return stack.pop() + + +def binary_dumps(obj, alt_format=False): + """ + Serialize ``obj`` to a binary VDF formatted ``bytes``. + """ + return b''.join(_binary_dump_gen(obj, alt_format=alt_format)) + + +def _binary_dump_gen(obj, level=0, alt_format=False): + if level == 0 and len(obj) == 0: + return + + int32 = struct.Struct('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 agPB-Hr+@fS7<|vajAlA!y!%vo;J*PlEK|Aw literal 0 HcmV?d00001 diff --git a/tools/Lutris/vdf/vdict.py b/tools/Lutris/vdf/vdict.py new file mode 100644 index 00000000..e4228327 --- /dev/null +++ b/tools/Lutris/vdf/vdict.py @@ -0,0 +1,219 @@ +# pylint: disable=no-member,unnecessary-dunder-call +from collections import Counter + +_iter_values = 'values' +_range = range +_string_type = str +import collections as _c + + +class _kView(_c.KeysView): + def __iter__(self): + return self._mapping.iterkeys() + + +class _vView(_c.ValuesView): + def __iter__(self): + return self._mapping.itervalues() + + +class _iView(_c.ItemsView): + def __iter__(self): + return self._mapping.iteritems() + + +class VDFDict(dict): + def __init__(self, data=None): + """ + This is a dictionary that supports duplicate keys and preserves insert order + + ``data`` can be a ``dict``, or a sequence of key-value tuples. (e.g. ``[('key', 'value'),..]``) + The only supported type for key is str. + + Get/set duplicates is done by tuples ``(index, key)``, where index is the duplicate index + for the specified key. (e.g. ``(0, 'key')``, ``(1, 'key')``...) + + When the ``key`` is ``str``, instead of tuple, set will create a duplicate and get will look up ``(0, key)`` + """ + super().__init__() + self.__omap = [] + self.__kcount = Counter() + + if data is not None: + if not isinstance(data, (list, dict)): + raise ValueError("Expected data to be list of pairs or dict, got %s" % type(data)) + self.update(data) + + def __repr__(self): + out = "%s(" % self.__class__.__name__ + out += "%s)" % repr(list(self.iteritems())) + return out + + def __len__(self): + return len(self.__omap) + + def _verify_key_tuple(self, key): + if len(key) != 2: + raise ValueError("Expected key tuple length to be 2, got %d" % len(key)) + if not isinstance(key[0], int): + raise TypeError("Key index should be an int") + if not isinstance(key[1], _string_type): + raise TypeError("Key value should be a str") + + def _normalize_key(self, key): + if isinstance(key, _string_type): + key = (0, key) + elif isinstance(key, tuple): + self._verify_key_tuple(key) + else: + raise TypeError("Expected key to be a str or tuple, got %s" % type(key)) + return key + + def __setitem__(self, key, value): + if isinstance(key, _string_type): + key = (self.__kcount[key], key) + self.__omap.append(key) + elif isinstance(key, tuple): + self._verify_key_tuple(key) + if key not in self: + raise KeyError("%s doesn't exist" % repr(key)) + else: + raise TypeError("Expected either a str or tuple for key") + super().__setitem__(key, value) + self.__kcount[key[1]] += 1 + + def __getitem__(self, key): + return super().__getitem__(self._normalize_key(key)) + + def __delitem__(self, key): + key = self._normalize_key(key) + result = super().__delitem__(key) + + start_idx = self.__omap.index(key) + del self.__omap[start_idx] + + dup_idx, skey = key + self.__kcount[skey] -= 1 + tail_count = self.__kcount[skey] - dup_idx + + if tail_count > 0: + for idx in _range(start_idx, len(self.__omap)): + if self.__omap[idx][1] == skey: + oldkey = self.__omap[idx] + newkey = (dup_idx, skey) + super().__setitem__(newkey, self[oldkey]) + super().__delitem__(oldkey) + self.__omap[idx] = newkey + + dup_idx += 1 + tail_count -= 1 + if tail_count == 0: + break + + if self.__kcount[skey] == 0: + del self.__kcount[skey] + + return result + + def __iter__(self): + return iter(self.iterkeys()) + + def __contains__(self, key): + return super().__contains__(self._normalize_key(key)) + + def __eq__(self, other): + if isinstance(other, VDFDict): + return list(self.items()) == list(other.items()) + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def clear(self): + super().clear() + self.__kcount.clear() + self.__omap = [] + + def get(self, key, *args): + return super().get(self._normalize_key(key), *args) + + def setdefault(self, key, default=None): + if key not in self: + self.__setitem__(key, default) + return self.__getitem__(key) + + def pop(self, key): + key = self._normalize_key(key) + value = self.__getitem__(key) + self.__delitem__(key) + return value + + def popitem(self): + if not self.__omap: + raise KeyError("VDFDict is empty") + key = self.__omap[-1] + return key[1], self.pop(key) + + def update(self, data=None, **kwargs): + if isinstance(data, dict): + data = data.items() + elif not isinstance(data, list): + raise TypeError("Expected data to be a list or dict, got %s" % type(data)) + + for key, value in data: + self.__setitem__(key, value) + + def iterkeys(self): + return (key[1] for key in self.__omap) + + def keys(self): + return _kView(self) + + def itervalues(self): + return (self[key] for key in self.__omap) + + def values(self): + return _vView(self) + + def iteritems(self): + return ((key[1], self[key]) for key in self.__omap) + + def items(self): + return _iView(self) + + def get_all_for(self, key): + """ Returns all values of the given key """ + if not isinstance(key, _string_type): + raise TypeError("Key needs to be a string.") + return [self[(idx, key)] for idx in _range(self.__kcount[key])] + + def remove_all_for(self, key): + """ Removes all items with the given key """ + if not isinstance(key, _string_type): + raise TypeError("Key need to be a string.") + + for idx in _range(self.__kcount[key]): + super().__delitem__((idx, key)) + + self.__omap = list(filter(lambda x: x[1] != key, self.__omap)) + + del self.__kcount[key] + + def has_duplicates(self): + """ + Returns ``True`` if the dict contains keys with duplicates. + Recurses through any all keys with value that is ``VDFDict``. + """ + for n in getattr(self.__kcount, _iter_values)(): + if n != 1: + return True + + def dict_recurse(obj): + for v in getattr(obj, _iter_values)(): + if isinstance(v, VDFDict) and v.has_duplicates(): + return True + if isinstance(v, dict): + return dict_recurse(v) + return False + + return dict_recurse(self) From d8b98f18f8f2075f8d9b0e0858ba768089a87fa3 Mon Sep 17 00:00:00 2001 From: Lx32 Date: Thu, 28 Sep 2023 15:38:56 +0200 Subject: [PATCH 3/3] Fixed comment --- tools/Lutris/shortcut.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Lutris/shortcut.py b/tools/Lutris/shortcut.py index 350092d9..278afa37 100644 --- a/tools/Lutris/shortcut.py +++ b/tools/Lutris/shortcut.py @@ -1,4 +1,4 @@ -"""Export lutris games to steam shortcuts""" +"""Export RetroDECK favorites games to steam shortcuts""" import binascii import os import re