162 lines
4.7 KiB
Python
Executable File
162 lines
4.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Copy workshop mods from SERVER.xml into LocalMods/ with readable names."""
|
|
|
|
import shutil
|
|
import sys
|
|
import os
|
|
import xml.etree.ElementTree as ET
|
|
|
|
WORKSHOP_DIR = "/mnt/nvme/B/SteamLibrary/steamapps/workshop/content/602960"
|
|
MODLIST_PATH = "/mnt/nvme/B/SteamLibrary/steamapps/common/Barotrauma/ModLists/SERVER.xml"
|
|
LOCALMODS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LocalMods")
|
|
|
|
|
|
def log_ok(msg: str, indent: int = 0):
|
|
prefix = " " * indent + "✓"
|
|
print(f"\033[92m{prefix}\033[0m {msg}")
|
|
|
|
|
|
def log_info(msg: str, indent: int = 0):
|
|
prefix = " " * indent + "•"
|
|
print(f"{prefix} {msg}")
|
|
|
|
|
|
def log_warn(msg: str, indent: int = 0):
|
|
prefix = " " * indent + "⚠"
|
|
print(f"\033[93m{prefix}\033[0m {msg}")
|
|
|
|
|
|
def log_err(msg: str, indent: int = 0):
|
|
prefix = " " * indent + "✗"
|
|
print(f"\033[91m{prefix}\033[0m {msg}", file=sys.stderr)
|
|
|
|
|
|
def sanitize_folder_name(name: str) -> str:
|
|
forbidden = '/\\:*?"<>|'
|
|
for ch in forbidden:
|
|
name = name.replace(ch, "_")
|
|
return name.strip()
|
|
|
|
|
|
def get_mod_name_from_filelist(folder_path: str) -> str | None:
|
|
fl_path = os.path.join(folder_path, "filelist.xml")
|
|
if not os.path.isfile(fl_path):
|
|
return None
|
|
try:
|
|
root = ET.parse(fl_path).getroot()
|
|
return root.get("name")
|
|
except Exception as e:
|
|
log_warn(f"Failed to parse filelist.xml: {e}", indent=2)
|
|
return None
|
|
|
|
|
|
def copy_mod(src: str, dst: str) -> bool:
|
|
if os.path.exists(dst):
|
|
log_info(f"Removing existing folder: {dst}", indent=2)
|
|
shutil.rmtree(dst)
|
|
log_info(f"Copying: {src} → {dst}", indent=2)
|
|
shutil.copytree(src, dst, symlinks=True)
|
|
return True
|
|
|
|
|
|
def main():
|
|
print()
|
|
print("╔══════════════════════════════════════════╗")
|
|
print("║ Prepare Local Mods from SERVER ║")
|
|
print("╚══════════════════════════════════════════╝")
|
|
print()
|
|
|
|
if not os.path.isfile(MODLIST_PATH):
|
|
log_err(f"Mod list not found: {MODLIST_PATH}")
|
|
sys.exit(1)
|
|
|
|
log_info(f"Reading mod list: {MODLIST_PATH}")
|
|
try:
|
|
tree = ET.parse(MODLIST_PATH)
|
|
root = tree.getroot()
|
|
except Exception as e:
|
|
log_err(f"Failed to parse mod list: {e}")
|
|
sys.exit(1)
|
|
|
|
mods = root.findall("Workshop")
|
|
if not mods:
|
|
log_warn("No <Workshop> entries found in mod list")
|
|
sys.exit(0)
|
|
|
|
log_info(f"Found {len(mods)} workshop mod(s)")
|
|
print()
|
|
|
|
copied = 0
|
|
skipped = 0
|
|
errors = 0
|
|
|
|
for mod in mods:
|
|
mod_id = mod.get("id", "").strip()
|
|
mod_name = mod.get("name", "").strip()
|
|
|
|
if not mod_id:
|
|
log_warn(f"Skipping entry with no id: name='{mod_name}'", indent=1)
|
|
skipped += 1
|
|
continue
|
|
|
|
print(f"[{mod_id}] {mod_name}")
|
|
log_info(f"Processing mod: {mod_name}", indent=1)
|
|
|
|
src_dir = os.path.join(WORKSHOP_DIR, mod_id)
|
|
if not os.path.isdir(src_dir):
|
|
log_err(f"Workshop folder not found: {src_dir}", indent=2)
|
|
errors += 1
|
|
continue
|
|
|
|
display_name = get_mod_name_from_filelist(src_dir)
|
|
if not display_name:
|
|
log_warn("No name found in filelist.xml, using Steam name", indent=2)
|
|
display_name = mod_name
|
|
|
|
safe_name = sanitize_folder_name(display_name)
|
|
if not safe_name:
|
|
log_warn("Sanitized name is empty, using mod ID as fallback", indent=2)
|
|
safe_name = mod_id
|
|
|
|
dst_dir = os.path.join(LOCALMODS_DIR, safe_name)
|
|
|
|
try:
|
|
copy_mod(src_dir, dst_dir)
|
|
log_ok(f"Copied as: {safe_name}", indent=2)
|
|
copied += 1
|
|
except Exception as e:
|
|
log_err(f"Copy failed: {e}", indent=2)
|
|
errors += 1
|
|
|
|
print()
|
|
|
|
print("─" * 50)
|
|
log_ok(f"Copied: {copied}")
|
|
log_info(f"Skipped: {skipped}")
|
|
if errors:
|
|
log_err(f"Errors: {errors}")
|
|
|
|
local_mods = root.findall("Local")
|
|
if local_mods:
|
|
print()
|
|
log_info(f"Checking {len(local_mods)} local mod reference(s)…")
|
|
for lm in local_mods:
|
|
lm_name = lm.get("name", "").strip()
|
|
if not lm_name:
|
|
continue
|
|
lm_path = os.path.join(LOCALMODS_DIR, sanitize_folder_name(lm_name))
|
|
if os.path.isdir(lm_path):
|
|
log_ok(f"Local mod present: {lm_name}", indent=1)
|
|
else:
|
|
log_warn(f"Local mod NOT found: {lm_name}", indent=1)
|
|
print()
|
|
|
|
if not errors and skipped == 0:
|
|
print()
|
|
log_ok("All mods prepared successfully!")
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|