feat: disable keyring in Steam environments and allow prelaunch to continue if OTP is missing
This commit is contained in:
parent
b1a25e20ee
commit
5ea9f38a12
1 changed files with 49 additions and 21 deletions
70
wrapper.py
70
wrapper.py
|
|
@ -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,16 +401,28 @@ 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."
|
||||||
error_log = os.path.join(config_dir, 'error.log')
|
print(error_msg)
|
||||||
try:
|
|
||||||
with open(error_log, 'a') as f:
|
# Log the error so user can see it if they check logs
|
||||||
f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Error: OTP secret not configured.\n")
|
error_log = os.path.join(config_dir, 'error.log')
|
||||||
f.write("Please run 'xivlauncher-wrapper' manually from your terminal or application menu to configure your secret before launching via Steam.\n\n")
|
try:
|
||||||
except Exception:
|
with open(error_log, 'a') as f:
|
||||||
pass
|
f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {error_msg}\n")
|
||||||
print("Error: No secret available.")
|
except Exception:
|
||||||
sys.exit(1)
|
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:
|
if args.inject_only:
|
||||||
# Only run the OTP injection loop and exit
|
# Only run the OTP injection loop and exit
|
||||||
|
|
@ -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
|
||||||
injector_thread = threading.Thread(target=send_otp, args=(secret,))
|
if secret:
|
||||||
injector_thread.daemon = True
|
injector_thread = threading.Thread(target=send_otp, args=(secret,))
|
||||||
injector_thread.start()
|
injector_thread.daemon = True
|
||||||
|
injector_thread.start()
|
||||||
|
else:
|
||||||
|
print("OTP injection skipped.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Wait for the launcher to exit
|
# Wait for the launcher to exit
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue