From b1a25e20ee0ef294b071b137568b08068115edd5 Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 18 Apr 2026 15:46:12 +0200 Subject: [PATCH] refactor: replace configparser with manual file processing for launcher.ini and reorder initialization logic to support early secret configuration and background injection --- wrapper.py | 145 +++++++++++++++++++++++++++++------------------------ 1 file changed, 80 insertions(+), 65 deletions(-) diff --git a/wrapper.py b/wrapper.py index b1e8f48..ccc2427 100755 --- a/wrapper.py +++ b/wrapper.py @@ -156,45 +156,58 @@ def check_and_update_launcher_ini(): pass return - config = configparser.ConfigParser() - config.optionxform = str # Preserve case sensitivity - try: - config.read(ini_path) - except configparser.Error as e: + with open(ini_path, 'r') as f: + lines = f.readlines() + except Exception as e: print(f"Warning: Could not read {ini_path}: {e}") return - - # Check if section exists, if not we assume malformed or empty, so we proceed to check/fix - if not config.has_section('Main'): - config.add_section('Main') - - current_value = config.get('Main', 'IsOtpServer', fallback='false').lower() - - if current_value != 'true': - print(f"\nConfiguration Check: '{ini_path}'") - print("XIVLauncher requires 'IsOtpServer' to be enabled to accept OTP codes from this wrapper.") - choice = 'n' - if sys.stdin.isatty(): - try: - choice = input("Enable 'IsOtpServer' now? [Y/n]: ").strip().lower() - except EOFError: - choice = 'y' - else: - print("Non-interactive mode detected. Auto-enabling 'IsOtpServer'.") - choice = 'y' + has_otp = False + is_true = False + for line in lines: + if line.strip().lower().startswith('isotpserver='): + has_otp = True + if line.strip().lower().endswith('true'): + is_true = True + break - if choice in ('', 'y', 'yes'): - config.set('Main', 'IsOtpServer', 'true') - try: - with open(ini_path, 'w') as f: - config.write(f) - print("Updated launcher.ini successfully.") - except OSError as e: - print(f"Error writing to {ini_path}: {e}") - else: - print("Warning: OTP injection will likely fail without this setting.") + if has_otp and is_true: + return + + print(f"\nConfiguration Check: '{ini_path}'") + print("XIVLauncher requires 'IsOtpServer' to be enabled to accept OTP codes from this wrapper.") + + choice = 'n' + if sys.stdin.isatty(): + try: + choice = input("Enable 'IsOtpServer' now? [Y/n]: ").strip().lower() + except EOFError: + choice = 'y' + else: + print("Non-interactive mode detected. Auto-enabling 'IsOtpServer'.") + choice = 'y' + + if choice in ('', 'y', 'yes'): + try: + new_lines = [] + replaced = False + for line in lines: + if line.strip().lower().startswith('isotpserver='): + new_lines.append('IsOtpServer=true\n') + replaced = True + else: + new_lines.append(line) + if not replaced: + new_lines.append('IsOtpServer=true\n') + + with open(ini_path, 'w') as f: + f.writelines(new_lines) + print("Updated launcher.ini successfully.") + except OSError as e: + print(f"Error writing to {ini_path}: {e}") + else: + print("Warning: OTP injection will likely fail without this setting.") def detect_launch_command(): """Detects available XIVLauncher installations.""" @@ -370,6 +383,35 @@ def main(): # 1. Check and Update launcher.ini check_and_update_launcher_ini() + + # Retrieve Secret (Keyring refactor) - Moved up so users can configure secret before launch check fails + 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) + + if args.inject_only: + # Only run the OTP injection loop and exit + send_otp(secret) + sys.exit(0) + + if args.prelaunch: + print("Running in prelaunch mode. Spawning OTP injector in background...") + # Spawn ourselves in background with --inject-only + subprocess.Popen([sys.executable, os.path.realpath(__file__), '--inject-only'], + start_new_session=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + sys.exit(0) cmd = config.get("launcher_cmd") config_dirty = False @@ -388,9 +430,10 @@ def main(): available = detect_launch_command() if not available: - print("Error: No XIVLauncher installation found (Flatpak or Native).") - print("Please install XIVLauncher or manually set 'launcher_cmd' in config.json.") - sys.exit(1) + print("\nError: No XIVLauncher installation found (Flatpak or Native).") + print("If you are using XLM (Steam Compatibility Tool), you can ignore this error and just launch the game via Steam!") + print("Otherwise, please install XIVLauncher or manually set 'launcher_cmd' in config.json.") + sys.exit(0) # Exit 0 instead of 1 so it's not treated as a failure if they just wanted to set up the secret elif len(available) >= 1: name, command = available[0] @@ -438,34 +481,6 @@ def main(): except OSError as e: print(f"Warning: Could not save updated config: {e}") - # Retrieve Secret (Keyring refactor) - 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) - - if args.inject_only: - # Only run the OTP injection loop and exit - send_otp(secret) - sys.exit(0) - - if args.prelaunch: - print("Running in prelaunch mode. Spawning OTP injector in background...") - # Spawn ourselves in background with --inject-only - subprocess.Popen([sys.executable, os.path.realpath(__file__), '--inject-only'], - start_new_session=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) - sys.exit(0) print(f"Launch Command: {cmd}")