Skip to content

Pre-defined Steam launch options: picker menu — #630 port staging - #6

Closed
Leb-Sun wants to merge 2 commits into
mainfrom
steam-launch-options-630-port
Closed

Pre-defined Steam launch options: picker menu — #630 port staging#6
Leb-Sun wants to merge 2 commits into
mainfrom
steam-launch-options-630-port

Conversation

@Leb-Sun

@Leb-Sun Leb-Sun commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Staging PR on the fork — this is the preview of what upstream PR WinNative-Emu#600 will be replaced with: the feature rebased onto current upstream main and squashed to two clean commits, for visual review + Actions build. Everything below is the intended WinNative-Emu#600 body.

Games with multiple Steam launch entries (Play DX11 / DX12, safe mode, mod
launcher…) always booted the default one. This adds a "Launch Options" item to
the STEAM dropdown on both game screens — only shown when the game actually has
2+ entries — opening a Workshop-style picker (WsBg scheme, pane-nav wired).
Tapping a row saves it as the game's default.

The selection is explicit per-shortcut state (launch_option_exe/
launch_option_args extras, stored verbatim): untouched shortcuts show the
default entry as active, a manual exe change in shortcut settings turns options
off entirely (a custom exe is never adopted as a launch option), and a read-only
hint under Exec args shows the args the active option appends (new string in all
locales). LaunchInfo gains arguments and launchId fields parsed from appinfo
config.launch; stale cached rows heal via a one-shot PICS re-fetch when the
picker opens.

At launch the selection routes through Steam itself: the Android side resolves
the picked entry's config.launch key (list position as fallback for pre-launchId
caches) and hands it to the in-Wine launcher, which drives steamclient's own
IClientAppManager::LaunchApp with that index — Steam registers and starts the
picked entry, and the session survives relaunches (forcing the exe directly lost
the Steam session on the second launch). Direct-exe mode remains only for a
user-overridden exe that isn't in appinfo at all, and the launcher still falls
back to CreateProcess if LaunchApp can't spawn the game.

Custom launch options from WinNative-Emu#520's %command% pipeline still compose on top.
LaunchApp alone launches the entry's stock command line, so with custom args
present the launcher uses LaunchApp only to register the game, reaps that spawn,
and relaunches the exe via CreateProcess with the full combined argv (entry args

  • user args; CommandLineToArgvW-correct quoting, whole-arg drop with a logged
    warning instead of mid-token truncation, no raw command lines in logs since
    execArgs can carry credentials). The Rust client's setLaunchCommandLine gets
    the custom-only args so the entry's own args aren't applied twice, the steam-env
    stamp now includes the effective line so switching options re-runs that setup,
    and warm launches re-push the command line and family-shared flag (both
    per-process native state).

The entry's own args are resolved from appinfo at launch time (falling back to
the picker's recorded selection, then the persisted override, if appinfo can't
name the entry), so every launch path ships the same command line. Steam applies
an entry's args only on the spawn it makes itself; the moment we take over —
reaping for custom args, or falling back — the line has to be rebuilt from our
own state. Resolving it from appinfo means a default-entry pick (which stores no
override) and an untouched shortcut both still carry the entry's args, e.g.
Subnautica's default -silent-crashes -vrmode none. Which path handles a launch
is invisible in the game's arguments.

localconfig's LaunchOptions is only ever cleared, never written with a value:
in-Wine Steam runs a non-empty value through ShellExecute, which pops a modal
"File not found." box that wedges steamclient's launch thread. It was never a
delivery path anyway — args reach the game via argv — so this channel does
hygiene only, and skips rewriting the file when nothing would change.

Review + testing hardening: the direct-exe override compares the full relative
path (entries differing only by directory were silently ignored) and
re-selecting Steam's default entry clears the args override so launches return
to LaunchApp. One shared exe-path normalizer is used on both the persist and
launch sides. The picker re-reads state on every open, both game screens share
one state holder, and saving a selection no longer loads every shortcut's icon.
The on-disk option filter matches case-insensitively, like the launch path
resolves those same paths. The picker and the game-screen launch resolve the
game's shortcut through one shared resolver over the loaded container list, so
a pick can't land in an orphaned or corrupt container dir the launch never
reads (and launching stops icon-decoding every shortcut just to find one).

Verified on device across: Halo MCC (non-default -no-eac entry) with custom
args added, changed, and removed, and after re-creating the game's container and
opening it in multiple containers; Subnautica (default entry and a no-arg picked
entry) with and without custom args, including full app close/reopen. Every case
delivered the resolved args exactly once with no doubling and the picked shortcut
resolving to the container that launched.

Limitations / needed follow-up (each planned as its own small PR):

  • Cold appinfo (the main wart): when Steam's appinfo for the game hasn't
    cached yet, LaunchApp returns MissingConfig on every attempt, so the launcher
    spends ~20s retrying and then starts the game via CreateProcess. The resolved
    args are still delivered, but the game has to self-register through
    SteamAPI_Init against a session that may not be ready — usually it succeeds, but
    a strict title (Halo MCC observed) will quit itself if it comes up unregistered.
    It self-corrects once appinfo is cached (once per install, online). Independent
    of the launch method and of the args. Follow-up: pre-warm appinfo before launch,
    or (see below) launch the entry directly and skip the LaunchApp round-trip.
  • Same game in two containers: the game screens act on the canonical
    shortcut (first loaded container's); a home-screen shortcut pinned from
    another container still launches with that shortcut's own per-shortcut
    settings. A container-aware picker is a possible follow-up if per-container
    selections are ever needed.
  • Pre-launchId appinfo caches fall back to the entry's list position as the
    LaunchApp index; a game with keyed/non-sequential config.launch entries could
    start the wrong entry until the cache heals (the picker's PICS re-fetch, plus
    the CreateProcess fallback, bound the damage).
  • Picker heal robustness: the PICS re-fetch can report success while the
    service is tearing down (persist no-ops), needing a picker reopen to show
    fresh entries.
  • Duplicate launch entries: the selection is matched by executable plus
    arguments, so a game listing two entries that agree on both (common for
    no-argument entries) resolves to the first of them. They launch identically,
    but a per-entry detail such as a beta key would come from the wrong one.
Screenshot_WinNative_20260702_223143 Screenshot_WinNative_20260702_223136 Screenshot_WinNative_20260705_210837

@Leb-Sun
Leb-Sun force-pushed the steam-launch-options-630-port branch from 3058f44 to 5bd6dd4 Compare July 21, 2026 01:58
@Leb-Sun Leb-Sun closed this Jul 22, 2026
@Leb-Sun
Leb-Sun deleted the steam-launch-options-630-port branch July 22, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant