Commit e6bab2a4 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Route mods' Google Play sign-in requests to this launcher

Minecraft 1.26.50+ runs only with the mcpelauncher-updates mod, which asks
for the Google Play sign-in when the game starts. The engine looked for a
binary named mcpelauncher-ui-qt, which this app does not have, so the mod got
no credentials and the game crashed during startup.

- Engine patch: MCPELAUNCHER_UI_EXECUTABLE names the credentials helper; the
  launcher sets it to itself when it starts the game.
- The request window now has explicit Allow and Deny buttons, shows which mod
  is asking and for which account, and explains what to do when signed out
  instead of failing on a missing account.
- Play asks for Google sign-in first when not signed in (trial mode aside),
  including for versions installed by another launcher.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 7855d8b9
...@@ -43,6 +43,11 @@ right-click > Open (or System Settings > Privacy & Security > Open Anyway). ...@@ -43,6 +43,11 @@ right-click > Open (or System Settings > Privacy & Security > Open Anyway).
Sign in with the Google account that bought Minecraft on Google Play. The Sign in with the Google account that bought Minecraft on Google Play. The
sign-in is stored encrypted on disk; its key lives in the macOS Keychain. sign-in is stored encrypted on disk; its key lives in the macOS Keychain.
Minecraft 1.26.50 and newer also needs the `mcpelauncher-updates` mod, which the
launcher offers to turn on. Each time the game starts, the mod asks to use your
Google Play sign-in to confirm you own the game; the launcher shows that request
and the game only starts once you allow it.
**Windows**: run `AL-Arcade-MC-Launcher-<version>-windows-x64-setup.exe` (per-user **Windows**: run `AL-Arcade-MC-Launcher-<version>-windows-x64-setup.exe` (per-user
install, no administrator rights needed) or unpack the portable `.zip`. The install, no administrator rights needed) or unpack the portable `.zip`. The
installer is not code-signed yet, so SmartScreen asks for More info > Run anyway. installer is not code-signed yet, so SmartScreen asks for More info > Run anyway.
...@@ -140,6 +145,7 @@ overrides the built-in URL for testing. ...@@ -140,6 +145,7 @@ overrides the built-in URL for testing.
| Patch | Fixes | | Patch | Fixes |
|---|---| |---|---|
| `mcpelauncher-core/0001-arm64-step-over-debug-breaks` | Debug break instructions in the game crashed or hung it on Apple Silicon; they are now stepped over | | `mcpelauncher-core/0001-arm64-step-over-debug-breaks` | Debug break instructions in the game crashed or hung it on Apple Silicon; they are now stepped over |
| `mcpelauncher-core/0002-ui-executable-from-environment` | Mods that ask for the Google Play sign-in reach this launcher (the engine looked for a binary named `mcpelauncher-ui-qt`) |
| `mcpelauncher-client/0001-ignore-sigpipe` | The game quit when a network connection dropped (for example with a VPN) | | `mcpelauncher-client/0001-ignore-sigpipe` | The game quit when a network connection dropped (for example with a VPN) |
| `mcpelauncher-client/0002-macos-fullscreen-shortcut` | Ctrl+Cmd+F toggles fullscreen; F11 is taken by macOS | | `mcpelauncher-client/0002-macos-fullscreen-shortcut` | Ctrl+Cmd+F toggles fullscreen; F11 is taken by macOS |
| `game-window/0001-optional-non-retina-rendering` | Setting to render at half resolution on Retina screens for more frames per second | | `game-window/0001-optional-non-retina-rendering` | Setting to render at half resolution on Retina screens for more frames per second |
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#include "launchersettings.h" #include "launchersettings.h"
#include "profilemanager.h" #include "profilemanager.h"
#include "EnvPathUtil.h" #include "EnvPathUtil.h"
#include <QCoreApplication>
#include <QFile> #include <QFile>
#include <QDir> #include <QDir>
#include "supportedandroidabis.h" #include "supportedandroidabis.h"
...@@ -32,6 +33,9 @@ void GameLauncher::start(bool disableGameLog, QString arch, bool hasVerifiedLice ...@@ -32,6 +33,9 @@ void GameLauncher::start(bool disableGameLog, QString arch, bool hasVerifiedLice
QStringList args; QStringList args;
QStringList cargs; QStringList cargs;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
// Mods ask for Google Play credentials through the launcher; tell the engine
// where it is (patches/engine/mcpelauncher-core/0002).
env.insert("MCPELAUNCHER_UI_EXECUTABLE", QCoreApplication::applicationFilePath());
#ifdef __APPLE__ #ifdef __APPLE__
if (!LauncherSettings().retinaRendering()) if (!LauncherSettings().retinaRendering())
env.insert("ALARCADE_RETINA", "0"); env.insert("ALARCADE_RETINA", "0");
......
...@@ -59,6 +59,10 @@ Item { ...@@ -59,6 +59,10 @@ Item {
} }
readonly property bool statusBlocking: !!statusMsg.title && statusMsg.tone !== "success" readonly property bool statusBlocking: !!statusMsg.title && statusMsg.tone !== "success"
readonly property bool signedIn: googleLoginHelper.account !== null readonly property bool signedIn: googleLoginHelper.account !== null
// Owning the game is checked through Google Play, and the compatibility mod
// for new game versions asks for the sign-in at start, so playing the full
// game needs it even when the version was installed earlier.
readonly property bool needsSignInToPlay: !signedIn && !launcherSettings.trialMode
// "1.26.52.3 (arm64-v8a, beta)" -> title "1.26.52.3", details "arm64-v8a, beta" // "1.26.52.3 (arm64-v8a, beta)" -> title "1.26.52.3", details "arm64-v8a, beta"
readonly property string versionTitle: activeVersionName.split(" (")[0] readonly property string versionTitle: activeVersionName.split(" (")[0]
readonly property string versionDetails: { readonly property string versionDetails: {
...@@ -179,10 +183,16 @@ Item { ...@@ -179,10 +183,16 @@ Item {
text: { text: {
if (playScreen.statusChecking || !playScreen.activeVersionName) if (playScreen.statusChecking || !playScreen.activeVersionName)
return "" return ""
if (playScreen.needsSignInToPlay)
return qsTr("Sign in to play").toUpperCase()
return (playScreen.activeVersionNeedsDownload ? qsTr("Download and play") : qsTr("Play")).toUpperCase() return (playScreen.activeVersionNeedsDownload ? qsTr("Download and play") : qsTr("Play")).toUpperCase()
} }
enabled: !(gameLauncher.running || playScreen.statusChecking || progressbarVisible || updateChecker.active || !playScreen.activeVersionSupported || !playScreen.activeVersionName || playScreen.statusBlocking) enabled: !(gameLauncher.running || playScreen.statusChecking || progressbarVisible || updateChecker.active || !playScreen.activeVersionSupported || !playScreen.activeVersionName || playScreen.statusBlocking)
onClicked: { onClicked: {
if (playScreen.needsSignInToPlay) {
actionSignIn()
return
}
if (playScreen.activeVersionNeedsDownload) { if (playScreen.activeVersionNeedsDownload) {
playDownloadTask.versionCode = getDownloadVersionCode() playDownloadTask.versionCode = getDownloadVersionCode()
if (playDownloadTask.versionCode === 0) if (playDownloadTask.versionCode === 0)
......
import QtQuick import QtQuick
import QtQuick.Window import QtQuick.Window
import QtQuick.Dialogs import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Qt.labs.platform
import io.mrarm.mcpelauncher 1.0 import io.mrarm.mcpelauncher 1.0
import "../Components" import "../Components"
import ".."
// Started by the game engine (--request-google-credentials --mod <path>) when a
// mod asks for the Google Play sign-in, before the game window opens. Allowing
// prints CRED=<email>:<token> on stderr and exits 0; anything else exits 1.
Window { Window {
id: window id: window
visible: true visible: true
width: 640 width: 560
height: 480 height: 500
title: qsTr("Request Google Credentials") minimumWidth: 460
minimumHeight: 440
title: qsTr("%1: Google Play sign-in").arg(APP_NAME)
color: Theme.background color: Theme.background
flags: Qt.Window | Qt.WindowStaysOnTopHint
// Read by the upstream screens shown here.
property bool hasUpdate: false property bool hasUpdate: false
property bool hasAskedForKey: false
property string updateDownloadUrl: "" property string updateDownloadUrl: ""
property bool isVersionsInitialized: false
property string currentGameDataDir: "" property string currentGameDataDir: ""
StackView { property bool answered: false
id: stackView property bool unlocking: googleLoginHelperInstance.hasEncryptedCredentials && googleLoginHelperInstance.account === null
anchors.fill: parent readonly property var account: googleLoginHelperInstance.account
readonly property string mode: !googleLoginHelperInstance.isSupported() ? "unsupported" : account === null ? "signedOut" : "ask"
readonly property string modName: {
const parts = SOURCE_MOD.split("/").filter(part => part.length > 0)
const mods = parts.lastIndexOf("mods")
if (mods >= 0 && mods + 1 < parts.length)
return parts[mods + 1]
const file = parts.length > 0 ? parts[parts.length - 1] : ""
return file.replace(/^lib/, "").replace(/\.so$/, "") || qsTr("A mod")
} }
GoogleLoginHelper { readonly property string maskedEmail: {
id: googleLoginHelperInstance if (account === null)
includeIncompatible: launcherSettings.showUnsupported return ""
singleArch: launcherSettings.singleArch const mail = account.accountIdentifier
chromeOS: launcherSettings.chromeOSMode || launcherSettings.trialMode const at = mail.indexOf("@")
if (mail.length < 4)
return mail
return mail.substring(0, 2) + "*".repeat(Math.max(3, (at > 2 ? at : mail.length) - 2)) + (at > 0 ? mail.substring(at) : "")
} }
Component { function allow() {
id: panelError answered = true
ErrorScreen { stdio.error("CRED=" + account.accountIdentifier + ":" + account.accountToken)
onFinished: defaultnext() stdio.error("CREDB64=" + Qt.btoa(JSON.stringify(account)))
} application.exit(0)
} }
StdioHelper { function deny() {
id: stdio answered = true
application.exit(1)
} }
Component { onClosing: {
id: panelDefault if (!answered)
ErrorScreen { deny()
message: qsTr("A mod at '%1'<br/>is requesting access to your google credentials,<br/>to reject this request close this window").arg(SOURCE_MOD)
onFinished: {
stdio.error("CRED=" + googleLoginHelperInstance.account.accountIdentifier + ":" + googleLoginHelperInstance.account.accountToken)
stdio.error("CREDB64=" + Qt.btoa(JSON.stringify(googleLoginHelperInstance.account)))
window.close()
application.quit()
}
}
} }
Component { Component.onCompleted: {
id: panelUnlock raise()
UnlockScreen { requestActivate()
onFinished: {
next()
}
}
} }
LauncherSettings { LauncherSettings {
id: launcherSettings id: launcherSettings
} }
Connections { StdioHelper {
target: window id: stdio
function onClosing() {
application.quit()
}
}
function needsToLogIn() {
return googleLoginHelperInstance.account == null && !googleLoginHelperInstance.hasEncryptedCredentials && versionManagerInstance.versions.size === 0
} }
Component.onCompleted: { GoogleLoginHelper {
next() id: googleLoginHelperInstance
includeIncompatible: launcherSettings.showUnsupported
singleArch: launcherSettings.singleArch
chromeOS: launcherSettings.chromeOSMode || launcherSettings.trialMode
} }
function next() { // Credentials protected by a password (older launcher versions) need it first.
if (!googleLoginHelperInstance.isSupported()) { Loader {
stackView.push(panelError) anchors.fill: parent
} else if (googleLoginHelperInstance.hasEncryptedCredentials && !hasAskedForKey) { active: window.unlocking
hasAskedForKey = true sourceComponent: UnlockScreen {
stackView.push(panelUnlock) onFinished: window.unlocking = false
} else {
defaultnext()
} }
} }
function defaultnext() { Card {
stackView.push(panelDefault) anchors.centerIn: parent
width: Math.min(parent.width - 2 * Theme.space6, 480)
padding: Theme.space6
visible: !window.unlocking
ColumnLayout {
width: parent.width
spacing: Theme.space4
Rectangle {
Layout.alignment: Qt.AlignHCenter
implicitWidth: 52
implicitHeight: 52
radius: Theme.radiusLarge
color: window.mode === "ask" ? Theme.accentSoft : Theme.warningSoft
AppIcon {
anchors.centerIn: parent
iconName: window.mode === "ask" ? "shield-check" : "triangle-alert"
size: 24
color: window.mode === "ask" ? Theme.accentText : Theme.warning
}
}
Text {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
color: Theme.text
font.pixelSize: Theme.fontHeading + 2
font.weight: Font.Bold
text: window.mode === "ask" ? qsTr("Allow %1 to use your Google Play sign-in?").arg(window.modName)
: window.mode === "signedOut" ? qsTr("Sign in to Google Play first")
: qsTr("Google Play is not available")
}
Text {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
lineHeight: 1.15
color: Theme.textSecondary
font.pixelSize: Theme.fontBody
text: window.mode === "ask"
? qsTr("The mod is asking for the Google account you signed in with (%1), usually to confirm that you own Minecraft on Google Play. It gets access to that Google Play account, so allow only mods you trust.").arg(window.maskedEmail)
: window.mode === "signedOut"
? qsTr("%1 needs your Google Play sign-in, but you are not signed in to %2. Open the launcher, sign in with the Google account that owns Minecraft and start the game again.").arg(window.modName).arg(APP_NAME)
: qsTr("This build of %1 cannot use Google Play, so %2 cannot get a sign-in.").arg(APP_NAME).arg(window.modName)
}
Text {
Layout.fillWidth: true
visible: window.mode === "ask"
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
color: Theme.textMuted
font.pixelSize: Theme.fontSmall + 1
text: qsTr("If you deny, game versions that need this mod may not start.")
}
Rectangle {
Layout.fillWidth: true
visible: SOURCE_MOD.length > 0
implicitHeight: modPath.implicitHeight + 2 * Theme.space2
radius: Theme.radius
color: Theme.surfaceRaised
Text {
id: modPath
anchors.fill: parent
anchors.margins: Theme.space2
text: SOURCE_MOD
color: Theme.textMuted
font.family: Theme.fontMono
font.pixelSize: Theme.fontCaption
elide: Text.ElideMiddle
verticalAlignment: Text.AlignVCenter
}
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Theme.space2
spacing: Theme.space2
Item { Layout.fillWidth: true }
MButton {
visible: window.mode === "ask"
text: qsTr("Deny")
onClicked: window.deny()
}
MButton {
kind: "primary"
text: window.mode === "ask" ? qsTr("Allow") : qsTr("Close")
iconName: window.mode === "ask" ? "check" : ""
onClicked: window.mode === "ask" ? window.allow() : window.deny()
}
}
}
} }
} }
Subject: Let the launcher tell the engine where its Google credentials helper is
diff --git a/src/minecraft_utils.cpp b/src/minecraft_utils.cpp
index d7f2360..f7a601b 100644
--- a/src/minecraft_utils.cpp
+++ b/src/minecraft_utils.cpp
@@ -129,6 +129,10 @@ struct GoogleCredentials {
};
static std::string getUiExecutablePath() {
+ // The launcher that started the game names itself here, whatever its file name.
+ const char* launcher = getenv("MCPELAUNCHER_UI_EXECUTABLE");
+ if(launcher && *launcher)
+ return launcher;
std::string path;
#ifndef MCPELAUNCHER_UI_PATH
#define MCPELAUNCHER_UI_PATH "."
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment