refactor: replace configparser with manual file processing for launcher.ini and reorder initialization logic to support early secret configuration and background injection

This commit is contained in:
CPTN Cosmo 2026-04-18 15:46:12 +02:00
parent 58508465c7
commit b1a25e20ee

View file

@ -156,45 +156,58 @@ def check_and_update_launcher_ini():
pass pass
return return
config = configparser.ConfigParser()
config.optionxform = str # Preserve case sensitivity
try: try:
config.read(ini_path) with open(ini_path, 'r') as f:
except configparser.Error as e: lines = f.readlines()
except Exception as e:
print(f"Warning: Could not read {ini_path}: {e}") print(f"Warning: Could not read {ini_path}: {e}")
return 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' has_otp = False
if sys.stdin.isatty(): is_true = False
try: for line in lines:
choice = input("Enable 'IsOtpServer' now? [Y/n]: ").strip().lower() if line.strip().lower().startswith('isotpserver='):
except EOFError: has_otp = True
choice = 'y' if line.strip().lower().endswith('true'):
else: is_true = True
print("Non-interactive mode detected. Auto-enabling 'IsOtpServer'.") break
choice = 'y'
if choice in ('', 'y', 'yes'): if has_otp and is_true:
config.set('Main', 'IsOtpServer', 'true') return
try:
with open(ini_path, 'w') as f: print(f"\nConfiguration Check: '{ini_path}'")
config.write(f) print("XIVLauncher requires 'IsOtpServer' to be enabled to accept OTP codes from this wrapper.")
print("Updated launcher.ini successfully.")
except OSError as e: choice = 'n'
print(f"Error writing to {ini_path}: {e}") if sys.stdin.isatty():
else: try:
print("Warning: OTP injection will likely fail without this setting.") 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(): def detect_launch_command():
"""Detects available XIVLauncher installations.""" """Detects available XIVLauncher installations."""
@ -370,6 +383,35 @@ def main():
# 1. Check and Update launcher.ini # 1. Check and Update launcher.ini
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") cmd = config.get("launcher_cmd")
config_dirty = False config_dirty = False
@ -388,9 +430,10 @@ def main():
available = detect_launch_command() available = detect_launch_command()
if not available: if not available:
print("Error: No XIVLauncher installation found (Flatpak or Native).") print("\nError: No XIVLauncher installation found (Flatpak or Native).")
print("Please install XIVLauncher or manually set 'launcher_cmd' in config.json.") print("If you are using XLM (Steam Compatibility Tool), you can ignore this error and just launch the game via Steam!")
sys.exit(1) 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: elif len(available) >= 1:
name, command = available[0] name, command = available[0]
@ -438,34 +481,6 @@ def main():
except OSError as e: except OSError as e:
print(f"Warning: Could not save updated config: {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}") print(f"Launch Command: {cmd}")