Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,30 @@ The launcher supports three account types:
| **Offline / Local** | A local username that never touches Mojang's servers — no purchase, no login | ❌ No (single-player and LAN only) |
| **Auth server** | A third-party Yggdrasil / authlib server (e.g. a community server) | ✅ Yes, on that server |

**If you cannot afford Minecraft: Java Edition**, the **offline (local) account** option is the supported route. It is
not a purchased account, it is a local username: open **Settings → Accounts → Add account → Local account**, pick a
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.
**You do not need to own Minecraft or have a Microsoft account to play.** The **offline (local) account** option is
fully supported and available to everyone on every build: open **Settings → Accounts → Add account → Local account**,
pick a username, and you can play single-player worlds, mods and resource packs immediately. The launcher never forces
you through Microsoft sign-in.

### Signing in without owning Minecraft
Local accounts are also the route to join **offline/cracked servers** (servers that do not verify ownership with
Mojang). Whether a server accepts you is that server's own check, not something a launcher can change — servers that
verify ownership with Mojang will reject an unowned account no matter what the launcher sends, so use a local account
with servers that allow it.

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:
### Signing in with a Microsoft account

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.
If you have a Microsoft account, you can sign in with it even if it has never bought Minecraft. Microsoft's identity
service authenticates you; 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:

1. **Signs you in anyway**, using your Xbox gamertag as a local offline identity.
2. **Lets you launch the instance you selected**, immediately, with no further interruption.
3. Shows a small "ownership unverified" hint so you know to expect the offline experience.

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.
connection. No client-side change can alter that — it is their check, not ours. Offline/cracked servers will accept
the account normally.

> [!IMPORTANT]
> This fork does not remove, disable, or bypass the ownership check. It still runs exactly as upstream intends, and
Expand Down Expand Up @@ -106,9 +112,13 @@ git clone git@github.com:ChimeraAnt-DEV/JavaAntLauncher.git

### 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
Microsoft sign-in **is entirely optional**. The build this text ships with has no OAuth client ID, so Microsoft sign-in
is unavailable — the launcher tells you so and points you to the local account flow instead. You can ignore Microsoft
sign-in completely and use a local account to play single-player, mods and offline/cracked servers.

If you maintain your own fork and want to enable Microsoft sign-in, you need to supply a client ID: Microsoft's
device-code endpoint rejects a request without one with `400 Bad Request` (`AADSTS900144: The request body must contain
the following parameter: 'client_id'`). The launcher 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
Expand All @@ -132,7 +142,7 @@ reuse, so forks must register their own:
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.
Without one of these, offline/local accounts still work fully; only Microsoft sign-in is unavailable.

## 🎨 Themes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ import com.movtery.zalithlauncher.coroutine.TaskSystem
import com.movtery.zalithlauncher.database.AppDatabase
import com.movtery.zalithlauncher.game.account.auth_server.data.AuthServer
import com.movtery.zalithlauncher.game.account.auth_server.data.AuthServerDao
import com.movtery.zalithlauncher.path.PathManager
import com.movtery.zalithlauncher.setting.AllSettings
import com.movtery.zalithlauncher.ui.androidText
import com.movtery.zalithlauncher.utils.isInGreaterChina
import com.movtery.zalithlauncher.utils.logging.Logger
import com.movtery.zalithlauncher.utils.network.isNetworkAvailable
import kotlinx.coroutines.CoroutineScope
Expand All @@ -38,7 +36,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.apache.commons.io.FileUtils
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList

