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.