-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstart_cli.py
More file actions
199 lines (169 loc) · 9.45 KB
/
start_cli.py
File metadata and controls
199 lines (169 loc) · 9.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import argparse
import subprocess
import json
import os
import sys
import time
# Use absolute path relative to the script to ensure settings are found regardless of CWD
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SETTINGS_FILE = os.path.join(SCRIPT_DIR, "broadcast_settings.json")
# OPTIMIZATION: Set low priority so this launcher doesn't steal CPU from the game
try:
if os.name == 'nt':
import win32api, win32process, win32con
handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, os.getpid())
win32process.SetPriorityClass(handle, win32process.BELOW_NORMAL_PRIORITY_CLASS)
except: pass
def load_settings():
# Aligned with BroadCast-Archipelago.pyw defaults
defaults = {
"server": "archipelago.gg:", "slot": "", "password": "", "sync_mode": "all",
"win_w": 400, "win_h": 600, "win_x": -1, "win_y": -1, "display_index": 0,
"last_game": "", "multi_slots": "",
"sync_mode": "all", "enable_overlay": True, "enable_obs": False
}
if os.path.exists(SETTINGS_FILE):
try:
with open(SETTINGS_FILE, "r") as f:
data = json.load(f)
# Legacy compatibility: if 'mode' exists but not 'sync_mode', use 'mode'
if "mode" in data and "sync_mode" not in data:
data["sync_mode"] = data["mode"]
defaults.update(data)
except Exception as e:
print(f"Warning: Failed to load settings from {SETTINGS_FILE}: {e}")
else:
print(f"Notice: Settings file not found at {SETTINGS_FILE}. Using defaults.")
return defaults
def save_settings(settings):
try:
with open(SETTINGS_FILE, "w") as f:
json.dump(settings, f, indent=4)
except Exception as e:
print(f"Error saving settings: {e}")
def main():
parser = argparse.ArgumentParser(description="Headless Broadcast Launcher")
parser.add_argument("--server", help="Archipelago server address (e.g., archipelago.gg:12345)")
parser.add_argument("--slot", help="Slot name")
parser.add_argument("--password", help="Server password")
parser.add_argument("--mode", choices=["all", "personal", "obs", "filtered"], help="Tracking mode (alias for sync_mode)")
parser.add_argument("--obs", action="store_true", help="Enable OBS Web Server")
parser.add_argument("--no-obs", action="store_true", help="Disable OBS Web Server")
parser.add_argument("--obs-mode", choices=["all", "personal", "filtered"], help="Tracking mode for OBS specifically")
parser.add_argument("--multi", help="Multi-slots (Slot1:Pass1, Slot2:Pass2)")
parser.add_argument("--tracked", help="Tracked players list (comma separated)")
parser.add_argument("--overlay", action="store_true", help="Force enable overlay")
parser.add_argument("--no-overlay", action="store_true", help="Force disable overlay")
args = parser.parse_args()
settings = load_settings()
# Update settings from CLI if provided
updated = False
if args.server: settings["server"] = args.server; updated = True
if args.slot: settings["slot"] = args.slot; updated = True
if args.password is not None: settings["password"] = args.password; updated = True
# Map --mode argument to sync_mode
if args.mode:
if args.mode == "obs":
settings["enable_obs"] = True
# We don't change sync_mode if it was just 'obs' to enable features,
# unless sync_mode isn't set yet.
else:
settings["sync_mode"] = args.mode
updated = True
if args.obs: settings["enable_obs"] = True; updated = True
if args.no_obs: settings["enable_obs"] = False; updated = True
if args.obs_mode: settings["obs_sync_mode"] = args.obs_mode; updated = True
if args.multi: settings["multi_slots"] = args.multi; updated = True
if args.tracked: settings["tracked_players"] = args.tracked; updated = True
if args.overlay: settings["enable_overlay"] = True; updated = True
if args.no_overlay: settings["enable_overlay"] = False; updated = True
if updated:
save_settings(settings)
print("Settings updated from command line arguments.")
if not settings.get("server") or settings.get("server") == "archipelago.gg:" or not settings.get("slot"):
print("\n[!] Error: System not configured.")
print(f"Please configure settings in the Control Center (BroadCast-Archipelago.pyw)")
print(f"or use arguments: --server <addr> --slot <name>")
print(f"Checked path: {SETTINGS_FILE}")
sys.exit(1)
sync_mode = settings.get("sync_mode", "all")
print(f"Starting broadcast system for {settings['slot']} on {settings['server']}...")
print(f" - Sync Mode: {sync_mode}")
print(f" - Overlay: {'Enabled' if settings.get('enable_overlay') else 'Disabled'}")
print(f" - OBS Web Server: {'Enabled' if settings.get('enable_obs') else 'Disabled'}")
procs = []
try:
# 0. Start Web Server if needed (OBS / Overlay dev)
dist_path = os.path.join(SCRIPT_DIR, "broadcast-app", "dist")
packaged_exe = os.path.join(SCRIPT_DIR, "broadcast-app", "dist-packaged", "win-unpacked", "Broadcast-Overlay.exe")
has_packaged = os.path.exists(packaged_exe)
has_build = os.path.exists(dist_path) and os.path.exists(os.path.join(dist_path, "index.html"))
if settings.get("enable_obs"):
if has_build:
print("\n[0/3] Starting lightweight Python Web Server (for OBS) on port 5173...")
cmd = [sys.executable, "-m", "http.server", "5173", "--directory", dist_path]
procs.append(subprocess.Popen(cmd))
else:
print("\n[0/3] Starting Vite Dev Server (for OBS) on port 5173...")
procs.append(subprocess.Popen(["cmd", "/c", "npx vite --no-open"], cwd=os.path.join(SCRIPT_DIR, "broadcast-app"), shell=True))
elif settings.get("enable_overlay") and not has_packaged and not has_build:
print("\n[0/3] Starting Vite Dev Server (for Overlay Dev) on port 5173...")
procs.append(subprocess.Popen(["cmd", "/c", "npx vite --no-open"], cwd=os.path.join(SCRIPT_DIR, "broadcast-app"), shell=True))
# 1. Check/Build Frontend if Overlay is enabled and not packaged
if settings.get("enable_overlay") and not has_packaged:
print("[1/3] Checking Frontend Build...")
if not os.path.exists(dist_path) or not os.path.exists(os.path.join(dist_path, "index.html")):
print(" Building Frontend for the first time... (This may take a minute)")
subprocess.run(["cmd", "/c", "npm run build"], cwd=os.path.join(SCRIPT_DIR, "broadcast-app"), shell=True)
print(" Finished building Frontend!")
# 2. Start Bridge
print("[2/3] Starting AP Bridge Connection...")
# Determine bridge mode: If EITHER is 'all' or 'filtered', bridge must be 'all' to get the data
# This logic matches BroadCast-Archipelago.pyw
bridge_mode = "all"
if settings.get("sync_mode") == "personal" and settings.get("obs_sync_mode", "all") == "personal":
bridge_mode = "personal"
bridge_script = os.path.join(SCRIPT_DIR, "broadcast", "bridge.py")
bridge_cmd = [sys.executable, "-u", bridge_script, "--server", settings["server"], "--slot", settings["slot"], "--mode", bridge_mode]
if settings.get("password"):
bridge_cmd.extend(["--password", settings["password"]])
if settings.get("last_game"):
bridge_cmd.extend(["--game", settings["last_game"]])
if settings.get("multi_slots"):
bridge_cmd.extend(["--multi", settings["multi_slots"]])
if settings.get("tracked_players"):
bridge_cmd.extend(["--tracked", settings["tracked_players"]])
procs.append(subprocess.Popen(bridge_cmd))
# 3. Start Electron Overlay if enabled
if settings.get("enable_overlay"):
sys.stdout.write("Launching UI overlay... ")
sys.stdout.flush()
time.sleep(1) # Wait a bit for bridge
if has_packaged:
print("\r[3/3] Starting Electron Standalone Overlay UI... ")
procs.append(subprocess.Popen([packaged_exe]))
else:
print("\r[3/3] Starting Electron Overlay UI... ")
procs.append(subprocess.Popen(["cmd", "/c", "npm run overlay"], cwd=os.path.join(SCRIPT_DIR, "broadcast-app"), shell=True))
else:
print("[3/3] Electron Overlay disabled in settings. Skipping.")
print("\n>>> System is operational! <<<")
print(">>> Press Ctrl+C in this console to stop all processes. <<<\n")
# Keep main thread alive
while True:
# Check if bridge is still running
if procs:
for p in procs:
if p.poll() is not None and p.poll() != 0:
print(f"\n[!] A background process exited unexpectedly (Code: {p.poll()})")
time.sleep(2)
except KeyboardInterrupt:
print("\n\nShutting down system...")
finally:
for p in procs:
try:
subprocess.run(["taskkill", "/F", "/T", "/PID", str(p.pid)], capture_output=True)
except: pass
print("Cleaned up background processes.")
if __name__ == "__main__":
main()