118 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			118 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								# pylint: disable=R0911,W1510
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								import subprocess
							 | 
						||
| 
								 | 
							
								from pathlib import Path, PureWindowsPath
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from worlds._sc2common.bot import logger
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## This file is used for compatibility with WSL and shouldn't need to be
							 | 
						||
| 
								 | 
							
								## accessed directly by any bot clients
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def win_path_to_wsl_path(path):
							 | 
						||
| 
								 | 
							
								    """Convert a path like C:\\foo to /mnt/c/foo"""
							 | 
						||
| 
								 | 
							
								    return Path("/mnt") / PureWindowsPath(re.sub("^([A-Z]):", lambda m: m.group(1).lower(), path))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def wsl_path_to_win_path(path):
							 | 
						||
| 
								 | 
							
								    """Convert a path like /mnt/c/foo to C:\\foo"""
							 | 
						||
| 
								 | 
							
								    return PureWindowsPath(re.sub("^/mnt/([a-z])", lambda m: m.group(1).upper() + ":", path))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_wsl_home():
							 | 
						||
| 
								 | 
							
								    """Get home directory of from Windows, even if run in WSL"""
							 | 
						||
| 
								 | 
							
								    proc = subprocess.run(["powershell.exe", "-Command", "Write-Host -NoNewLine $HOME"], capture_output=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if proc.returncode != 0:
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return win_path_to_wsl_path(proc.stdout.decode("utf-8"))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								RUN_SCRIPT = """$proc = Start-Process -NoNewWindow -PassThru "%s" "%s"
							 | 
						||
| 
								 | 
							
								if ($proc) {
							 | 
						||
| 
								 | 
							
								    Write-Host $proc.id
							 | 
						||
| 
								 | 
							
								    exit $proc.ExitCode
							 | 
						||
| 
								 | 
							
								} else {
							 | 
						||
| 
								 | 
							
								    exit 1
							 | 
						||
| 
								 | 
							
								}"""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def run(popen_args, sc2_cwd):
							 | 
						||
| 
								 | 
							
								    """Run SC2 in Windows and get the pid so that it can be killed later."""
							 | 
						||
| 
								 | 
							
								    path = wsl_path_to_win_path(popen_args[0])
							 | 
						||
| 
								 | 
							
								    args = " ".join(popen_args[1:])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return subprocess.Popen(
							 | 
						||
| 
								 | 
							
								        ["powershell.exe", "-Command", RUN_SCRIPT % (path, args)],
							 | 
						||
| 
								 | 
							
								        cwd=sc2_cwd,
							 | 
						||
| 
								 | 
							
								        stdout=subprocess.PIPE,
							 | 
						||
| 
								 | 
							
								        universal_newlines=True,
							 | 
						||
| 
								 | 
							
								        bufsize=1,
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def kill(wsl_process):
							 | 
						||
| 
								 | 
							
								    """Needed to kill a process started with WSL. Returns true if killed successfully."""
							 | 
						||
| 
								 | 
							
								    # HACK: subprocess and WSL1 appear to have a nasty interaction where
							 | 
						||
| 
								 | 
							
								    # any streams are never closed and the process is never considered killed,
							 | 
						||
| 
								 | 
							
								    # despite having an exit code (this works on WSL2 as well, but isn't
							 | 
						||
| 
								 | 
							
								    # necessary). As a result,
							 | 
						||
| 
								 | 
							
								    # 1: We need to read using readline (to make sure we block long enough to
							 | 
						||
| 
								 | 
							
								    #    get the exit code in the rare case where the user immediately hits ^C)
							 | 
						||
| 
								 | 
							
								    out = wsl_process.stdout.readline().rstrip()
							 | 
						||
| 
								 | 
							
								    # 2: We need to use __exit__, since kill() calls send_signal(), which thinks
							 | 
						||
| 
								 | 
							
								    #    the process has already exited!
							 | 
						||
| 
								 | 
							
								    wsl_process.__exit__(None, None, None)
							 | 
						||
| 
								 | 
							
								    proc = subprocess.run(["taskkill.exe", "-f", "-pid", out], capture_output=True)
							 | 
						||
| 
								 | 
							
								    return proc.returncode == 0  # Returns 128 on failure
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def detect():
							 | 
						||
| 
								 | 
							
								    """Detect the current running version of WSL, and bail out if it doesn't exist"""
							 | 
						||
| 
								 | 
							
								    # Allow disabling WSL detection with an environment variable
							 | 
						||
| 
								 | 
							
								    if os.getenv("SC2_WSL_DETECT", "1") == "0":
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    wsl_name = os.environ.get("WSL_DISTRO_NAME")
							 | 
						||
| 
								 | 
							
								    if not wsl_name:
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        wsl_proc = subprocess.run(["wsl.exe", "--list", "--running", "--verbose"], capture_output=True)
							 | 
						||
| 
								 | 
							
								    except (OSError, ValueError):
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								    if wsl_proc.returncode != 0:
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # WSL.exe returns a bunch of null characters for some reason, as well as
							 | 
						||
| 
								 | 
							
								    # windows-style linebreaks. It's inconsistent about how many \rs it uses
							 | 
						||
| 
								 | 
							
								    # and this could change in the future, so strip out all junk and split by
							 | 
						||
| 
								 | 
							
								    # Unix-style newlines for safety's sake.
							 | 
						||
| 
								 | 
							
								    lines = re.sub(r"\000|\r", "", wsl_proc.stdout.decode("utf-8")).split("\n")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def line_has_proc(ln):
							 | 
						||
| 
								 | 
							
								        return re.search("^\\s*[*]?\\s+" + wsl_name, ln)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def line_version(ln):
							 | 
						||
| 
								 | 
							
								        return re.sub("^.*\\s+(\\d+)\\s*$", "\\1", ln)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    versions = [line_version(ln) for ln in lines if line_has_proc(ln)]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        version = versions[0]
							 | 
						||
| 
								 | 
							
								        if int(version) not in [1, 2]:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								    except (ValueError, IndexError):
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    logger.info(f"WSL version {version} detected")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if version == "2" and not (os.environ.get("SC2CLIENTHOST") and os.environ.get("SC2SERVERHOST")):
							 | 
						||
| 
								 | 
							
								        logger.warning("You appear to be running WSL2 without your hosts configured correctly.")
							 | 
						||
| 
								 | 
							
								        logger.warning("This may result in SC2 staying on a black screen and not connecting to your bot.")
							 | 
						||
| 
								 | 
							
								        logger.warning("Please see the python-sc2 README for WSL2 configuration instructions.")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return "WSL" + version
							 |