mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
Stores: Epic overlay off by default, switches saved on disk, registry writes checked
With the overlay pointer set Metalstorm exits while loading; without it the game signs in and runs. The overlay now defaults to off: the sidecar's epic block carries a version, and a block from before it (the old default wrote overlay=true unasked) reads as off, in the app and in droiddeck-store-launch alike. The overlay is fetched only for a game that turns it on. The card's switches read the sidecar fresh, write it and read it back, and show what was read back. droiddeck-store-launch waits for a wineserver still holding the prefix before appending to user.reg - one that exits rewrites the file and drops the line - reads the value back and logs what the file holds. Its docstring is raw. The game-PATH xdg-open logs each call as scheme://host only and accepts steam://openurl.
This commit is contained in:
1 parent
1d1de10b2d
commit
a844483080
9 files changed
+165
-30
No files matched your search
@@ -60,7 +60,7 @@ class StoreGameSidecar(
|
||||
put("title", title)
|
||||
if (exe.isNotEmpty()) put("exe", exe)
|
||||
if (state != STATE_INSTALLED) put("state", state)
|
||||
if (store == Store.EPIC) put("epic", JSONObject().put("eos", epic.eos).put("offline", epic.offline).put("overlay", epic.overlay))
|
||||
if (store == Store.EPIC) put("epic", JSONObject().put("v", EpicOptions.VERSION).put("eos", epic.eos).put("offline", epic.offline).put("overlay", epic.overlay))
|
||||
if (!launcher.isNullOrEmpty()) put("launcher", launcher)
|
||||
if (args.isNotEmpty()) put("args", JSONArray(args))
|
||||
if (env.isNotEmpty()) put("env", JSONObject(env as Map<*, *>))
|
||||
@@ -87,6 +87,18 @@ class StoreGameSidecar(
|
||||
|
||||
fun file(folder: File): File = File(folder, FILE_NAME)
|
||||
|
||||
/**
|
||||
* Changes an Epic game's launch choices on disk: reads the sidecar fresh (never a copy held
|
||||
* by a screen), applies [change], writes it, and reads it back. The written sidecar, or null
|
||||
* when there is none or the write did not take. Blocking.
|
||||
*/
|
||||
fun updateEpic(folder: File, change: (EpicOptions) -> EpicOptions): StoreGameSidecar? {
|
||||
val current = read(folder) ?: return null
|
||||
val next = current.copy(epic = change(current.epic))
|
||||
next.write(folder)
|
||||
return read(folder)?.takeIf { it.epic == next.epic }
|
||||
}
|
||||
|
||||
/** The sidecar in [folder], or null when there is none or it cannot be read. */
|
||||
fun read(folder: File): StoreGameSidecar? {
|
||||
val f = file(folder)
|
||||
@@ -122,7 +134,10 @@ class StoreGameSidecar(
|
||||
extra = extra,
|
||||
state = state,
|
||||
epic = o.optJSONObject("epic")?.let { e ->
|
||||
EpicOptions(eos = e.optBoolean("eos", true), offline = e.optBoolean("offline", false), overlay = e.optBoolean("overlay", true))
|
||||
// Before v2 the overlay was on by default and written so without anyone choosing
|
||||
// it; the overlay crashed games, so those read as off. From v2 it is a choice.
|
||||
val chosen = e.optInt("v", 1) >= EpicOptions.VERSION
|
||||
EpicOptions(eos = e.optBoolean("eos", true), offline = e.optBoolean("offline", false), overlay = chosen && e.optBoolean("overlay", false))
|
||||
} ?: EpicOptions(),
|
||||
)
|
||||
}
|
||||
@@ -136,9 +151,15 @@ class StoreGameSidecar(
|
||||
/**
|
||||
* An Epic game's launch choices. [eos]: pass a fresh exchange code (Epic sign-in). [offline]: start
|
||||
* with the Epic identity arguments only, no code - for a game that will not start when sign-in fails.
|
||||
* [overlay]: point the game's prefix at Epic's EOS overlay.
|
||||
* [overlay]: point the game's prefix at Epic's EOS overlay - off by default: with it on, a game can
|
||||
* exit while loading on this stack (Metalstorm did, twice; off, it signs in and runs).
|
||||
*/
|
||||
data class EpicOptions(val eos: Boolean = true, val offline: Boolean = false, val overlay: Boolean = true) {
|
||||
data class EpicOptions(val eos: Boolean = true, val offline: Boolean = false, val overlay: Boolean = false) {
|
||||
/** Whether a launch asks for a code at all. */
|
||||
val wantsCode: Boolean get() = eos && !offline
|
||||
|
||||
companion object {
|
||||
/** v2: the overlay is off unless chosen (Metalstorm exits while loading with it on). */
|
||||
const val VERSION = 2
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,10 @@ object StoreLaunchRequests {
|
||||
val code = StoreLaunch.epicCode(app, id)
|
||||
// The overlay is placed once and shared; the launch writes the prefix's pointer.
|
||||
val overlay = com.droiddeck.launcher.stores.epic.EpicOverlay.installedVersion(app)
|
||||
Log.i(TAG, "epic overlay installed=${overlay ?: "no"} prefix=${request.optString("prefix").ifEmpty { "?" }}")
|
||||
if (overlay == null) com.droiddeck.launcher.stores.epic.EpicOverlay.ensureAsync(app)
|
||||
val wanted = StoreInstallRoot.gameFolders(app).firstNotNullOfOrNull { f -> StoreGameSidecar.read(f)?.takeIf { it.store == Store.EPIC && it.id == id } }?.epic?.overlay == true
|
||||
Log.i(TAG, "epic overlay installed=${overlay ?: "no"} wanted=$wanted prefix=${request.optString("prefix").ifEmpty { "?" }}")
|
||||
// Fetched only for a game that has the overlay on (656 MB; off by default).
|
||||
if (overlay == null && wanted) com.droiddeck.launcher.stores.epic.EpicOverlay.ensureAsync(app)
|
||||
JSONObject().put("ok", true).put("code", code.written).put("reason", code.reason).put("overlay", overlay ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,6 @@ object EpicBackend : StoreBackend, EpicLaunchSupport {
|
||||
when (result) {
|
||||
is EpicLibrary.SyncResult.Ok -> {
|
||||
StoresState.library[store] = result.games.map { EpicLibrary.toCatalogItem(app, it) }; StoresState.problems.remove(store)
|
||||
// The EOS overlay, once, so a game's first launch already has it.
|
||||
EpicOverlay.ensureAsync(app)
|
||||
}
|
||||
is EpicLibrary.SyncResult.Failed -> StoresState.problems[store] = result.message
|
||||
EpicLibrary.SyncResult.NotLoggedIn -> StoresState.problems[store] = "Epic session expired: sign in again."
|
||||
|
||||
@@ -508,6 +508,7 @@ internal fun LaunchSettings(s: FrontEndState, a: FrontEndActions, host: MenuHost
|
||||
/** The Epic card: sign-in, offline and overlay switches, read from and written to the game's sidecar. */
|
||||
@Composable
|
||||
private fun EpicLaunchCard(host: MenuHost, folder: java.io.File) {
|
||||
val appContext = androidx.compose.ui.platform.LocalContext.current.applicationContext
|
||||
var sidecar by remember(folder) { mutableStateOf(com.droiddeck.launcher.stores.StoreGameSidecar.read(folder)) }
|
||||
val options = sidecar?.epic ?: com.droiddeck.launcher.stores.EpicOptions()
|
||||
val value = buildList {
|
||||
@@ -515,19 +516,25 @@ private fun EpicLaunchCard(host: MenuHost, folder: java.io.File) {
|
||||
else if (options.eos) add(stringResource(R.string.epic_card_eos))
|
||||
if (options.overlay) add(stringResource(R.string.epic_card_overlay))
|
||||
}.ifEmpty { listOf(stringResource(R.string.epic_card_none)) }.joinToString(" · ")
|
||||
fun set(next: com.droiddeck.launcher.stores.EpicOptions) {
|
||||
val current = sidecar ?: return
|
||||
val updated = current.copy(epic = next)
|
||||
sidecar = updated
|
||||
Thread({ runCatching { updated.write(folder) } }, "epic-options").start()
|
||||
// Each switch goes straight to the sidecar on disk - both launch paths read it there - and the
|
||||
// card shows what was read back, so a write that did not take is never shown as done.
|
||||
fun set(change: (com.droiddeck.launcher.stores.EpicOptions) -> com.droiddeck.launcher.stores.EpicOptions) {
|
||||
Thread({
|
||||
val written = runCatching { com.droiddeck.launcher.stores.StoreGameSidecar.updateEpic(folder, change) }
|
||||
.onFailure { android.util.Log.w("EpicLaunchCard", "could not write ${folder.name}: ${it.message}") }.getOrNull()
|
||||
if (written != null) android.util.Log.i("EpicLaunchCard", "epic options ${written.id} eos=${written.epic.eos} offline=${written.epic.offline} overlay=${written.epic.overlay}")
|
||||
if (written?.epic?.overlay == true) com.droiddeck.launcher.stores.epic.EpicOverlay.ensureAsync(appContext)
|
||||
else android.util.Log.w("EpicLaunchCard", "epic options not saved for ${folder.name}")
|
||||
com.droiddeck.launcher.stores.StoresState.post { sidecar = written ?: com.droiddeck.launcher.stores.StoreGameSidecar.read(folder) }
|
||||
}, "epic-options").start()
|
||||
}
|
||||
SettingCard(stringResource(R.string.epic_card_title), value, "card:epic", Modifier.fillMaxSize()) {
|
||||
host.open = if (host.open == "epic") null else "epic"
|
||||
}
|
||||
AnchoredMenu(host.open == "epic", onDismiss = { if (host.open == "epic") host.open = null }, title = stringResource(R.string.epic_card_title)) { first ->
|
||||
MenuItem(stringResource(R.string.epic_eos), checked = options.eos, focusRequester = first) { set(options.copy(eos = !options.eos)) }
|
||||
MenuItem(stringResource(R.string.epic_offline), checked = options.offline) { set(options.copy(offline = !options.offline)) }
|
||||
MenuItem(stringResource(R.string.epic_overlay), checked = options.overlay) { set(options.copy(overlay = !options.overlay)) }
|
||||
MenuItem(stringResource(R.string.epic_eos), checked = options.eos, focusRequester = first) { set { it.copy(eos = !it.eos) } }
|
||||
MenuItem(stringResource(R.string.epic_offline), checked = options.offline) { set { it.copy(offline = !it.offline) } }
|
||||
MenuItem(stringResource(R.string.epic_overlay), checked = options.overlay) { set { it.copy(overlay = !it.overlay) } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,14 +47,34 @@ class StoreGameSidecarTest {
|
||||
|
||||
@Test fun epicLaunchChoicesRoundTripAndDefaultOn() {
|
||||
val plain = StoreGameSidecar.parse(StoreGameSidecar(Store.EPIC, "m", "Metalstorm", "M.exe").toJson().toString())!!
|
||||
assertEquals(EpicOptions(eos = true, offline = false, overlay = true), plain.epic)
|
||||
assertEquals(EpicOptions(eos = true, offline = false, overlay = false), plain.epic)
|
||||
assertTrue(plain.epic.wantsCode)
|
||||
val set = StoreGameSidecar(Store.EPIC, "m", "Metalstorm", "M.exe", epic = EpicOptions(eos = true, offline = true, overlay = false))
|
||||
val back = StoreGameSidecar.parse(set.toJson().toString())!!
|
||||
assertEquals(EpicOptions(eos = true, offline = true, overlay = false), back.epic)
|
||||
assertFalse(back.epic.wantsCode)
|
||||
// A sidecar from before the choices existed reads as all on.
|
||||
// A sidecar from before the choices existed reads as the defaults: sign-in on, overlay off.
|
||||
assertEquals(EpicOptions(), StoreGameSidecar.parse("""{"store":"epic","id":"m","title":"M","exe":"M.exe"}""")!!.epic)
|
||||
// The old default wrote overlay=true without a version: nobody chose it, so it reads as off.
|
||||
assertFalse(StoreGameSidecar.parse("""{"store":"epic","id":"m","title":"M","exe":"M.exe","epic":{"eos":true,"offline":false,"overlay":true}}""")!!.epic.overlay)
|
||||
// Chosen from v2 on, it stays.
|
||||
val on = StoreGameSidecar.parse(StoreGameSidecar(Store.EPIC, "m", "M", "M.exe", epic = EpicOptions(overlay = true)).toJson().toString())!!
|
||||
assertTrue(on.epic.overlay)
|
||||
}
|
||||
|
||||
@Test fun aSwitchOnTheEpicCardChangesTheSidecarOnDisk() {
|
||||
val folder = java.io.File(org.robolectric.RuntimeEnvironment.getApplication().filesDir, "Games/Stores/Epic/Metalstorm").apply { mkdirs() }
|
||||
StoreGameSidecar(Store.EPIC, "m", "Metalstorm", "Metalstorm.exe", launcher = ".droiddeck-launch.bat").write(folder)
|
||||
val written = StoreGameSidecar.updateEpic(folder) { it.copy(overlay = true) }!!
|
||||
assertTrue(written.epic.overlay)
|
||||
// What the Steam-launch helper reads: the file itself.
|
||||
val onDisk = org.json.JSONObject(java.io.File(folder, StoreGameSidecar.FILE_NAME).readText())
|
||||
assertTrue(onDisk.getJSONObject("epic").getBoolean("overlay"))
|
||||
assertEquals(EpicOptions.VERSION, onDisk.getJSONObject("epic").getInt("v"))
|
||||
assertTrue(onDisk.getJSONObject("epic").getBoolean("eos"))
|
||||
// Everything else in it stays.
|
||||
assertEquals(".droiddeck-launch.bat", StoreGameSidecar.read(folder)!!.launcher)
|
||||
assertEquals(null, StoreGameSidecar.updateEpic(java.io.File(folder, "missing")) { it })
|
||||
}
|
||||
|
||||
@Test fun anInstallUnderWayHasNoExeYetAndIsNotInstalled() {
|
||||
|
||||
@@ -144,6 +144,24 @@ without it and Setup says "Stores engine not built").
|
||||
(`token`, `__token__`, `f_token`, `hdnts`, access/refresh/id tokens, `code=`) and Authorization /
|
||||
Cookie headers are blanked outside URLs too. `StoreLogTest` covers a GOG secure link and an Epic
|
||||
Akamai URL. The engine's own logcat lines (tag `EpicNative` etc.) are written on the native side.
|
||||
- **Epic overlay off by default** (device A/B, Metalstorm: overlay pointer set → exits while
|
||||
loading, twice; off → signs in to its Starform account and runs at 118 fps). `EpicOptions.overlay`
|
||||
defaults to false; the sidecar's `epic` block carries `"v": 2`, and an older block (the old default
|
||||
wrote `"overlay": true` unasked) reads as off - in the app and in `droiddeck-store-launch` alike.
|
||||
The 656 MB overlay is fetched only for a game that turns it on (the card's switch, or a launch of
|
||||
such a game), no longer after every library sync.
|
||||
- **The card's switches are saved where both launch paths read them:** each toggle reads the
|
||||
sidecar fresh from disk, writes it and reads it back (`StoreGameSidecar.updateEpic`), and the card
|
||||
shows what was read back; the app logs `epic options <id> eos= offline= overlay=`.
|
||||
- **Registry lines that a running wineserver would drop:** `droiddeck-store-launch` waits up to 8 s
|
||||
for a wineserver still holding the prefix to exit before appending to `user.reg` (it rewrites the
|
||||
file from memory when it exits, which is how `OverlayPath` vanished), reads the value back, and
|
||||
logs `overlay-set`, `overlay-off`, `overlay-not-written` or `overlay-still-set`, plus
|
||||
`server-running` when one never left. The helper's docstring is raw (no SyntaxWarning).
|
||||
- **Browser hand-off diagnostics:** the game-PATH `xdg-open` logs every call as `scheme://host`
|
||||
only and accepts `steam://openurl/<address>`. On the device it was never called while EOS waited
|
||||
on its browser flow, so how EOS opens the browser under this Proton is still to be seen in the
|
||||
next log.
|
||||
- **The exchange code no longer reaches the game's environment** (device: Metalstorm's Player.log
|
||||
dumped `DD_AUTH: … -AUTH_PASSWORD=<code>`). The launcher read the code into a variable the game
|
||||
inherited. It now clears the variable on the same line that starts the game - cmd has expanded it
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/python3
|
||||
"""Before Proton starts a store game, asks the app for what that store needs at launch.
|
||||
r"""Before Proton starts a store game, asks the app for what that store needs at launch.
|
||||
|
||||
Run by droiddeck-proton / droiddeck-proton-wrap (steam-compatibility) for every real game launch,
|
||||
whoever started it: the Games tab, a pinned shortcut, or the Steam client's own Play button. It
|
||||
@@ -39,6 +39,7 @@ EPIC_CODE = ".droiddeck-epic-code"
|
||||
TIMEOUT = 5.0
|
||||
STALE_SECONDS = 300
|
||||
OVERLAY_DIR = "/root/.local/share/droiddeck/epic-overlay"
|
||||
SERVER_WAIT = 8.0
|
||||
OVERLAY_DLL = "EOSOVH-Win64-Shipping.dll"
|
||||
EOS_KEY = "[Software\\\\Epic Games\\\\EOS]"
|
||||
BROWSER_KEY = "[Software\\\\Wine\\\\WineBrowser]"
|
||||
@@ -171,24 +172,74 @@ def remove_value(reg, key, name):
|
||||
def options(sidecar):
|
||||
"""An Epic game's launch choices from its sidecar, as the Games tab's Epic card sets them."""
|
||||
e = sidecar.get("epic") if isinstance(sidecar.get("epic"), dict) else {}
|
||||
return {"eos": e.get("eos", True) is not False, "offline": e.get("offline", False) is True, "overlay": e.get("overlay", True) is not False}
|
||||
# The overlay is off unless chosen (v2 on): the old default wrote overlay=true without a version,
|
||||
# and with it on a game could exit while loading. The app's EpicOptions reads it the same way.
|
||||
chosen = isinstance(e.get("v"), int) and e.get("v") >= 2
|
||||
return {"eos": e.get("eos", True) is not False, "offline": e.get("offline", False) is True, "overlay": chosen and e.get("overlay") is True}
|
||||
|
||||
|
||||
def provision_prefix(compat_data, overlay_dir=OVERLAY_DIR, overlay=True):
|
||||
def server_socket(pfx):
|
||||
"""The wineserver socket for prefix [pfx], as Wine names it (server-<dev>-<inode> under /tmp/.wine-<uid>)."""
|
||||
try:
|
||||
st = os.stat(pfx)
|
||||
except OSError:
|
||||
return None
|
||||
base = os.environ.get("BL_WINE_SERVER_BASE") or "/tmp/.wine-%d" % os.getuid()
|
||||
return Path(base) / ("server-%x-%x" % (st.st_dev, st.st_ino)) / "socket"
|
||||
|
||||
|
||||
def wait_for_server(pfx, timeout=SERVER_WAIT):
|
||||
"""
|
||||
Waits for a wineserver still holding this prefix (an earlier verb's, or the last launch's) to
|
||||
exit: it rewrites user.reg from its own memory when it does, which drops a line appended
|
||||
meanwhile. True when none is left.
|
||||
"""
|
||||
sock = server_socket(pfx)
|
||||
if sock is None:
|
||||
return True
|
||||
deadline = time.monotonic() + timeout
|
||||
while sock.exists() and time.monotonic() < deadline:
|
||||
time.sleep(0.1)
|
||||
return not sock.exists()
|
||||
|
||||
|
||||
def has_value(reg, key, name, value=None):
|
||||
"""Whether the last word on `key` in user.reg sets "name" (to [value], when given)."""
|
||||
if not reg.is_file():
|
||||
return False
|
||||
inside, last = False, None
|
||||
for row in reg.read_text(errors="replace").splitlines():
|
||||
if row.startswith("["):
|
||||
inside = row.startswith(key + " ") or row == key
|
||||
elif inside and row.startswith('"%s"=' % name):
|
||||
last = row
|
||||
if last is None:
|
||||
return False
|
||||
return value is None or last == '"%s"="%s"' % (name, reg_escape(value))
|
||||
|
||||
|
||||
def provision_prefix(compat_data, overlay_dir=OVERLAY_DIR, overlay=False, wait=SERVER_WAIT):
|
||||
"""The EOS overlay pointer (or its removal, when [overlay] is off) and Wine's browser, in the game's prefix."""
|
||||
if not compat_data:
|
||||
return "no-prefix"
|
||||
reg = Path(compat_data) / "pfx" / "user.reg"
|
||||
pfx = Path(compat_data) / "pfx"
|
||||
reg = pfx / "user.reg"
|
||||
if not reg.is_file():
|
||||
return "prefix-not-created"
|
||||
# A wineserver still up for this prefix would write its own copy of user.reg over these lines.
|
||||
busy = not wait_for_server(pfx, wait)
|
||||
set_value(reg, BROWSER_KEY, "Browsers", "xdg-open")
|
||||
if not overlay:
|
||||
remove_value(reg, EOS_KEY, "OverlayPath")
|
||||
return "overlay-off"
|
||||
if overlay_version(overlay_dir) is None:
|
||||
state = "overlay-off" if not has_value(reg, EOS_KEY, "OverlayPath") else "overlay-still-set"
|
||||
elif overlay_version(overlay_dir) is None:
|
||||
return "overlay-missing"
|
||||
set_value(reg, EOS_KEY, "OverlayPath", windows_path(overlay_dir))
|
||||
return "overlay-set"
|
||||
else:
|
||||
target = windows_path(overlay_dir)
|
||||
set_value(reg, EOS_KEY, "OverlayPath", target)
|
||||
# Read back, so the log says what the file holds, not what was meant.
|
||||
state = "overlay-set" if has_value(reg, EOS_KEY, "OverlayPath", target) else "overlay-not-written"
|
||||
return state + (" server-running" if busy else "")
|
||||
|
||||
|
||||
def main(argv):
|
||||
|
||||
@@ -20,9 +20,23 @@ def hand_over(url, launch_dir):
|
||||
os.rename(tmp, req / (rid + ".json"))
|
||||
|
||||
|
||||
def where(url):
|
||||
"""scheme://host only, for the log: no path, no query."""
|
||||
from urllib.parse import urlsplit
|
||||
try:
|
||||
u = urlsplit(url)
|
||||
return "%s://%s" % (u.scheme, u.hostname or "")
|
||||
except ValueError:
|
||||
return "?"
|
||||
|
||||
|
||||
def main(argv):
|
||||
url = argv[0] if argv else ""
|
||||
# The Steam client's own form (steam://openurl/<address>), in case a launcher passes it on.
|
||||
if url.startswith("steam://openurl/"):
|
||||
url = url[len("steam://openurl/"):]
|
||||
launch_dir = os.environ.get("BL_LAUNCH_DIR", "")
|
||||
print("droiddeck xdg-open: %s" % where(url), file=sys.stderr, flush=True)
|
||||
if url.startswith(("https://", "http://")) and launch_dir:
|
||||
hand_over(url, launch_dir)
|
||||
return 0
|
||||
|
||||
@@ -128,18 +128,18 @@ class Prefix(unittest.TestCase):
|
||||
def test_the_overlay_pointer_names_the_shared_copy_through_z(self):
|
||||
(self.overlay / store.OVERLAY_DLL).write_bytes(b'MZ')
|
||||
(self.overlay / 'droiddeck-version').write_text('1.0.42')
|
||||
self.assertEqual('overlay-set', store.provision_prefix(str(self.compat), str(self.overlay)))
|
||||
self.assertEqual('overlay-set', store.provision_prefix(str(self.compat), str(self.overlay), overlay=True))
|
||||
text = self.reg.read_text()
|
||||
self.assertIn('[Software\\\\Epic Games\\\\EOS] ', text)
|
||||
self.assertIn('"OverlayPath"="Z:' + str(self.overlay).replace('/', '\\\\') + '"', text)
|
||||
self.assertIn('"Browsers"="xdg-open"', text)
|
||||
# A second launch adds nothing: the last word already says so.
|
||||
store.provision_prefix(str(self.compat), str(self.overlay))
|
||||
store.provision_prefix(str(self.compat), str(self.overlay), overlay=True)
|
||||
self.assertEqual(text, self.reg.read_text())
|
||||
self.assertEqual('1.0.42', store.overlay_version(str(self.overlay)))
|
||||
|
||||
def test_no_overlay_yet_or_no_prefix_yet(self):
|
||||
self.assertEqual('overlay-missing', store.provision_prefix(str(self.compat), str(self.overlay)))
|
||||
self.assertEqual('overlay-missing', store.provision_prefix(str(self.compat), str(self.overlay), overlay=True))
|
||||
self.assertNotIn('OverlayPath', self.reg.read_text())
|
||||
self.assertEqual('prefix-not-created', store.provision_prefix(self.tmp.name + '/compatdata/1', str(self.overlay)))
|
||||
self.assertEqual('no-prefix', store.provision_prefix('', str(self.overlay)))
|
||||
@@ -150,7 +150,10 @@ class Choices(unittest.TestCase):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
folder = game(root)
|
||||
sidecar = json.loads((folder / '.droiddeck-store.json').read_text())
|
||||
self.assertEqual({'eos': True, 'offline': False, 'overlay': True}, store.options(sidecar))
|
||||
self.assertEqual({'eos': True, 'offline': False, 'overlay': False}, store.options(sidecar))
|
||||
# The old default (no version) never turns it on; a v2 choice does.
|
||||
self.assertFalse(store.options({'epic': {'eos': True, 'offline': False, 'overlay': True}})['overlay'])
|
||||
self.assertTrue(store.options({'epic': {'v': 2, 'overlay': True}})['overlay'])
|
||||
sidecar['epic'] = {'eos': True, 'offline': True, 'overlay': False}
|
||||
(folder / '.droiddeck-store.json').write_text(json.dumps(sidecar))
|
||||
(folder / '.droiddeck-epic-code').write_text('left over')
|
||||
@@ -185,6 +188,7 @@ class Browser(unittest.TestCase):
|
||||
reqs = list((Path(root) / 'stores/req').glob('*.json'))
|
||||
self.assertEqual(1, len(reqs))
|
||||
self.assertEqual({'op': 'open-url', 'url': 'https://www.epicgames.com/activate'}, json.loads(reqs[0].read_text()))
|
||||
self.assertEqual('https://www.epicgames.com', xdg.where('https://www.epicgames.com/activate?code=SECRET'))
|
||||
|
||||
|
||||
class Launchers(unittest.TestCase):
|
||||
|
||||
Reference in new issue
Block a user