From 274f3d0c7aee85a676151f73e06be049293cb76c Mon Sep 17 00:00:00 2001 From: The412Banner Date: Thu, 24 Sep 2026 09:03:34 -0400 Subject: [PATCH] DuckStation (PS1) set up like ARMSX2 - From the rail: -batch -bigpicture -fullscreen (the same flags as PCSX2's, src/duckstation-qt/qthost.cpp) - straight into the game, gone when it is quit. - bannerlator-pad-defaults: a new player starts on DuckStation's first-time setup with the BIOS folder in the ROMs folder (psx/bios, created, or an existing one), psx as the game list, Vulkan, the controller on Player 1 and the guide button on the pause menu (not the analog toggle). An existing install gets the guide and game-list changes, and once, its own empty BIOS folder moved to the ROMs folder and Automatic made Vulkan; later choices are kept. Users bring their own BIOS and games. --- .../droiddeck/launcher/frontend/Library.kt | 2 + .../usr/local/bin/bannerlator-pad-defaults | 116 +++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/droiddeck/launcher/frontend/Library.kt b/app/src/main/java/com/droiddeck/launcher/frontend/Library.kt index 0337e9d94..f437de9a3 100644 --- a/app/src/main/java/com/droiddeck/launcher/frontend/Library.kt +++ b/app/src/main/java/com/droiddeck/launcher/frontend/Library.kt @@ -191,6 +191,8 @@ object Library { // Straight into the game in its controller-driven full-screen UI (first-time setup there // too), and gone when the game is quit from its pause menu (guide button): -batch. "armsx2" -> listOf("-batch", "-bigpicture", "-fullscreen", "--", guestPath) + // DuckStation shares PCSX2's flags (src/duckstation-qt/qthost.cpp): the same launch and exit. + "duckstation" -> listOf("-batch", "-bigpicture", "-fullscreen", "--", guestPath) "dolphin" -> listOf("-e", guestPath) "cemu" -> listOf("-g", guestPath) else -> listOf(guestPath) diff --git a/tools/linuxfs/overlay/usr/local/bin/bannerlator-pad-defaults b/tools/linuxfs/overlay/usr/local/bin/bannerlator-pad-defaults index 45459a0e2..e9ca2b5d5 100755 --- a/tools/linuxfs/overlay/usr/local/bin/bannerlator-pad-defaults +++ b/tools/linuxfs/overlay/usr/local/bin/bannerlator-pad-defaults @@ -180,9 +180,121 @@ SmallMotor = SDL-0/SmallMotor """ +def ini_get(path, section, key): + try: + with open(path, encoding="utf-8", errors="replace") as f: + lines = f.read().splitlines() + except OSError: + return None + span = ini_section(lines, section) + if not span: + return None + for l in lines[span[0]:span[1]]: + if "=" in l and l.split("=", 1)[0].strip() == key: + return l.split("=", 1)[1].strip() + return None + + +def ini_set(path, section, key, value): + """Sets one key (the first of that name) in [section], adding either as needed.""" + with open(path, encoding="utf-8", errors="replace") as f: + lines = f.read().splitlines() + span = ini_section(lines, section) + if span is None: + lines += ["", f"[{section}]", f"{key} = {value}"] + else: + for i in range(span[0], span[1]): + if "=" in lines[i] and lines[i].split("=", 1)[0].strip() == key: + lines[i] = f"{key} = {value}" + break + else: + end = span[1] + while end > span[0] and not lines[end - 1].strip(): + end -= 1 + lines.insert(end, f"{key} = {value}") + with open(path + ".tmp", "w", encoding="utf-8") as f: + f.write("\n".join(lines) + "\n") + os.replace(path + ".tmp", path) + + +def drop_line(path, line): + with open(path, encoding="utf-8", errors="replace") as f: + lines = f.read().splitlines() + kept = [l for l in lines if l.replace(" ", "") != line.replace(" ", "")] + if len(kept) != len(lines): + with open(path + ".tmp", "w", encoding="utf-8") as f: + f.write("\n".join(kept) + "\n") + os.replace(path + ".tmp", path) + + +def system_folder(names, default): + for name in names: + for cand in (name, name.upper()): + d = os.path.join(ROMS, cand) + if os.path.isdir(d): + return d + return os.path.join(ROMS, default) + + +def bios_folder(base): + for name in ("bios", "BIOS", "firmware"): + d = os.path.join(base, name) + if os.path.isdir(d): + return d + d = os.path.join(base, "bios") + if os.path.isdir(ROMS): + try: + os.makedirs(d, exist_ok=True) # where the user is told to put their BIOS + except OSError: + pass + return d + + +# DuckStation (PS1) - set up like ARMSX2: users bring their own BIOS and games, in the ROMs folder. +# Front-end runs use -batch -bigpicture -fullscreen, so the guide button is the pause menu +# (Hotkeys OpenPauseMenu, src/core/hotkeys.cpp), not the analog toggle. Keys from +# src/core/settings.cpp: [BIOS] SearchDirectory, [GPU] Renderer ("Vulkan"), [Main] +# SetupWizardIncomplete (src/duckstation-qt/qthost.cpp). +DUCKSTATION_PAD1_GUIDE_FREE = "\n".join(l for l in DUCKSTATION_PAD1.splitlines() if not l.startswith("Analog ")) + "\n" + + def duckstation(): - for d in (os.path.join(CONFIG, "duckstation"), os.path.join(HOME, ".local/share/duckstation")): - patch_ini_section(os.path.join(d, "settings.ini"), "Pad1", DUCKSTATION_PAD1, "DuckStation") + ps1 = system_folder(("psx", "ps1"), "psx") + d = os.path.join(CONFIG, "duckstation") + ini = os.path.join(d, "settings.ini") + legacy = os.path.join(HOME, ".local/share/duckstation/settings.ini") + if not os.path.isfile(ini) and os.path.isfile(legacy): + ini = legacy + if not os.path.isfile(ini): + # A new player: DuckStation's own first-time setup still runs, looking in the ROMs folder. + bios = bios_folder(ps1) + seed(ini, "[Main]\nSetupWizardIncomplete = true\n\n" + f"[BIOS]\nSearchDirectory = {bios}\n\n" + f"[GameList]\nRecursivePaths = {ps1}\n\n" + "[GPU]\nRenderer = Vulkan\n\n" + "[Hotkeys]\nOpenPauseMenu = Keyboard/Escape\nOpenPauseMenu = SDL-0/Guide\n\n" + "[Pad1]\n" + DUCKSTATION_PAD1_GUIDE_FREE, "DuckStation", "first-time setup, BIOS from " + bios) + return + patch_ini_section(ini, "Pad1", DUCKSTATION_PAD1_GUIDE_FREE, "DuckStation") + drop_line(ini, "Analog = SDL-0/Guide") + patch_ini_section(ini, "Hotkeys", "OpenPauseMenu = SDL-0/Guide\n", "DuckStation", needle="SDL-0/Guide", + done="the guide button opens the pause menu") + patch_ini_section(ini, "GameList", f"RecursivePaths = {ps1}\n", "DuckStation", needle=ROMS, + done="games listed from the ROMs folder") + # Once, for a DuckStation set up before this: its own empty BIOS folder moves to the ROMs folder, + # and Automatic becomes Vulkan. A later choice in DuckStation's settings is kept. + marker = os.path.join(os.path.dirname(ini), ".droiddeck-setup") + if os.path.exists(marker): + return + current = ini_get(ini, "BIOS", "SearchDirectory") or "bios" + own = current if os.path.isabs(current) else os.path.join(os.path.dirname(ini), current) + if not (os.path.isdir(own) and os.listdir(own)): + ini_set(ini, "BIOS", "SearchDirectory", bios_folder(ps1)) + print(f"bannerlator-pad-defaults: DuckStation: BIOS from {bios_folder(ps1)}") + if (ini_get(ini, "GPU", "Renderer") or "Automatic") == "Automatic": + ini_set(ini, "GPU", "Renderer", "Vulkan") + print("bannerlator-pad-defaults: DuckStation: renderer Vulkan") + open(marker, "w").close() # PCSX2: FaceSouth/... names (pcsx2/Input/SDLInputSource.cpp), keys from SIO/Pad/PadDualshock2.cpp.