From 5ea9f38a12c113e4eaca7f7bbd841e03f235a2b9 Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 18 Apr 2026 15:54:36 +0200 Subject: [PATCH] feat: disable keyring in Steam environments and allow prelaunch to continue if OTP is missing --- wrapper.py | 70 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/wrapper.py b/wrapper.py index ccc2427..74cffed 100755 --- a/wrapper.py +++ b/wrapper.py @@ -237,18 +237,31 @@ def detect_launch_command(): return commands +def is_steam_env(): + """Detects if we are running under Steam, Steam Deck Gaming Mode, or as a Steam Compat Tool.""" + if os.environ.get('XDG_CURRENT_DESKTOP', '').lower() == 'gamescope': + return True + if 'STEAM_COMPAT_DATA_PATH' in os.environ: + return True + if 'STEAM_RUNTIME' in os.environ: + return True + return False + def get_secret(config, config_path, allow_prompt=True): """ Retrieves the OTP secret in the following priority: - 1. System Keyring (SERVICE_NAME, USERNAME) - if available - 2. config.json - if Keyring unavailable or secret found there (and Keyring unavailable for migration) - 3. User Input - prompts user and saves to Keyring (if available) or config.json. + 1. System Keyring (SERVICE_NAME, USERNAME) - if available and not in Steam environment + 2. config.json - if Keyring unavailable/disabled or secret found there + 3. User Input - prompts user and saves to Keyring/config (if allowed and not in Steam environment) """ SERVICE_NAME = "XIVLauncherWrapper" USERNAME = "OTP_SECRET" + steam_env = is_steam_env() + use_keyring = HAS_KEYRING and not steam_env + # 1. Try Keyring - if HAS_KEYRING: + if use_keyring: try: secret = keyring.get_password(SERVICE_NAME, USERNAME) if secret: @@ -260,7 +273,7 @@ def get_secret(config, config_path, allow_prompt=True): if config and "secret" in config: secret = config["secret"] if secret and secret != "YOUR_BASE32_SECRET_HERE": - if HAS_KEYRING: + if use_keyring: print("Migrating secret from config.json to system keyring...") try: keyring.set_password(SERVICE_NAME, USERNAME, secret) @@ -279,11 +292,11 @@ def get_secret(config, config_path, allow_prompt=True): # No keyring, just use config return secret - if not allow_prompt: + if not allow_prompt or steam_env: return None # 3. Prompt User - if HAS_KEYRING: + if use_keyring: print("\nOTP Secret not found in keyring.") else: print("\nOTP Secret not found in config.json and keyring module is missing.") @@ -319,7 +332,7 @@ def get_secret(config, config_path, allow_prompt=True): secret_input = getpass.getpass("Secret: ").strip() parsed = parse_secret(secret_input) - if HAS_KEYRING: + if use_keyring: try: keyring.set_password(SERVICE_NAME, USERNAME, parsed) print("Secret saved to system keyring.") @@ -388,16 +401,28 @@ def main(): secret = get_secret(config, config_path, allow_prompt=not (args.prelaunch or args.inject_only)) if not secret: - if args.prelaunch or args.inject_only: - error_log = os.path.join(config_dir, 'error.log') - try: - with open(error_log, 'a') as f: - f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Error: OTP secret not configured.\n") - f.write("Please run 'xivlauncher-wrapper' manually from your terminal or application menu to configure your secret before launching via Steam.\n\n") - except Exception: - pass - print("Error: No secret available.") - sys.exit(1) + error_msg = "Error: OTP secret not configured. Please run 'xivlauncher-wrapper' manually in Desktop Mode to configure it." + print(error_msg) + + # Log the error so user can see it if they check logs + error_log = os.path.join(config_dir, 'error.log') + try: + with open(error_log, 'a') as f: + f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {error_msg}\n") + except Exception: + pass + + if args.prelaunch: + print("Continuing prelaunch without OTP injection so the game can still start.") + sys.exit(0) + elif args.inject_only: + sys.exit(1) + else: + # If we are acting as a wrapper for %command%, continue without OTP. + if unknown_args: + print("Continuing command launch without OTP injection.") + else: + sys.exit(1) if args.inject_only: # Only run the OTP injection loop and exit @@ -514,9 +539,12 @@ def main(): sys.exit(1) # Start the OTP injector in a background thread - injector_thread = threading.Thread(target=send_otp, args=(secret,)) - injector_thread.daemon = True - injector_thread.start() + if secret: + injector_thread = threading.Thread(target=send_otp, args=(secret,)) + injector_thread.daemon = True + injector_thread.start() + else: + print("OTP injection skipped.") try: # Wait for the launcher to exit