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
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')
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
current_value = config.get('Main', 'IsOtpServer', fallback='false').lower()
if has_otp and is_true:
return
if current_value != 'true':
print(f"\nConfiguration Check: '{ini_path}'")
print("XIVLauncher requires 'IsOtpServer' to be enabled to accept OTP codes from this wrapper.")
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 = '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'):
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 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."""
@ -371,6 +384,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
cmd_args = None
@ -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}")