Skip to content

Instantly share code, notes, and snippets.

@Mara-Li
Last active February 27, 2026 05:32
Show Gist options
  • Select an option

  • Save Mara-Li/784fccb3b6841667afd2e4b10434744d to your computer and use it in GitHub Desktop.

Select an option

Save Mara-Li/784fccb3b6841667afd2e4b10434744d to your computer and use it in GitHub Desktop.
FFIXV automatic login using 1password/ProtonPass cli & XIVLauncher

Warning

The two following scripts are Windows only It should be possible to make them Linux or Mac Ready but I don't play FFXIV on the steamdeck so…

1Password

You need to install 1password Cli first.

image

Or use :

ps2exe ".\automatic_login.ps1" "XIVLauncher.exe" -noConsole -icon ".\dalamud.ico" -title "XIVLauncher" -STA -noOutput -noError

Proton Pass

You need to install Proton Pass Cli and Python. Tkinter should be already included (as it is a part of the main library).

Config

You need to create a environment variable named "FFXIV_OTP" with Vault_Name;KeyName For example: Personal;FFXIV

Additionnally, if you want to change the default port, add it in the key: VAULT_NAME;ITEM_TITLE;[PORT] For example: Personal;FFXIV;4646

(You can also configure FFIXV_OTP_PORT env key for the port)

Also it is possible to set the XIVLauncher exe using the key FFXIV_LAUNCHER_PATH.

Note

If you want to pin to your taskbar the pyw, you need to create a shortcut with the path as follow: pythonw.exe "FFXIV.pyw"

# You shouldn't need to modify this unless you have a custom launcher install
$LauncherPath = Join-Path $env:LOCALAPPDATA "XIVLauncher\XIVLauncher.exe"
# change this to the key in 1password
$VaultItemName = "FFXIV"
function Get-OtpFrom1Password {
param([Parameter(Mandatory)][string]$ItemName)
$psi = [System.Diagnostics.ProcessStartInfo]::new()
$psi.FileName = "op"
$psi.Arguments = "item get `"$ItemName`" --otp"
$psi.UseShellExecute = $false
$psi.CreateNoWindow = $true
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$p = [System.Diagnostics.Process]::Start($psi)
$out = $p.StandardOutput.ReadToEnd().Trim()
$err = $p.StandardError.ReadToEnd().Trim()
$p.WaitForExit()
if ($p.ExitCode -ne 0 -or [string]::IsNullOrWhiteSpace($out)) {
throw "op failed (exit $($p.ExitCode)): $err"
}
return $out
}
try {
$OTP = Get-OtpFrom1Password -ItemName $VaultItemName
} catch {
# pas de Read-Host dans un exe GUI
[System.Windows.Forms.MessageBox]::Show($_.Exception.Message, "XIVLauncher OTP", "OK", "Error") | Out-Null
exit 1
}
if ($OTP.Length -eq 6) {
# Lance directement le launcher (PAS powershell.exe)
Start-Process -FilePath $LauncherPath
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
Start-Sleep -Seconds 1
try {
Invoke-WebRequest -Uri "http://127.0.0.1:4646/ffxivlauncher/$OTP" -UseBasicParsing | Out-Null
} catch { }
} else {
[System.Windows.Forms.MessageBox]::Show("OTP invalide: '$OTP'", "XIVLauncher OTP", "OK", "Error") | Out-Null
exit 1
}
import subprocess
import json
import time
import sys
import os
import threading
import requests
from tkinter import messagebox
def show_error(message):
messagebox.showerror("XIVLauncher OTP", message)
# ================================
# CONFIG
# ================================
def get_launcher_path():
default_path = path = os.path.join(os.environ["LOCALAPPDATA"], "XIVLauncher", "XIVLauncher.exe")
path_envs = os.environ.get("FFXIV_LAUNCHER_PATH", default_path)
if not os.path.exists(path_envs):
show_error(f"XIVLauncher not found at {path}.")
sys.exit(1)
return path
LAUNCHER_PATH = get_launcher_path()
def params():
OTP_ENV = os.environ.get("FFXIV_OTP")
if not OTP_ENV:
show_error(
"The FFXIV_OTP environment variable is not set. It must be in the format VAULT_NAME;ITEM_TITLE;[PORT]."
)
sys.exit(0)
ENVS = OTP_ENV.split(";")
if len(ENVS) < 2:
show_error(
"The FFXIV_OTP environment variable is not in the correct format. It must be in the format VAULT_NAME;ITEM_TITLE;[PORT]."
)
sys.exit(1)
VAULT_NAME = ENVS[0].strip()
ITEM_TITLE = ENVS[1].strip()
PORT = ENVS[2].strip() if len(ENVS) > 2 else os.environ.get("FFIXV_OTP_PORT", "4646")
return VAULT_NAME, ITEM_TITLE, PORT
VAULT_NAME, ITEM_TITLE, PORT = params()
# ================================
# PROTON PASS
# ================================
def get_otp():
try:
result = subprocess.run(
[
"pass-cli",
"item",
"totp",
"--vault-name",
VAULT_NAME,
"--item-title",
ITEM_TITLE,
"--output",
"json",
],
capture_output=True,
text=True,
creationflags=subprocess.CREATE_NO_WINDOW, # <<< No console window
)
if result.returncode != 0 or not result.stdout.strip():
raise Exception(result.stderr.strip())
data = json.loads(result.stdout)
otp = data.get("totp")
if not otp or len(otp) != 6:
raise Exception(f"OTP invalide: {otp}")
return otp
except Exception as e:
show_error(f"Failed to get OTP: {e}")
exit(1)
# ================================
# WAIT FOR LAUNCHER
# ================================
def wait_for_launcher(timeout=30):
for _ in range(timeout * 2): # 0.5s interval
try:
requests.get(f"http://127.0.0.1:{PORT}", timeout=1)
return True
except requests.RequestException:
time.sleep(0.5)
return False
# ================================
# MAIN
# ================================
# Start the launcher in the background
subprocess.Popen([LAUNCHER_PATH])
# Get OTP
otp_result = [None]
def otp_thread():
otp_result[0] = get_otp()
t = threading.Thread(target=otp_thread)
t.start()
if not wait_for_launcher():
show_error(f"XIV doesn't respond on configured {PORT} port.")
sys.exit(1)
t.join()
OTP = otp_result[0]
# Inject!
try:
requests.get(f"http://127.0.0.1:{PORT}/ffxivlauncher/{OTP}", timeout=3)
except requests.RequestException:
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment