mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Stores: the Epic code reaches the game on its command line only
Metalstorm's Player.log printed its environment, and with it the exchange code: the launcher read the code into a variable the game inherited. The variable (now with a plain name) is cleared on the same line that starts the game - cmd has expanded it there already - so the game gets the code as arguments and nothing of it in its environment. A launcher from an earlier build is rewritten before the next Epic launch, from the app and from the Steam-launch request alike.
This commit is contained in:
1 parent
7ab0c12c97
commit
1d1de10b2d
3 files changed
+51
-14
No files matched your search
@@ -44,16 +44,23 @@ object StoreLaunch {
|
||||
lines += "rem Written by DroidDeck for ${sidecar.store.label}: ${sidecar.title}. Steam's shortcut runs this file."
|
||||
lines += "cd /d \"%~dp0$dir\""
|
||||
for ((k, v) in sidecar.env) if (ENV_NAME.matches(k)) lines += "set \"$k=${v.replace("\"", "")}\""
|
||||
lines += "set \"DD_AUTH=\""
|
||||
if (sidecar.store == Store.EPIC) {
|
||||
lines += "if not exist \"%~dp0$EPIC_CODE\" goto run"
|
||||
lines += "set /p DD_CODE=<\"%~dp0$EPIC_CODE\""
|
||||
lines += "del \"%~dp0$EPIC_CODE\""
|
||||
lines += "set \"DD_AUTH=-AUTH_LOGIN=unused -AUTH_PASSWORD=%DD_CODE% -AUTH_TYPE=exchangecode\""
|
||||
lines += ":run"
|
||||
}
|
||||
val args = sidecar.args.joinToString(" ") { quoteArg(it) }
|
||||
lines += "\"%~dp0$exe\"" + (if (args.isNotEmpty()) " $args" else "") + " %DD_AUTH%"
|
||||
val run = "\"%~dp0$exe\"" + (if (args.isNotEmpty()) " $args" else "")
|
||||
if (sidecar.store == Store.EPIC) {
|
||||
// The one-shot code reaches the game on its command line only. cmd expands %DD_X% when it
|
||||
// parses the last line, so the variable is cleared on that same line before the game
|
||||
// starts: nothing of it is left in the environment the game inherits (and dumps).
|
||||
lines += "set \"$CODE_VAR=\""
|
||||
lines += "if exist \"%~dp0$EPIC_CODE\" set /p $CODE_VAR=<\"%~dp0$EPIC_CODE\""
|
||||
lines += "if exist \"%~dp0$EPIC_CODE\" del \"%~dp0$EPIC_CODE\""
|
||||
lines += "if defined $CODE_VAR goto signed"
|
||||
lines += run
|
||||
lines += "goto :eof"
|
||||
lines += ":signed"
|
||||
lines += "set \"$CODE_VAR=\" & $run -AUTH_LOGIN=unused -AUTH_PASSWORD=%$CODE_VAR% -AUTH_TYPE=exchangecode"
|
||||
} else {
|
||||
lines += run
|
||||
}
|
||||
return lines.joinToString("\r\n") + "\r\n"
|
||||
}
|
||||
|
||||
@@ -70,6 +77,23 @@ object StoreLaunch {
|
||||
* fresh exchange code written beside its launcher (a few seconds at most; the launch goes
|
||||
* ahead without one when the store cannot be reached). Other stores need nothing.
|
||||
*/
|
||||
/** The launcher's variable for the one-shot code; a plain name, cleared before the game starts. */
|
||||
private const val CODE_VAR = "DD_X"
|
||||
|
||||
/**
|
||||
* Rewrites a game's launcher when its text is not what this build writes - an install from an
|
||||
* earlier build keeps its old .bat otherwise. Called before every Epic launch.
|
||||
*/
|
||||
fun refreshLauncher(folder: File, sidecar: StoreGameSidecar) {
|
||||
if (sidecar.launcher != LAUNCHER) return
|
||||
val file = File(folder, LAUNCHER)
|
||||
val text = launcherText(sidecar)
|
||||
if (runCatching { file.readText() }.getOrNull() != text) {
|
||||
file.writeText(text)
|
||||
Log.i(TAG, "launcher rewritten for ${sidecar.id}")
|
||||
}
|
||||
}
|
||||
|
||||
fun prepare(context: Context, store: Store, id: String, onReady: () -> Unit) {
|
||||
if (store != Store.EPIC) { onReady(); return }
|
||||
val app = context.applicationContext
|
||||
@@ -93,7 +117,9 @@ object StoreLaunch {
|
||||
val app = context.applicationContext
|
||||
val result = try {
|
||||
val folder = StoreInstallRoot.gameFolders(app).firstOrNull { f -> StoreGameSidecar.read(f)?.let { it.store == Store.EPIC && it.id == id } == true }
|
||||
val options = folder?.let { StoreGameSidecar.read(it)?.epic }
|
||||
val sidecar = folder?.let { StoreGameSidecar.read(it) }
|
||||
sidecar?.let { refreshLauncher(folder, it) }
|
||||
val options = sidecar?.epic
|
||||
if (folder == null) CodeResult(false, "not-installed")
|
||||
else if (options != null && !options.wantsCode) {
|
||||
File(folder, EPIC_CODE).delete()
|
||||
|
||||
@@ -88,17 +88,22 @@ class StoreGameSidecarTest {
|
||||
val text = StoreLaunch.launcherText(sample.copy(exe = "Binaries/Win64/Celeste.exe"))
|
||||
assertTrue(text.contains("cd /d \"%~dp0Binaries\\Win64\"\r\n"))
|
||||
assertTrue(text.contains("set \"FOO=bar\"\r\n"))
|
||||
assertTrue(text.contains("\"%~dp0Binaries\\Win64\\Celeste.exe\" -EpicPortal -epicusername=\"Some One\" %DD_AUTH%\r\n"))
|
||||
assertTrue(text.contains("\r\n\"%~dp0Binaries\\Win64\\Celeste.exe\" -EpicPortal -epicusername=\"Some One\"\r\n"))
|
||||
// Epic reads the one-shot exchange code the app leaves beside the launcher, then deletes it.
|
||||
assertTrue(text.contains("set /p DD_CODE=<\"%~dp0.droiddeck-epic-code\""))
|
||||
assertTrue(text.contains("-AUTH_TYPE=exchangecode"))
|
||||
assertTrue(text.contains("set /p DD_X=<\"%~dp0.droiddeck-epic-code\""))
|
||||
// The code goes on the command line only: the variable is cleared on the very line that
|
||||
// starts the game (cmd has already expanded it there), so the game inherits none of it.
|
||||
assertTrue(text.contains("set \"DD_X=\" & \"%~dp0Binaries\\Win64\\Celeste.exe\" -EpicPortal -epicusername=\"Some One\" -AUTH_LOGIN=unused -AUTH_PASSWORD=%DD_X% -AUTH_TYPE=exchangecode\r\n"))
|
||||
assertFalse(text.contains("AUTH="))
|
||||
assertFalse(text.contains("DD_AUTH"))
|
||||
}
|
||||
|
||||
@Test fun gogLauncherHasNoEpicBlockAndIsOnlyWrittenWhenNeeded() {
|
||||
val plain = StoreGameSidecar(Store.GOG, "1", "T", "game.exe")
|
||||
val text = StoreLaunch.launcherText(plain.copy(args = listOf("-windowed")))
|
||||
assertFalse(text.contains("exchangecode"))
|
||||
assertTrue(text.contains("\"%~dp0game.exe\" -windowed %DD_AUTH%"))
|
||||
assertTrue(text.contains("\"%~dp0game.exe\" -windowed\r\n"))
|
||||
assertFalse(text.contains("DD_X"))
|
||||
assertEquals("\"two words\"", StoreLaunch.quoteArg("two words"))
|
||||
assertEquals("-x=\"a b\"", StoreLaunch.quoteArg("-x=\"a b\""))
|
||||
}
|
||||
|
||||
@@ -144,6 +144,12 @@ 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.
|
||||
- **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
|
||||
there already - so the code is on the command line only; the variable has a plain name. An old
|
||||
launcher is rewritten before the next Epic launch (`StoreLaunch.refreshLauncher`, from both the
|
||||
app and the Steam-launch request).
|
||||
- **A store game page shows no description that is only its title again** (Metalstorm's).
|
||||
- **Epic launch card** (Games tab › Launch settings, Epic games only): "Epic sign-in (EOS)" on,
|
||||
"Launch offline" off, "Epic overlay" on, stored in the game's sidecar (`"epic": {"eos", "offline",
|
||||
|
||||
Reference in new issue
Block a user