mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
fix(proton-run): register the wrapper for an installed Valve Proton depot (#2392)
* fix(proton-run): find Proton tools in Steam libraries * fix(proton-run): register the wrapper for an installed Valve Proton depot Valve's ARM64 Proton depots are not compatibility tools, and running them directly bypasses the DroidDeck wrapper. Register the wrapper from the depot when a store app is launched before Steam has started, instead of scanning steamapps/common. * test(proton): install complete tool fixtures in the auto-update tests * fix(proton-run): skip incomplete Proton depots and keep listing tools when the wrapper cannot be written * fix(proton-run): pick a runnable depot and rebuild a wrapper whose launcher is missing * test(proton-run): remove redundant depot fixture write * fix(proton-run): rebuild a wrapper whose manifest is missing * test(proton-run): cover runnable depot selection
This commit is contained in:
1 parent
13c8edb3d5
commit
634cb390c9
6 files changed
+117
-5
No files matched your search
@@ -48,11 +48,19 @@ object ProtonExtras {
|
||||
|
||||
internal fun installedIn(dir: File, tool: Tool): List<String> =
|
||||
dir.listFiles()
|
||||
?.filter { it.isDirectory && it.name.startsWith(tool.prefix) && File(it, "toolmanifest.vdf").isFile }
|
||||
?.filter { it.isDirectory && it.name.startsWith(tool.prefix) && usable(it) }
|
||||
?.map { it.name }
|
||||
?.sortedWith { a, b -> compareVersions(a, b) }
|
||||
?: emptyList()
|
||||
|
||||
private fun usable(dir: File): Boolean {
|
||||
val manifest = runCatching { File(dir, "toolmanifest.vdf").readText() }.getOrNull() ?: return false
|
||||
val command = Regex("\\\"commandline\\\"\\s+\\\"/?([^\\\" ]+)").find(manifest)?.groupValues?.getOrNull(1) ?: return false
|
||||
val compat = runCatching { File(dir, "compatibilitytool.vdf").readText() }.getOrNull() ?: return false
|
||||
return Regex("\\\"from_oslist\\\"\\s+\\\"[^\\\"]*windows", RegexOption.IGNORE_CASE).containsMatchIn(compat) &&
|
||||
File(dir, command).isFile && File(dir, command).canExecute()
|
||||
}
|
||||
|
||||
private val ARCH = Regex("(?i)aarch64|arm64|x86_64|x86-64|amd64")
|
||||
private val NUMBER = Regex("\\d+")
|
||||
|
||||
|
||||
@@ -83,7 +83,9 @@ class ProtonMenuAutoTest {
|
||||
|
||||
private fun install(name: String): File = File(root, "root/.local/share/Steam/compatibilitytools.d/$name").apply {
|
||||
mkdirs()
|
||||
File(this, "toolmanifest.vdf").writeText("manifest")
|
||||
File(this, "toolmanifest.vdf").writeText("\"manifest\" { \"commandline\" \"/proton %verb%\" }")
|
||||
File(this, "compatibilitytool.vdf").writeText("\"from_oslist\" \"windows\"")
|
||||
File(this, "proton").apply { writeText(""); setExecutable(true) }
|
||||
}
|
||||
|
||||
private fun waitUntil(done: () -> Boolean) {
|
||||
|
||||
@@ -44,6 +44,8 @@ class ProtonExtrasTest {
|
||||
private fun installTool(parent: File, name: String) = File(parent, name).apply {
|
||||
mkdirs()
|
||||
File(this, "toolmanifest.vdf").writeText("\"manifest\" { \"commandline\" \"/proton %verb%\" }")
|
||||
File(this, "compatibilitytool.vdf").writeText("\"from_oslist\" \"windows\"")
|
||||
File(this, "proton").apply { writeText(""); setExecutable(true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -120,6 +122,17 @@ class ProtonExtrasTest {
|
||||
assertEquals(emptyList<String>(), ProtonExtras.installedIn(File(tools, "missing"), tool("ge")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun interruptedBuildsAreNotReportedAsInstalled() {
|
||||
val tools = temp.newFolder("compatibilitytools.d")
|
||||
installTool(tools, "GE-Proton11-10")
|
||||
File(tools, "GE-Proton11-11").apply {
|
||||
mkdirs()
|
||||
File(this, "toolmanifest.vdf").writeText("\"manifest\" { \"commandline\" \"/proton %verb%\" }")
|
||||
}
|
||||
assertEquals(listOf("GE-Proton11-10"), ProtonExtras.installedIn(tools, tool("ge")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anUpdateIsANewerReleaseThanTheNewestInstalledBuild() {
|
||||
assertEquals("DroidDeck-Proton-11-7.6", ProtonExtras.updateFor(listOf("DroidDeck-Proton 11-7.5"), "DroidDeck-Proton-11-7.6"))
|
||||
|
||||
@@ -70,8 +70,19 @@ def mapping(root):
|
||||
|
||||
|
||||
def tools(root):
|
||||
c = compat()
|
||||
wrapper = os.path.join(root, "compatibilitytools.d", c["TOOL"])
|
||||
complete = os.access(os.path.join(wrapper, c["LAUNCHER"]), os.X_OK) and os.path.isfile(os.path.join(wrapper, "toolmanifest.vdf"))
|
||||
if c["TOOL"].lower() not in c["local_tools"](root) or not complete:
|
||||
depot = c["find_source"](root, runnable=True)
|
||||
if depot is not None:
|
||||
try:
|
||||
c["build_tool"](wrapper, depot)
|
||||
except OSError:
|
||||
pass
|
||||
found = {}
|
||||
for base in [os.path.join(root, "compatibilitytools.d")] + list(SYSTEM_TOOLS):
|
||||
bases = [os.path.join(root, "compatibilitytools.d")] + list(SYSTEM_TOOLS)
|
||||
for base in bases:
|
||||
try:
|
||||
entries = sorted(os.listdir(base))
|
||||
except OSError:
|
||||
|
||||
@@ -1599,10 +1599,11 @@ def depot_build(depot):
|
||||
return int(first[0]) if first and first[0].isdigit() else 0
|
||||
|
||||
|
||||
def find_source(steam_root, sources=SOURCES):
|
||||
def find_source(steam_root, sources=SOURCES, runnable=False):
|
||||
for name in sources:
|
||||
found = [os.path.join(root, "steamapps", "common", name) for root in libraries(steam_root)
|
||||
if os.path.isdir(os.path.join(root, "steamapps", "common", name, "files", "bin-arm64"))]
|
||||
if os.path.isdir(os.path.join(root, "steamapps", "common", name, "files", "bin-arm64"))
|
||||
and (not runnable or os.access(os.path.join(root, "steamapps", "common", name, "proton"), os.X_OK))]
|
||||
if found:
|
||||
return max(found, key=depot_build)
|
||||
return None
|
||||
|
||||
@@ -482,6 +482,83 @@ class ProtonRunTest(Temp):
|
||||
self.assertEqual(found["droiddeck-proton-arm64"],
|
||||
(str(self.root / "compatibilitytools.d/droiddeck-proton-arm64"), "droiddeck-proton"))
|
||||
|
||||
def test_a_valve_depot_is_registered_through_the_droiddeck_wrapper(self):
|
||||
name = "Proton Experimental (ARM64)"
|
||||
folder = "steamapps/common/%s" % name
|
||||
shutil.rmtree(self.root / "compatibilitytools.d/droiddeck-proton-arm64")
|
||||
self.write("Steam/steamapps/appmanifest_4427310.acf", '"AppState" { "appid" "4427310" }')
|
||||
self.write("Steam/%s/toolmanifest.vdf" % folder, '"manifest"\n{\n "commandline" "/proton %verb%"\n}\n')
|
||||
self.write("Steam/%s/proton" % folder, b"#!/bin/sh\n", 0o755)
|
||||
self.write("Steam/%s/files/bin-arm64/proton" % folder, b"#!/bin/sh\n", 0o755)
|
||||
found = RUN["tools"](str(self.root))
|
||||
wrapper = self.root / "compatibilitytools.d/droiddeck-proton-arm64"
|
||||
self.assertEqual(found["droiddeck-proton-arm64"], (str(wrapper), "droiddeck-proton"))
|
||||
self.assertNotIn(name, found)
|
||||
|
||||
def test_an_incomplete_valve_depot_is_not_registered(self):
|
||||
name = "Proton Experimental (ARM64)"
|
||||
folder = "steamapps/common/%s" % name
|
||||
shutil.rmtree(self.root / "compatibilitytools.d/droiddeck-proton-arm64")
|
||||
self.write("Steam/steamapps/appmanifest_4427310.acf", '"AppState" { "appid" "4427310" }')
|
||||
self.write("Steam/%s/toolmanifest.vdf" % folder, '"manifest"\n{\n "commandline" "/proton %verb%"\n}\n')
|
||||
self.write("Steam/%s/files/bin-arm64/proton" % folder, b"#!/bin/sh\n", 0o755)
|
||||
found = RUN["tools"](str(self.root))
|
||||
self.assertNotIn("droiddeck-proton-arm64", found)
|
||||
self.assertFalse((self.root / "compatibilitytools.d/droiddeck-proton-arm64").exists())
|
||||
|
||||
def test_a_failed_wrapper_build_does_not_hide_existing_tools(self):
|
||||
shutil.rmtree(self.root / "compatibilitytools.d/droiddeck-proton-arm64")
|
||||
self.write("Steam/steamapps/appmanifest_4427310.acf", '"AppState" { "appid" "4427310" }')
|
||||
folder = "steamapps/common/Proton Experimental (ARM64)"
|
||||
self.write("Steam/%s/proton" % folder, b"#!/bin/sh\n", 0o755)
|
||||
self.write("Steam/%s/files/bin-arm64/proton" % folder, b"#!/bin/sh\n", 0o755)
|
||||
build = mock.Mock(side_effect=OSError("read-only"))
|
||||
with mock.patch.dict(RUN["compat"](), {"build_tool": build}):
|
||||
found = RUN["tools"](str(self.root))
|
||||
build.assert_called_once()
|
||||
self.assertIn("droiddeck-proton-11-arm64", found)
|
||||
|
||||
def test_a_runnable_depot_is_chosen_over_a_newer_incomplete_one(self):
|
||||
shutil.rmtree(self.root / "compatibilitytools.d/droiddeck-proton-arm64")
|
||||
self.write("Steam/steamapps/appmanifest_4427310.acf", '"AppState" { "appid" "4427310" }')
|
||||
good = "Steam/steamapps/common/Proton Experimental (ARM64)"
|
||||
self.write("%s/proton" % good, b"#!/bin/sh\n", 0o755)
|
||||
self.write("%s/files/bin-arm64/proton" % good, b"#!/bin/sh\n", 0o755)
|
||||
broken = "Library/steamapps/common/Proton Experimental (ARM64)"
|
||||
self.write("Library/steamapps/libraryfolders.vdf", b"")
|
||||
self.write("%s/version" % broken, b"2\n")
|
||||
self.write("%s/files/bin-arm64/proton" % broken, b"#!/bin/sh\n", 0o755)
|
||||
self.write("Steam/steamapps/libraryfolders.vdf", '"path" "%s"' % (self.tmp / "Library"))
|
||||
compat = RUN["compat"]()
|
||||
source = compat["find_source"]
|
||||
self.assertEqual(source(str(self.root), runnable=True), str(self.tmp / good))
|
||||
|
||||
def test_a_registered_wrapper_without_its_launcher_is_rebuilt(self):
|
||||
wrapper = self.root / "compatibilitytools.d/droiddeck-proton-arm64"
|
||||
self.write("Steam/steamapps/appmanifest_4427310.acf", '"AppState" { "appid" "4427310" }')
|
||||
folder = "Steam/steamapps/common/Proton Experimental (ARM64)"
|
||||
self.write("%s/proton" % folder, b"#!/bin/sh\n", 0o755)
|
||||
self.write("%s/files/bin-arm64/proton" % folder, b"#!/bin/sh\n", 0o755)
|
||||
launcher = wrapper / RUN["compat"]()["LAUNCHER"]
|
||||
if launcher.exists():
|
||||
launcher.unlink()
|
||||
found = RUN["tools"](str(self.root))
|
||||
self.assertTrue(os.access(launcher, os.X_OK))
|
||||
self.assertIn("droiddeck-proton-arm64", found)
|
||||
|
||||
def test_a_registered_wrapper_without_its_manifest_is_rebuilt(self):
|
||||
wrapper = self.root / "compatibilitytools.d/droiddeck-proton-arm64"
|
||||
self.write("Steam/steamapps/appmanifest_4427310.acf", '"AppState" { "appid" "4427310" }')
|
||||
folder = "Steam/steamapps/common/Proton Experimental (ARM64)"
|
||||
self.write("%s/proton" % folder, b"#!/bin/sh\n", 0o755)
|
||||
self.write("%s/files/bin-arm64/proton" % folder, b"#!/bin/sh\n", 0o755)
|
||||
manifest = wrapper / "toolmanifest.vdf"
|
||||
if manifest.exists():
|
||||
manifest.unlink()
|
||||
found = RUN["tools"](str(self.root))
|
||||
self.assertTrue(manifest.is_file())
|
||||
self.assertIn("droiddeck-proton-arm64", found)
|
||||
|
||||
def test_steams_choice_for_the_title_then_its_default(self):
|
||||
available = RUN["tools"](str(self.root))
|
||||
current = RUN["mapping"](str(self.root))
|
||||
|
||||
Reference in new issue
Block a user