feat: disable keyring in Steam environments and allow prelaunch to continue if OTP is missing

This commit is contained in:
CPTN Cosmo 2026-04-18 15:54:36 +02:00
parent b1a25e20ee
commit 5ea9f38a12

View file

@ -237,18 +237,31 @@ def detect_launch_command():
return commands 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): def get_secret(config, config_path, allow_prompt=True):
""" """
Retrieves the OTP secret in the following priority: Retrieves the OTP secret in the following priority:
1. System Keyring (SERVICE_NAME, USERNAME) - if available 1. System Keyring (SERVICE_NAME, USERNAME) - if available and not in Steam environment
2. config.json - if Keyring unavailable or secret found there (and Keyring unavailable for migration) 2. config.json - if Keyring unavailable/disabled or secret found there
3. User Input - prompts user and saves to Keyring (if available) or config.json. 3. User Input - prompts user and saves to Keyring/config (if allowed and not in Steam environment)
""" """
SERVICE_NAME = "XIVLauncherWrapper" SERVICE_NAME = "XIVLauncherWrapper"
USERNAME = "OTP_SECRET" USERNAME = "OTP_SECRET"
steam_env = is_steam_env()
use_keyring = HAS_KEYRING and not steam_env
# 1. Try Keyring # 1. Try Keyring
if HAS_KEYRING: if use_keyring:
try: try:
secret = keyring.get_password(SERVICE_NAME, USERNAME) secret = keyring.get_password(SERVICE_NAME, USERNAME)
if secret: if secret:
@ -260,7 +273,7 @@ def get_secret(config, config_path, allow_prompt=True):
if config and "secret" in config: if config and "secret" in config:
secret = config["secret"] secret = config["secret"]
if secret and secret != "YOUR_BASE32_SECRET_HERE": if secret and secret != "YOUR_BASE32_SECRET_HERE":
if HAS_KEYRING: if use_keyring:
print("Migrating secret from config.json to system keyring...") print("Migrating secret from config.json to system keyring...")
try: try:
keyring.set_password(SERVICE_NAME, USERNAME, secret) 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 # No keyring, just use config
return secret return secret
if not allow_prompt: if not allow_prompt or steam_env:
return None return None
# 3. Prompt User # 3. Prompt User
if HAS_KEYRING: if use_keyring:
print("\nOTP Secret not found in keyring.") print("\nOTP Secret not found in keyring.")
else: else:
print("\nOTP Secret not found in config.json and keyring module is missing.") 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() secret_input = getpass.getpass("Secret: ").strip()
parsed = parse_secret(secret_input) parsed = parse_secret(secret_input)
if HAS_KEYRING: if use_keyring:
try: try:
keyring.set_password(SERVICE_NAME, USERNAME, parsed) keyring.set_password(SERVICE_NAME, USERNAME, parsed)
print("Secret saved to system keyring.") print("Secret saved to system keyring.")
@ -388,15 +401,27 @@ def main():
secret = get_secret(config, config_path, allow_prompt=not (args.prelaunch or args.inject_only)) secret = get_secret(config, config_path, allow_prompt=not (args.prelaunch or args.inject_only))
if not secret: if not secret:
if args.prelaunch or args.inject_only: 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') error_log = os.path.join(config_dir, 'error.log')
try: try:
with open(error_log, 'a') as f: 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(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {error_msg}\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: except Exception:
pass pass
print("Error: No secret available.")
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) sys.exit(1)
if args.inject_only: if args.inject_only:
@ -514,9 +539,12 @@ def main():
sys.exit(1) sys.exit(1)
# Start the OTP injector in a background thread # Start the OTP injector in a background thread
if secret:
injector_thread = threading.Thread(target=send_otp, args=(secret,)) injector_thread = threading.Thread(target=send_otp, args=(secret,))
injector_thread.daemon = True injector_thread.daemon = True
injector_thread.start() injector_thread.start()
else:
print("OTP injection skipped.")
try: try:
# Wait for the launcher to exit # Wait for the launcher to exit