SC2: Windows ".dll missing" fix and fix for finding SC2 install automatically (#721)
This commit is contained in:
parent
6e0a0c5c4a
commit
e804f592de
|
@ -19,7 +19,13 @@ from worlds.sc2wol.Items import lookup_id_to_name, item_table
|
||||||
from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET
|
from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET
|
||||||
from worlds.sc2wol import SC2WoLWorld
|
from worlds.sc2wol import SC2WoLWorld
|
||||||
|
|
||||||
from Utils import init_logging
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
from MultiServer import mark_raw
|
||||||
|
import ctypes
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from Utils import init_logging, is_windows
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
init_logging("SC2Client", exception_logger="Client")
|
init_logging("SC2Client", exception_logger="Client")
|
||||||
|
@ -73,6 +79,17 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||||
request_unfinished_missions(self.ctx.checked_locations, self.ctx.mission_req_table, self.ctx.ui, self.ctx)
|
request_unfinished_missions(self.ctx.checked_locations, self.ctx.mission_req_table, self.ctx.ui, self.ctx)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@mark_raw
|
||||||
|
def _cmd_set_path(self, path: str = '') -> bool:
|
||||||
|
"""Manually set the SC2 install directory (if the automatic detection fails)."""
|
||||||
|
if path:
|
||||||
|
os.environ["SC2PATH"] = path
|
||||||
|
check_mod_install()
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
sc2_logger.warning("When using set_path, you must type the path to your SC2 install directory.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class SC2Context(CommonContext):
|
class SC2Context(CommonContext):
|
||||||
command_processor = StarcraftClientProcessor
|
command_processor = StarcraftClientProcessor
|
||||||
|
@ -111,6 +128,11 @@ class SC2Context(CommonContext):
|
||||||
for mission in slot_req_table:
|
for mission in slot_req_table:
|
||||||
self.mission_req_table[mission] = MissionInfo(**slot_req_table[mission])
|
self.mission_req_table[mission] = MissionInfo(**slot_req_table[mission])
|
||||||
|
|
||||||
|
# Look for and set SC2PATH.
|
||||||
|
# check_game_install_path() returns True if and only if it finds + sets SC2PATH.
|
||||||
|
if "SC2PATH" not in os.environ and check_game_install_path():
|
||||||
|
check_mod_install()
|
||||||
|
|
||||||
if cmd in {"PrintJSON"}:
|
if cmd in {"PrintJSON"}:
|
||||||
if "receiving" in args:
|
if "receiving" in args:
|
||||||
if self.slot_concerns_self(args["receiving"]):
|
if self.slot_concerns_self(args["receiving"]):
|
||||||
|
@ -415,6 +437,7 @@ async def starcraft_launch(ctx: SC2Context, mission_id):
|
||||||
|
|
||||||
sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id]}. If game does not launch check log file for errors.")
|
sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id]}. If game does not launch check log file for errors.")
|
||||||
|
|
||||||
|
with DllDirectory(None):
|
||||||
run_game(sc2.maps.get(maps_table[mission_id - 1]), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id),
|
run_game(sc2.maps.get(maps_table[mission_id - 1]), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id),
|
||||||
name="Archipelago", fullscreen=True)], realtime=True)
|
name="Archipelago", fullscreen=True)], realtime=True)
|
||||||
|
|
||||||
|
@ -796,6 +819,101 @@ def initialize_blank_mission_dict(location_table):
|
||||||
return unlocks
|
return unlocks
|
||||||
|
|
||||||
|
|
||||||
|
def check_game_install_path() -> bool:
|
||||||
|
# First thing: go to the default location for ExecuteInfo.
|
||||||
|
# An exception for Windows is included because it's very difficult to find ~\Documents if the user moved it.
|
||||||
|
if is_windows:
|
||||||
|
# The next five lines of utterly inscrutable code are brought to you by copy-paste from Stack Overflow.
|
||||||
|
# https://stackoverflow.com/questions/6227590/finding-the-users-my-documents-path/30924555#
|
||||||
|
import ctypes.wintypes
|
||||||
|
CSIDL_PERSONAL = 5 # My Documents
|
||||||
|
SHGFP_TYPE_CURRENT = 0 # Get current, not default value
|
||||||
|
|
||||||
|
buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
|
||||||
|
ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf)
|
||||||
|
documentspath = buf.value
|
||||||
|
einfo = str(documentspath / Path("StarCraft II\\ExecuteInfo.txt"))
|
||||||
|
else:
|
||||||
|
einfo = str(sc2.paths.get_home() / Path(sc2.paths.USERPATH[sc2.paths.PF]))
|
||||||
|
|
||||||
|
# Check if the file exists.
|
||||||
|
if os.path.isfile(einfo):
|
||||||
|
|
||||||
|
# Open the file and read it, picking out the latest executable's path.
|
||||||
|
with open(einfo) as f:
|
||||||
|
content = f.read()
|
||||||
|
if content:
|
||||||
|
base = re.search(r" = (.*)Versions", content).group(1)
|
||||||
|
if os.path.exists(base):
|
||||||
|
executable = sc2.paths.latest_executeble(Path(base).expanduser() / "Versions")
|
||||||
|
|
||||||
|
# Finally, check the path for an actual executable.
|
||||||
|
# If we find one, great. Set up the SC2PATH.
|
||||||
|
if os.path.isfile(executable):
|
||||||
|
sc2_logger.info(f"Found an SC2 install at {base}!")
|
||||||
|
sc2_logger.debug(f"Latest executable at {executable}.")
|
||||||
|
os.environ["SC2PATH"] = base
|
||||||
|
sc2_logger.debug(f"SC2PATH set to {base}.")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
sc2_logger.warning(f"We may have found an SC2 install at {base}, but couldn't find {executable}.")
|
||||||
|
else:
|
||||||
|
sc2_logger.warning(f"{einfo} pointed to {base}, but we could not find an SC2 install there.")
|
||||||
|
else:
|
||||||
|
sc2_logger.warning(f"Couldn't find {einfo}. Please run /set_path with your SC2 install directory.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_mod_install() -> bool:
|
||||||
|
# Pull up the SC2PATH if set. If not, encourage the user to manually run /set_path.
|
||||||
|
try:
|
||||||
|
# Check inside the Mods folder for Archipelago.SC2Mod. If found, tell user. If not, tell user.
|
||||||
|
if os.path.isfile(modfile := (os.environ["SC2PATH"] / Path("Mods") / Path("Archipelago.SC2Mod"))):
|
||||||
|
sc2_logger.info(f"Archipelago mod found at {modfile}.")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
sc2_logger.warning(f"Archipelago mod could not be found at {modfile}. Please install the mod file there.")
|
||||||
|
except KeyError:
|
||||||
|
sc2_logger.warning(f"SC2PATH isn't set. Please run /set_path with the path to your SC2 install.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class DllDirectory:
|
||||||
|
# Credit to Black Sliver for this code.
|
||||||
|
# More info: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw
|
||||||
|
_old: typing.Optional[str] = None
|
||||||
|
_new: typing.Optional[str] = None
|
||||||
|
|
||||||
|
def __init__(self, new: typing.Optional[str]):
|
||||||
|
self._new = new
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
old = self.get()
|
||||||
|
if self.set(self._new):
|
||||||
|
self._old = old
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
if self._old is not None:
|
||||||
|
self.set(self._old)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get() -> str:
|
||||||
|
if sys.platform == "win32":
|
||||||
|
n = ctypes.windll.kernel32.GetDllDirectoryW(0, None)
|
||||||
|
buf = ctypes.create_unicode_buffer(n)
|
||||||
|
ctypes.windll.kernel32.GetDllDirectoryW(n, buf)
|
||||||
|
return buf.value
|
||||||
|
# NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set(s: typing.Optional[str]) -> bool:
|
||||||
|
if sys.platform == "win32":
|
||||||
|
return ctypes.windll.kernel32.SetDllDirectoryW(s) != 0
|
||||||
|
# NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
colorama.init()
|
colorama.init()
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|
Loading…
Reference in New Issue