From 8e26f831bd3e79d2c6509cdcc3702eabd8bb2f9f Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 17 Sep 2026 05:25:32 +0000 Subject: [PATCH] Warn instead of block when Minecraft ownership is unverified Signing in with a Microsoft account that has never bought Minecraft previously threw NotPurchasedMinecraftException and aborted the login with an error the user could not act on. The ownership check itself is unchanged and still runs; only its failure handling changes. - verifyGameOwnership() now returns whether an entitlement was found instead of throwing. microsoftAuthAsync() uses that to fall back to an offline identity, built from the Xbox gamertag carried on the XSTS response, when no entitlement exists. The selected instance still launches. - Account gains ownsMinecraft, persisted via a new Room migration, so the state survives restarts. The account list shows an "ownership unverified" hint, and skin/cape fetches are skipped because no official profile exists to read from. - Login reports the situation as a notice rather than an error. Offline play is unaffected. Online play is not unlocked by this change and cannot be, because official servers perform their own ownership check on every connection. Also surface the real cause of the Microsoft login 400. A build with no oauth_client_id sends an empty client_id and Azure answers 400 AADSTS900144, which the launcher presented as "(400) The server could not understand the request". fetchDeviceCodeResponse() now detects the missing ID up front and says so, pointing at gradle.properties, the git-ignored .oauth_client_id.txt, and the OAUTH_CLIENT_ID CI secret. README: document the non-blocking ownership behaviour, the three account types, and how to register an Azure app to enable Microsoft sign-in. Co-authored-by: openhands --- README.md | 55 +++++++++++++-- .../zalithlauncher/database/AppDatabase.kt | 11 ++- .../zalithlauncher/game/account/Account.kt | 9 ++- .../game/account/AccountUtils.kt | 13 ++++ .../game/account/AccountsManager.kt | 2 + .../microsoft/MicrosoftAuthenticator.kt | 67 ++++++++++++++++--- .../MissingOAuthClientIdException.kt | 33 +++++++++ .../models/MicrosoftXboxAuthModels.kt | 4 +- .../game/launch/GameLaunchFlow.kt | 5 ++ .../content/elements/AccountElements.kt | 7 ++ .../viewmodel/AccountManageViewModel.kt | 5 ++ .../src/main/res/values-zh-rCN/strings.xml | 4 +- .../src/main/res/values-zh-rTW/strings.xml | 4 +- .../src/main/res/values/strings.xml | 4 +- 14 files changed, 203 insertions(+), 20 deletions(-) create mode 100644 ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/MissingOAuthClientIdException.kt diff --git a/README.md b/README.md index 3e20e3578..936e4024a 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,28 @@ not a purchased account, it is a local username: open **Settings → Accounts username, and you can play single-player worlds and mods immediately. You will not be able to join official online servers, because those verify ownership with Mojang — that is a server-side rule, not something a launcher can change. +### Signing in without owning Minecraft + +You can also sign in with your Microsoft account even if it has never bought Minecraft. Microsoft's identity service +will authenticate you fine; the launcher then checks ownership against Mojang's `entitlements` service as part of the +normal login flow. When that check reports no entitlement, the launcher now: + +1. **Tells you plainly** that ownership could not be verified and that features may be limited. +2. **Signs you in anyway**, using your Xbox gamertag as a local offline identity. +3. **Lets you launch the instance you selected**, immediately, with no further interruption. + +What you get in that state is the offline experience: single-player worlds, mods, resource packs and LAN. What you do +not get is online play against official Mojang servers, because **those servers verify ownership themselves** on every +connection. No client-side change can alter that — it is their check, not ours. + > [!IMPORTANT] -> Removing or bypassing the Minecraft purchase/ownership check (DRM) is **not** supported by this project, and no such -> change will be made. The launcher verifies ownership against Mojang's `entitlements` service during a Microsoft -> login, which is a requirement of Minecraft's terms of service. Skipping that check would be piracy and would also -> break multiplayer. Use an offline account instead if online play is not an option for you. +> This fork does not remove, disable, or bypass the ownership check. It still runs exactly as upstream intends, and +> still reports honestly. The only change is that a failed check is now treated as a *limitation notice* rather than a +> hard stop, so a user who signs in with an account that has no entitlement is not left staring at an error with no way +> forward. +> +> For full online play, buying Minecraft: Java Edition is the only legitimate route, and this project will not help you +> avoid that purchase. [Discord Server Shutdown Announcement](/.github/notice/DiscordStatus.md) @@ -87,6 +104,36 @@ git clone git@github.com:ChimeraAnt-DEV/JavaAntLauncher.git > distributed under a distinct name; the user-facing application name can be changed in > [gradle.properties](./ZalithLauncher/gradle.properties). +### Enabling Microsoft sign-in (required for Microsoft accounts) + +Microsoft sign-in **will not work in a build that has no OAuth client ID**, and the failure is confusing: Microsoft's +device-code endpoint rejects the request with `400 Bad Request` (`AADSTS900144: The request body must contain the +following parameter: 'client_id'`). The launcher now detects this case up front and tells you, but you still have to +supply an ID to use Microsoft accounts. + +An OAuth client ID is an Azure app registration that belongs to whoever ships the build. Upstream's ID is not yours to +reuse, so forks must register their own: + +1. Open the [Azure Portal](https://portal.azure.com/) → **Microsoft Entra ID** → **App registrations** → **New + registration**. +2. Set **Supported account types** to *Personal Microsoft accounts only* (or *Accounts in any organizational directory + and personal Microsoft accounts*). +3. Under **Authentication**, enable **Allow public client flows** — required for the device-code flow. +4. Copy the **Application (client) ID**. +5. Provide it to the build in one of two ways: + * **Local builds** — create `ZalithLauncher/.oauth_client_id.txt` containing the ID. This file is git-ignored, so it + will not end up in a commit: + ```bash + echo "00000000-0000-0000-0000-000000000000" > ZalithLauncher/.oauth_client_id.txt + ``` + * **CI builds** — add a repository secret named `OAUTH_CLIENT_ID` (see + [build.yml](./.github/workflows/build.yml) for how it is passed in). + + Alternatively you can set `oauth_client_id` in `ZalithLauncher/gradle.properties`, but note that file **is** + tracked by git, so it is a poor place for a real value. + +Without one of these, offline accounts still work fully; only Microsoft sign-in is unavailable. + ## 🎨 Themes Zalith Launcher 2 ships with a set of built-in color themes plus Material You dynamic color on Android 12+. diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/database/AppDatabase.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/database/AppDatabase.kt index efb4b4a6a..08e403bfb 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/database/AppDatabase.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/database/AppDatabase.kt @@ -34,7 +34,7 @@ import com.movtery.zalithlauncher.game.path.GamePathDao @Database( entities = [Account::class, AuthServer::class, GamePath::class], - version = 2, + version = 3, exportSchema = false //默认不支持导出 ) @TypeConverters(Converters::class) @@ -64,6 +64,13 @@ abstract class AppDatabase : RoomDatabase() { } } + val MIGRATION_2_3 = object : Migration(2, 3) { + override fun migrate(db: SupportSQLiteDatabase) { + //已有账号默认视为拥有,避免升级后改变行为 + db.execSQL("ALTER TABLE accounts ADD COLUMN ownsMinecraft INTEGER NOT NULL DEFAULT 1") + } + } + /** * 获取全局数据库实例 */ @@ -74,7 +81,7 @@ abstract class AppDatabase : RoomDatabase() { AppDatabase::class.java, "launcher_data.db" ) - .addMigrations(MIGRATION_1_2) + .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .build() INSTANCE = instance instance diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/Account.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/Account.kt index d957ac00d..f5642edc4 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/Account.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/Account.kt @@ -59,7 +59,12 @@ data class Account( var otherAccount: String? = null, var otherPassword: String? = null, var accountType: String? = null, - var skinModelType: SkinModelType = SkinModelType.NONE + var skinModelType: SkinModelType = SkinModelType.NONE, + /** + * 账号是否通过 Minecraft 所有权校验 + * 未通过时改用离线身份启动,仅影响联机功能,单人游戏与模组不受影响 + */ + var ownsMinecraft: Boolean = true ): Parcelable { val hasSkinFile: Boolean get() = getSkinFile().exists() @@ -75,6 +80,8 @@ data class Account( * 下载并更新账号的皮肤文件 */ suspend fun downloadYggdrasil() = withContext(Dispatchers.IO) { + //未拥有 Minecraft 时不存在官方档案,跳过皮肤/披风请求 + if (!ownsMinecraft) return@withContext val baseUrl = when { isMicrosoftAccount() -> "https://sessionserver.mojang.com" isAuthServerAccount() -> otherBaseUrl!!.removeSuffix("/") + "/sessionserver/" diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/AccountUtils.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/AccountUtils.kt index 8600eb425..7f145d5a3 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/AccountUtils.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/AccountUtils.kt @@ -33,6 +33,7 @@ import com.movtery.zalithlauncher.game.account.auth_server.getAuthServeInfo import com.movtery.zalithlauncher.game.account.microsoft.AsyncStatus import com.movtery.zalithlauncher.game.account.microsoft.AuthType import com.movtery.zalithlauncher.game.account.microsoft.MinecraftProfileException +import com.movtery.zalithlauncher.game.account.microsoft.MissingOAuthClientIdException import com.movtery.zalithlauncher.game.account.microsoft.NotPurchasedMinecraftException import com.movtery.zalithlauncher.game.account.microsoft.XboxLoginException import com.movtery.zalithlauncher.game.account.microsoft.fetchDeviceCodeResponse @@ -155,6 +156,14 @@ fun microsoftLogin( AccountsManager.saveAccount(account) AccountsManager.markSessionValidated(account) Logger.info(TAG, "Microsoft account login successful: ${account.username}") + if (!account.ownsMinecraft) { + //登录成功但未拥有游戏:提示功能受限,不阻止继续使用 + Logger.info(TAG, "Account ${account.username} does not own Minecraft, limited functionality") + showToast( + androidText(R.string.account_logging_not_purchased_minecraft), + Toast.LENGTH_LONG + ) + } onSuccess() }, onError = { th -> @@ -163,6 +172,7 @@ fun microsoftLogin( } when (th) { is HttpRequestTimeoutException -> androidText(R.string.account_logging_time_out) + is MissingOAuthClientIdException -> th.toLocal() is NotPurchasedMinecraftException -> toLocal() is MinecraftProfileException -> th.toLocal() is XboxLoginException -> th.toLocal() @@ -272,6 +282,8 @@ suspend fun Account.refreshMicrosoft( this.username = newAcc.username this.refreshToken = newAcc.refreshToken this.xUid = newAcc.xUid + this.ownsMinecraft = newAcc.ownsMinecraft + this.skinModelType = newAcc.skinModelType } } @@ -304,6 +316,7 @@ fun Throwable.isReloginRequired(): Boolean { } fun accountErrorText(th: Throwable): AndroidStringText = when (th) { + is MissingOAuthClientIdException -> th.toLocal() is NotPurchasedMinecraftException -> toLocal() is MinecraftProfileException -> th.toLocal() is XboxLoginException -> th.toLocal() diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/AccountsManager.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/AccountsManager.kt index 0e2bf8df1..f280ba4f1 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/AccountsManager.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/AccountsManager.kt @@ -207,6 +207,8 @@ object AccountsManager { */ fun isLaunchCheckNeeded(account: Account): Boolean = when { account.isNoLoginRequired() -> false + //未拥有 Minecraft 的账号使用离线身份,无需服务端校验 + account.isMicrosoftAccount() && !account.ownsMinecraft -> false account.isMicrosoftAccount() -> !isSessionValidated(account) || System.currentTimeMillis() > account.expiresAt - 5 * 60 * 1000 else -> !isSessionValidated(account) diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/MicrosoftAuthenticator.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/MicrosoftAuthenticator.kt index d1017aa47..a454caa7e 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/MicrosoftAuthenticator.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/MicrosoftAuthenticator.kt @@ -42,6 +42,7 @@ import com.movtery.zalithlauncher.game.account.microsoft.models.XSTSAuthResult import com.movtery.zalithlauncher.game.account.microsoft.models.XSTSProperties import com.movtery.zalithlauncher.game.account.microsoft.models.XSTSRequest import com.movtery.zalithlauncher.game.account.wardrobe.SkinModelType +import com.movtery.zalithlauncher.game.account.wardrobe.getLocalUUIDWithSkinModel import com.movtery.zalithlauncher.game.account.yggdrasil.findUsing import com.movtery.zalithlauncher.game.account.yggdrasil.getPlayerProfile import com.movtery.zalithlauncher.game.account.yggdrasil.getSkinModel @@ -97,6 +98,10 @@ const val MINECRAFT_SERVICES_URL = "https://api.minecraftservices.com" * 设备代码用于在单独的设备或浏览器上授权用户 */ suspend fun fetchDeviceCodeResponse(context: CoroutineContext): DeviceCodeResponse = coroutineScope { + //未配置客户端 ID 时微软会返回 400,提前抛出可给出明确指引 + if (BuildKeys.OAUTH_CLIENT_ID.isBlank()) { + throw MissingOAuthClientIdException() + } withRetry { submitForm( url = "$MICROSOFT_AUTH_URL$TENANT/oauth2/v2.0/devicecode", @@ -215,7 +220,7 @@ private suspend fun ClientRequestException.errorCode(): String? { * * 支持刷新现有访问令牌或使用提供的访问令牌。然后,继续使用 Xbox Live (XBL)、Xbox 安全令牌服务 (XSTS) 进行身份验证,最后访问 Minecraft。 * - * 支持验证用户是否拥有游戏,然后创建 [Account] 对象。 + * 支持验证用户是否拥有游戏。若未拥有,则降级为离线身份创建 [Account] 对象,不再中断登录。 * * @param statusUpdate 验证执行到哪个步骤,通过这个进行回调更新 */ @@ -238,9 +243,20 @@ suspend fun microsoftAuthAsync( Logger.debug(TAG, "Authenticating with Minecraft services") val authResponse = authenticateMinecraft(xstsToken, statusUpdate, context) Logger.debug(TAG, "Verifying Minecraft ownership") - verifyGameOwnership(authResponse.accessToken, statusUpdate) + val ownsMinecraft = verifyGameOwnership(authResponse.accessToken, statusUpdate) + + if (!ownsMinecraft) { + Logger.info(TAG, "Account does not own Minecraft, falling back to an offline identity") + } - return@coroutineScope createAccount(authResponse, newRefreshToken, xblToken.second, statusUpdate) + return@coroutineScope createAccount( + authResponse = authResponse, + refreshToken = newRefreshToken, + uhs = xblToken.second, + ownsMinecraft = ownsMinecraft, + fallbackUsername = xstsToken.gamertag, + statusUpdate = statusUpdate + ) } /** @@ -355,7 +371,14 @@ private suspend fun authenticateXSTS( context = context ) - XSTSAuthResult(token = response["Token"].text(), uhs = uhs) + XSTSAuthResult( + token = response["Token"].text(), + uhs = uhs, + gamertag = response["DisplayClaims"]?.jsonObject + ?.get("xui")?.jsonArray + ?.firstOrNull()?.jsonObject + ?.get("gtg")?.jsonPrimitive?.content + ) } catch (e: ClientRequestException) { // XSTS 对账号类问题统一返回 4xx 及 XErr 错误码,expectSuccess 会提前抛出异常 // 因此必须从异常响应体中解析 XErr,才能向用户展示真实的失败原因 @@ -407,15 +430,17 @@ private suspend fun authenticateMinecraft( } } -private suspend fun verifyGameOwnership(accessToken: String, update: (AsyncStatus) -> Unit) { +/** + * 校验账号是否拥有 Minecraft + * @return 拥有返回 true;未拥有返回 false,由调用方决定降级处理,不再中断登录 + */ +private suspend fun verifyGameOwnership(accessToken: String, update: (AsyncStatus) -> Unit): Boolean { update(AsyncStatus.VERIFY_GAME_OWNERSHIP) - withRetry { + return withRetry { val response = GLOBAL_CLIENT.get("$MINECRAFT_SERVICES_URL/entitlements/mcstore") { header(HttpHeaders.Authorization, "Bearer $accessToken") } - if (response.safeBodyAsJson()["items"]?.jsonArray?.isEmpty() != false) { - throw NotPurchasedMinecraftException() - } + response.safeBodyAsJson()["items"]?.jsonArray?.isNotEmpty() == true } } @@ -423,8 +448,31 @@ private suspend fun createAccount( authResponse: MinecraftAuthResponse, refreshToken: String, uhs: String, + ownsMinecraft: Boolean, + fallbackUsername: String?, statusUpdate: (AsyncStatus) -> Unit ): Account { + if (!ownsMinecraft) { + //未拥有 Minecraft 时无法读取官方档案,改用 Xbox 玩家代号构造离线身份 + val username = fallbackUsername?.takeIf { it.isNotBlank() } ?: "Player" + val profileId = getLocalUUIDWithSkinModel(username, SkinModelType.NONE) + //避免同一个账号反复添加 + val account = AccountsManager.loadFromProfileID(profileId, AccountType.MICROSOFT.tag) ?: Account() + + return account.apply { + this.username = username + this.profileId = profileId + this.accessToken = authResponse.accessToken + this.expiresAt = System.currentTimeMillis() + authResponse.expiresIn * 1000 + this.accountType = AccountType.MICROSOFT.tag + this.clientToken = BuildKeys.LAUNCHER_NAME.toUuidStr().replace("-", "") + this.refreshToken = refreshToken.ifEmpty { "None" } + this.xUid = uhs + this.skinModelType = SkinModelType.NONE + this.ownsMinecraft = false + } + } + statusUpdate(AsyncStatus.GETTING_PLAYER_PROFILE) val profile = getPlayerProfile( @@ -446,6 +494,7 @@ private suspend fun createAccount( this.refreshToken = refreshToken.ifEmpty { "None" } this.xUid = uhs this.skinModelType = profile.skins.findUsing()?.getSkinModel() ?: SkinModelType.NONE + this.ownsMinecraft = true } } diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/MissingOAuthClientIdException.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/MissingOAuthClientIdException.kt new file mode 100644 index 000000000..987babd8c --- /dev/null +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/MissingOAuthClientIdException.kt @@ -0,0 +1,33 @@ +/* + * Zalith Launcher 2 + * Copyright (C) 2025 MovTery and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.movtery.zalithlauncher.game.account.microsoft + +import com.movtery.zalithlauncher.R +import com.movtery.zalithlauncher.ui.AndroidStringText +import com.movtery.zalithlauncher.ui.androidText + +/** + * 构建时未提供 OAuth 客户端 ID + * 微软设备代码端点会因此返回 400 (AADSTS900144),直接说明原因比展示裸 HTTP 错误更有帮助 + */ +class MissingOAuthClientIdException : RuntimeException("OAUTH_CLIENT_ID is not configured") + +fun MissingOAuthClientIdException.toLocal(): AndroidStringText { + return androidText(R.string.account_logging_missing_client_id) +} diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/models/MicrosoftXboxAuthModels.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/models/MicrosoftXboxAuthModels.kt index d129a79b3..f363d4e65 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/models/MicrosoftXboxAuthModels.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/account/microsoft/models/MicrosoftXboxAuthModels.kt @@ -64,5 +64,7 @@ data class XSTSAuthResult( @SerialName("token") val token: String, @SerialName("uhs") - val uhs: String + val uhs: String, + /** Xbox 玩家代号,未拥有 Minecraft 时作为离线身份名称 */ + val gamertag: String? = null ) \ No newline at end of file diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/launch/GameLaunchFlow.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/launch/GameLaunchFlow.kt index 527ccf7b6..f1c3c7580 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/launch/GameLaunchFlow.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/launch/GameLaunchFlow.kt @@ -141,6 +141,11 @@ class GameLaunchFlow(scope: CoroutineScope) { version.offlineAccountLogin = true } + //未拥有 Minecraft 的微软账号无法通过服务端校验,同样以降级为离线身份启动 + if (account.isMicrosoftAccount() && !account.ownsMinecraft) { + version.offlineAccountLogin = true + } + return buildPhase { if (hasNetwork && !skipAccountRefresh && AccountsManager.isLaunchCheckNeeded(account)) { //账号管理页正在刷新该账号时,直接使用现有凭据启动 diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/content/elements/AccountElements.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/content/elements/AccountElements.kt index 480dddb88..415e67288 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/content/elements/AccountElements.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/content/elements/AccountElements.kt @@ -391,6 +391,13 @@ fun AccountItem( text = getAccountTypeName(account), style = MaterialTheme.typography.labelMedium ) + if (account.isMicrosoftAccount() && !account.ownsMinecraft) { + Text( + text = stringResource(R.string.account_not_owned_hint), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.error + ) + } } Row { //更换皮肤/披风 diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/viewmodel/AccountManageViewModel.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/viewmodel/AccountManageViewModel.kt index c745b33ea..4034b4e6b 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/viewmodel/AccountManageViewModel.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/viewmodel/AccountManageViewModel.kt @@ -559,6 +559,11 @@ class AccountManageViewModel @AssistedInject constructor( /** 获取微软披风列表 */ private fun fetchMicrosoftCapes(account: Account) { + //未拥有 Minecraft 时没有官方档案,跳过皮肤与披风请求 + if (!account.ownsMinecraft) { + emitToast(androidText(R.string.account_logging_not_purchased_minecraft), Toast.LENGTH_LONG) + return + } TaskSystem.submitTask( Task.runTask( id = account.uniqueUUID, diff --git a/ZalithLauncher/src/main/res/values-zh-rCN/strings.xml b/ZalithLauncher/src/main/res/values-zh-rCN/strings.xml index c7ec1193e..df4d8dbe1 100644 --- a/ZalithLauncher/src/main/res/values-zh-rCN/strings.xml +++ b/ZalithLauncher/src/main/res/values-zh-rCN/strings.xml @@ -476,7 +476,9 @@ 登录过于频繁,请稍后再试。 当前 IP 地址已被禁止登陆。 请先创建 Minecraft 玩家档案,然后再重新登陆! - 未检查到 Minecraft 配置信息,请前往 Minecraft 的官方网站购买。 + 已登录,但未检测到 Minecraft 购买信息。你仍可游玩单人游戏和使用模组;联机与 Realms 需要拥有正版。 + 此构建未配置 Microsoft OAuth 客户端 ID,无法使用微软登录。请在 ZalithLauncher/gradle.properties 中设置 oauth_client_id(或添加 OAUTH_CLIENT_ID 构建密钥),使用你自己的 Azure 应用注册后重新构建。 + 未验证拥有权 — 仅可离线游玩 需要重新登录 启动器无法刷新该微软账号的登录凭据,账号密码可能已被修改,或登录授权已失效,需要重新登录微软账号。 验证服务器拒绝了当前登录,密码可能已被修改,请重新输入 %s 的密码。 diff --git a/ZalithLauncher/src/main/res/values-zh-rTW/strings.xml b/ZalithLauncher/src/main/res/values-zh-rTW/strings.xml index 1d82bb8fc..5e4fa903c 100644 --- a/ZalithLauncher/src/main/res/values-zh-rTW/strings.xml +++ b/ZalithLauncher/src/main/res/values-zh-rTW/strings.xml @@ -422,7 +422,9 @@ 登入失敗 獲取玩家皮膚、儲存帳號資訊 網路連線逾時,請檢查網路環境。 - 未偵測到 Minecraft 購買資訊,請前往 Minecraft 官方網站購買。 + 已登入,但未偵測到 Minecraft 購買資訊。你仍可遊玩單人遊戲與使用模組;多人連線與 Realms 需要擁有正版。 + 此組建未設定 Microsoft OAuth 用戶端 ID,無法使用微軟登入。請在 ZalithLauncher/gradle.properties 設定 oauth_client_id(或加入 OAUTH_CLIENT_ID 組建密鑰),使用你自己的 Azure 應用程式註冊後重新組建。 + 未驗證擁有權 — 僅可離線遊玩 需要重新登入 啟動器無法重新整理該 Microsoft 帳號的登入憑證,帳號密碼可能已被修改,或登入授權已失效,需要重新登入 Microsoft 帳號。 驗證伺服器拒絕了目前登入,密碼可能已被修改,請重新輸入 %s 的密碼。 diff --git a/ZalithLauncher/src/main/res/values/strings.xml b/ZalithLauncher/src/main/res/values/strings.xml index db65038d6..9c9b5edeb 100644 --- a/ZalithLauncher/src/main/res/values/strings.xml +++ b/ZalithLauncher/src/main/res/values/strings.xml @@ -476,7 +476,9 @@ Login attempts are too frequent. Please try again later. The current IP address has been blocked from logging in. Please create a Minecraft player profile first, then try logging in again! - Minecraft ownership not verified. Please purchase it from Minecraft\'s official website. + Signed in, but Minecraft ownership could not be verified. You can still play single-player and use mods; online multiplayer and Realms require a purchased copy. + This build has no Microsoft OAuth client ID configured, so Microsoft sign-in cannot work. Set oauth_client_id in ZalithLauncher/gradle.properties (or add an OAUTH_CLIENT_ID build secret) using your own Azure app registration, then rebuild. + Ownership unverified — offline play only Sign In Required The launcher failed to refresh this Microsoft account\'s sign-in credentials. The password may have been changed or the authorization has been revoked, so you need to sign in to the Microsoft account again. The auth server rejected the current sign-in, possibly because the password has been changed. Please enter the password of %s again.