fix: reach the Windows side from WSL

Windows rules ran under WSL with C:\ paths read as relative and no Windows variables, so nothing was found and a forced RetroBat install went to ~/bios inside the distribution.
This commit is contained in:
Abdessamad Derraz committed 2026-10-18 20:53:11 +02:00
1 parent 16f8b87ef4
commit 7e71f06c57
5 files changed
+100 -7

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ param(
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "d86244b95c49d37d3950e0547fe13fa6df589dc21ba9868d46da4ab305522b3f"
$defaultInstallSha256 = "142edea98e646ed11e27052d3dd02125a27d4cdb7ee04392e6af8fcd28f9762f"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+51 -5
View File
@@ -26,6 +26,7 @@ import platform
import re
import shutil
import stat
import subprocess
import sys
import tarfile
import tempfile
@@ -181,9 +182,16 @@ def _os_default_dests(os_type: str) -> dict[str, Path]:
return {
# RetroArch's system directory defaults to <exe folder>\system
# (platform_win32.c), and its installer to C:\RetroArch-Win64.
"retroarch": Path("C:/RetroArch-Win64/system"),
"retroarch": _host_path("C:/RetroArch-Win64/system"),
# RetroBat's installer defaults to C:\RetroBat (installer.iss).
"retrobat": Path("C:/RetroBat/bios"),
"retrobat": _host_path("C:/RetroBat/bios"),
}
if os_type == "wsl":
# RetroArch may be the Linux one inside the distribution; RetroBat
# only exists on the Windows side, reached through /mnt/c.
return {
"retroarch": home / ".config" / "retroarch" / "system",
"retrobat": _host_path("C:/RetroBat/bios"),
}
if os_type == "darwin":
return {"retroarch": _macos_retroarch_base() / "system"}
@@ -435,6 +443,42 @@ def _parse_os_release() -> dict[str, str]:
return result
# Set under WSL: a Windows path read from a config or a rule names a drive
# that Linux sees under /mnt (drvfs). C:\\RetroBat\\bios was used as a relative
# path and nothing Windows-side was ever found from WSL.
_WSL = {"active": False, "root": "/mnt"}
_WINDOWS_VARIABLES = ("USERPROFILE", "APPDATA", "LOCALAPPDATA", "ProgramFiles", "ProgramFiles(x86)")
def _host_path(text: str) -> Path:
"""A path as this machine opens it: under WSL, C:\\X is /mnt/c/X."""
match = re.match(r"^([A-Za-z]):(?:[\\/]+(.*))?$", text) if _WSL["active"] else None
if match:
rest = [part for part in re.split(r"[\\/]", match.group(2) or "") if part]
return Path(_WSL["root"], match.group(1).lower(), *rest)
return Path(text)
def _enter_wsl() -> None:
"""Read Windows paths through drvfs, and the Windows variables detection
needs, which WSL does not pass to Linux (only those named in WSLENV)."""
_WSL["active"] = True
for variable in _WINDOWS_VARIABLES:
if os.environ.get(variable):
continue
try:
result = subprocess.run(
["cmd.exe", "/c", f"echo %{variable}%"],
capture_output=True, text=True, timeout=10,
cwd="/mnt/c" if Path("/mnt/c").is_dir() else None,
)
except (OSError, subprocess.SubprocessError):
return
value = result.stdout.strip()
if value and not value.startswith("%"):
os.environ[variable] = str(_host_path(value))
def _expand_retroarch_path(value: str, app_dir: Path) -> Path:
"""Expand the notations RetroArch writes into its config values.
@@ -446,7 +490,7 @@ def _expand_retroarch_path(value: str, app_dir: Path) -> Path:
return Path(str(Path.home())) / value[2:]
if value[:1] == ":":
return app_dir / value[2:]
return Path(os.path.expandvars(os.path.expanduser(value)))
return _host_path(os.path.expandvars(os.path.expanduser(value)))
def _retroarch_cfg_dir(
@@ -506,7 +550,7 @@ def _windows_retroarch_dirs() -> list[Path]:
names under Program Files, every Steam library, and the Emulators folder
of an installed ES-DE (es_find_rules.xml lists all of them).
"""
candidates = [Path("C:/RetroArch-Win64"), Path("C:/RetroArch")]
candidates = [_host_path("C:/RetroArch-Win64"), _host_path("C:/RetroArch")]
for var in ("ProgramFiles", "ProgramFiles(x86)"):
root = os.environ.get(var, "")
if root:
@@ -960,7 +1004,7 @@ def _rule_path(template: str) -> Path:
if template.startswith(variable):
rest = [part for part in re.split(r"[\\/]", template[len(variable):]) if part]
return base.joinpath(*rest)
return Path(template)
return _host_path(template)
# The registry's executable rules: (os, platform, glob under $HOME or
@@ -3501,6 +3545,8 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
print(f"{term.bold('RetroBIOS')} {term.dim(installer_id())}\n")
os_type = detect_os()
if os_type == "wsl":
_enter_wsl()
# Early exit for listing
if args.list_platforms:
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="d86244b95c49d37d3950e0547fe13fa6df589dc21ba9868d46da4ab305522b3f"
DEFAULT_INSTALL_SHA256="142edea98e646ed11e27052d3dd02125a27d4cdb7ee04392e6af8fcd28f9762f"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+40
View File
@@ -2786,5 +2786,45 @@ class APartialIsClosedBeforeItIsDeleted(unittest.TestCase):
self.assertLess(branch.index("partial.close()"), branch.index("tmp_path.unlink()"))
class WindowsSeenFromWsl(unittest.TestCase):
"""Under WSL, C:\\RetroBat\\bios was a relative path, %APPDATA% and
%USERPROFILE% were unset, and --platform retrobat wrote 7 753 files into
~/bios inside the distribution, where RetroBat reads nothing."""
def setUp(self):
self.root = Path(_tempdir(self))
patcher = unittest.mock.patch.dict(install._WSL, {"active": True, "root": str(self.root)})
patcher.start()
self.addCleanup(patcher.stop)
def test_a_drive_path_is_its_mount(self):
self.assertEqual(install._host_path("C:\\RetroBat\\bios"), self.root / "c" / "RetroBat" / "bios")
self.assertEqual(install._rule_path("C:\\RetroBat\\bios"), self.root / "c" / "RetroBat" / "bios")
self.assertEqual(install._default_dest("wsl", "retrobat"), self.root / "c" / "RetroBat" / "bios")
self.assertEqual(install._host_path("relative/path"), Path("relative/path"))
def test_retrobat_on_the_windows_side_is_detected(self):
(self.root / "c" / "RetroBat" / "bios").mkdir(parents=True)
found: list = []
install._detect_by_path("wsl", found)
self.assertIn(("retrobat", self.root / "c" / "RetroBat" / "bios"), found)
def test_the_windows_variables_are_read_through_cmd(self):
answers = {"USERPROFILE": "C:\\Users\\me", "APPDATA": "C:\\Users\\me\\AppData\\Roaming"}
def cmd(argv, **_kwargs):
name = argv[-1].split("%")[1]
return subprocess.CompletedProcess(argv, 0, answers.get(name, f"%{name}%") + "\r\n", "")
cleared = {name: "" for name in install._WINDOWS_VARIABLES}
with unittest.mock.patch.dict(os.environ, cleared), \
unittest.mock.patch.object(install.subprocess, "run", cmd):
install._enter_wsl()
self.assertEqual(os.environ["USERPROFILE"], str(self.root / "c" / "Users" / "me"))
self.assertEqual(os.environ["APPDATA"],
str(self.root / "c" / "Users" / "me" / "AppData" / "Roaming"))
self.assertEqual(os.environ["ProgramFiles"], "")
if __name__ == "__main__":
unittest.main()
+7
View File
@@ -291,6 +291,13 @@ not already found that way.
Desktop installs are read from their own configuration, and several can be
found on one machine.
Under WSL both sides are searched. The Windows one is reached through its
`/mnt/<drive>` mount: a `C:\` path from a rule or a config becomes
`/mnt/c/...`, and the Windows variables the rules use (`USERPROFILE`,
`APPDATA`, `LOCALAPPDATA`, `ProgramFiles`) are read through `cmd.exe` when
WSL does not pass them. A forced `--platform retrobat` falls back to
`/mnt/c/RetroBat/bios`.
| Evidence | Platform | BIOS directory |
|----------|----------|----------------|
| `~/.config/EmuDeck/settings.sh` | EmuDeck | `emulationPath` + `/bios` |