Expand All @@ -64,9 +61,6 @@ object AccountsManager {
/** 控制刷新所有账号衣橱 */
val refreshWardrobe = _refreshWardrobe.asStateFlow()

private val _isOffline = MutableStateFlow(false)
val isOffline = _isOffline

//本次启动器会话内已通过服务端校验的账号
private val sessionValidatedAccounts: MutableSet<String> = ConcurrentHashMap.newKeySet()

Expand Down Expand Up @@ -236,21 +230,12 @@ object AccountsManager {
}

/**
* 刷新当前账号,同时刷新非中国大陆地区的正版状态
* 刷新当前账号状态。
* 本地账号与微软离线账号均被允许,不再根据地区或是否拥有游戏进行限制
*/
private fun refreshCurrentAccountState() {
val currentAccount = getCurrentAccount()
val isOffline = checkLimit()
_currentAccountFlow.update {
//若处于非正版状态,不允许使用账号
if (isOffline) null else currentAccount
}
_isOffline.update { isOffline }
}

private fun checkLimit(): Boolean {
val circumventLimit = File(PathManager.DIR_FILES_EXTERNAL, "circumventLimit")
return !circumventLimit.exists() && !isInGreaterChina() && !hasMicrosoftAccount()
_currentAccountFlow.update { currentAccount }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,6 @@ class GameLaunchFlow(scope: CoroutineScope) {
version.offlineAccountLogin = true
}

//未拥有 Minecraft 的微软账号无法通过服务端校验,同样以降级为离线身份启动
if (account.isMicrosoftAccount() && !account.ownsMinecraft) {
version.offlineAccountLogin = true
}

return buildPhase {
if (hasNetwork && !skipAccountRefresh && AccountsManager.isLaunchCheckNeeded(account)) {
//账号管理页正在刷新该账号时,直接使用现有凭据启动
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ private fun AccountManageContent(
.padding(all = 12.dp)
.weight(3f),
currentAccount = profileUiState.currentAccount,
isOffline = profileUiState.isOffline,
actions = actions
)

Expand All @@ -253,7 +252,6 @@ private fun AccountManageContent(
.weight(7f),
accounts = profileUiState.accounts,
currentAccount = profileUiState.currentAccount,
isOffline = profileUiState.isOffline,
accountOperation = operationUiState.accountOp,
accountSkinOperation = operationUiState.accountSkinOp,
accountSkinDialogState = operationUiState.accountSkinDialogState,
Expand All @@ -278,7 +276,6 @@ private fun ActionsLayout(
isVisible: Boolean,
modifier: Modifier = Modifier,
currentAccount: Account?,
isOffline: Boolean,
actions: AccountActions
) {
val xOffset by swapAnimateDpAsState(
Expand Down Expand Up @@ -350,17 +347,12 @@ private fun ActionsLayout(
}
}

//添加账号
//添加账号(本地账号即可游玩,直接打开完整登录菜单)
ScalingActionButton(
modifier = Modifier
.fillMaxWidth(),
onClick = {
if (isOffline) {
//非正版状态下,只允许创建微软账号
actions.onIntent(AccountManageIntent.UpdateMicrosoftLoginOp(MicrosoftLoginOperation.Tip))
} else {
actions.onIntent(AccountManageIntent.UpdateLoginMenuOp(LoginMenuOperation.Login))
}
actions.onIntent(AccountManageIntent.UpdateLoginMenuOp(LoginMenuOperation.Login))
}
) {
MarqueeText(text = stringResource(R.string.account_add_new_account))
Expand Down Expand Up @@ -680,7 +672,6 @@ private fun AccountsLayout(
modifier: Modifier = Modifier,
accounts: List<Account>,
currentAccount: Account?,
isOffline: Boolean,
accountOperation: AccountOperation,
accountSkinOperation: AccountSkinOperation,
accountSkinDialogState: AccountManageViewModel.AccountSkinDialogState,
Expand Down Expand Up @@ -723,7 +714,6 @@ private fun AccountsLayout(
.padding(vertical = 6.dp),
currentAccount = currentAccount,
account = account,
enabled = !isOffline, //非正版状态下不允许选择任何状态
onSelected = { AccountsManager.setCurrentAccount(it) },
openChangeSkinDialog = {
if (!account.isAuthServerAccount()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,7 @@ fun LaunchGameOperation(
is LaunchGameOperation.NoAccount -> {
LaunchedEffect(Unit) {
eventViewModel.sendToast(androidText(R.string.game_launch_no_account))
val isOffline = AccountsManager.isOffline.value
toAccountManageScreen(
if (isOffline) FirstLoginMenu.MICROSOFT
else FirstLoginMenu.NORMAL
)
toAccountManageScreen(FirstLoginMenu.NORMAL)
launchGameViewModel.updateOperation(LaunchGameOperation.None)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,13 @@ class AccountManageViewModel @AssistedInject constructor(
AccountsManager.accountsFlow,
AccountsManager.currentAccountFlow,
AccountsManager.authServersFlow,
_accountCapeOpMap,
AccountsManager.isOffline
) { accounts, currentAccount, authServers, accountCapeOpMap, isOffline ->
_accountCapeOpMap
) { accounts, currentAccount, authServers, accountCapeOpMap ->
ProfileUiState(
accounts = accounts,
currentAccount = currentAccount,
authServers = authServers,
accountCapeOpMap = accountCapeOpMap,
isOffline = isOffline
accountCapeOpMap = accountCapeOpMap
)
}.stateIn(
scope = viewModelScope,
Expand All @@ -269,8 +267,7 @@ class AccountManageViewModel @AssistedInject constructor(
val accounts: List<Account> = emptyList(),
val currentAccount: Account? = null,
val authServers: List<AuthServer> = emptyList(),
val accountCapeOpMap: Map<String, List<PlayerProfile.Cape>> = emptyMap(),
val isOffline: Boolean = false
val accountCapeOpMap: Map<String, List<PlayerProfile.Cape>> = emptyMap()
)

/**
Expand Down
2 changes: 1 addition & 1 deletion ZalithLauncher/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@
<string name="account_logging_blocked_ip">当前 IP 地址已被禁止登陆。</string>
<string name="account_logging_profile_not_exists">请先创建 Minecraft 玩家档案,然后再重新登陆!</string>
<string name="account_logging_not_purchased_minecraft">已登录,但未检测到 Minecraft 购买信息。你仍可游玩单人游戏和使用模组;联机与 Realms 需要拥有正版。</string>
<string name="account_logging_missing_client_id">此构建未配置 Microsoft OAuth 客户端 ID,无法使用微软登录。请在 ZalithLauncher/gradle.properties 中设置 oauth_client_id(或添加 OAUTH_CLIENT_ID 构建密钥),使用你自己的 Azure 应用注册后重新构建。</string>
<string name="account_logging_missing_client_id">此构建未配置 Microsoft OAuth 客户端 ID,因此无法使用微软登录。无需拥有 Minecraft 或微软账号也能游玩:请改用离线本地账号登录,可直接加入离线/破解服务器。</string>
<string name="account_not_owned_hint">未验证拥有权 — 仅可离线游玩</string>
<string name="account_relogin_title">需要重新登录</string>
<string name="account_relogin_microsoft_message">启动器无法刷新该微软账号的登录凭据,账号密码可能已被修改,或登录授权已失效,需要重新登录微软账号。</string>
Expand Down
2 changes: 1 addition & 1 deletion ZalithLauncher/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@
<string name="account_logging_blocked_ip">The current IP address has been blocked from logging in.</string>
<string name="account_logging_profile_not_exists">Please create a Minecraft player profile first, then try logging in again!</string>
<string name="account_logging_not_purchased_minecraft">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.</string>
<string name="account_logging_missing_client_id">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.</string>
<string name="account_logging_missing_client_id">Microsoft sign-in is not available in this build (no OAuth client ID configured). You don\'t need a Microsoft account to play: add a local account instead and join offline/cracked servers directly.</string>
<string name="account_not_owned_hint">Ownership unverified — offline play only</string>
<string name="account_relogin_title">Sign In Required</string>
<string name="account_relogin_microsoft_message">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.</string>
Expand Down