Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
7 changes: 7 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import org.jackhuang.hmcl.auth.microsoft.MicrosoftService;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
import org.jackhuang.hmcl.auth.demo.DemoAccount;
import org.jackhuang.hmcl.auth.demo.DemoAccountFactory;
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
import org.jackhuang.hmcl.game.OAuthServer;
import org.jackhuang.hmcl.task.Schedulers;
Expand Down Expand Up @@ -72,6 +74,7 @@ private Accounts() {
public static final OAuthServer.Factory OAUTH_CALLBACK = new OAuthServer.Factory();

public static final OfflineAccountFactory FACTORY_OFFLINE = new OfflineAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER);
public static final DemoAccountFactory FACTORY_DEMO = new DemoAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER);
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, Accounts::getOrCreateAuthlibInjectorServer);
public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(OAUTH_CALLBACK));
public static final List<AccountFactory<?>> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

FACTORY_DEMO 应该被添加到 FACTORIES 列表中。如果不添加,该账户类型将不会出现在“添加账户”对话框的选项卡中,用户只能通过微软登录界面的快捷按钮发现此功能,这降低了功能的发现感和一致性。

Suggested change
public static final List<AccountFactory<?>> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);
public static final List<AccountFactory<?>> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR, FACTORY_DEMO);

Expand All @@ -82,6 +85,7 @@ private Accounts() {

static {
type2factory.put("offline", FACTORY_OFFLINE);
type2factory.put("demo", FACTORY_DEMO);
type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR);
type2factory.put("microsoft", FACTORY_MICROSOFT);

Expand Down Expand Up @@ -112,6 +116,8 @@ public static BoundAuthlibInjectorAccountFactory getAccountFactoryByAuthlibInjec
public static AccountFactory<?> getAccountFactory(Account account) {
if (account instanceof OfflineAccount)
return FACTORY_OFFLINE;
else if (account instanceof DemoAccount)
return FACTORY_DEMO;
else if (account instanceof AuthlibInjectorAccount)
return FACTORY_AUTHLIB_INJECTOR;
else if (account instanceof MicrosoftAccount)
Expand Down Expand Up @@ -400,6 +406,7 @@ private static void removeDanglingAuthlibInjectorAccounts() {
// ==== Login type name i18n ===
private static final Map<AccountFactory<?>, String> unlocalizedLoginTypeNames = mapOf(
pair(Accounts.FACTORY_OFFLINE, "account.methods.offline"),
pair(Accounts.FACTORY_DEMO, "account.methods.demo"),
pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector"),
pair(Accounts.FACTORY_MICROSOFT, "account.methods.microsoft"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.jackhuang.hmcl.auth.authlibinjector.BoundAuthlibInjectorAccountFactory;
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccountFactory;
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
import org.jackhuang.hmcl.auth.demo.DemoAccountFactory;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.game.TexturesLoader;
Expand Down Expand Up @@ -337,7 +338,7 @@ public AccountDetailsInputPane(AccountFactory<?> factory, Runnable onAction) {

int rowIndex = 0;

if (!IntegrityChecker.isOfficial() && !(factory instanceof OfflineAccountFactory)) {
if (!IntegrityChecker.isOfficial() && !(factory instanceof OfflineAccountFactory) && !(factory instanceof DemoAccountFactory)) {
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
hintPane.setSegment(i18n("unofficial.hint"));
GridPane.setColumnSpan(hintPane, 2);
Expand Down Expand Up @@ -493,6 +494,11 @@ public AccountDetailsInputPane(AccountFactory<?> factory, Runnable onAction) {
rowIndex++;
}

if (factory instanceof DemoAccountFactory) {
Label lblDemo = new Label(i18n("account.methods.demo.hint"));
add(lblDemo, 0, rowIndex);
}

valid = new BooleanBinding() {
{
if (cboServers != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.WeakListenerHolder;
import org.jackhuang.hmcl.ui.construct.*;
Expand Down Expand Up @@ -112,7 +113,14 @@ public MicrosoftAccountLoginPane(Account account, Consumer<AuthInfo> callback, R
btnCancel.getStyleClass().add("dialog-cancel");
btnCancel.setOnAction(e -> onCancel());

setActions(loginButtonSpinner, btnCancel);
JFXButton btnDemo = new JFXButton(i18n("account.demo"));
btnDemo.getStyleClass().add("dialog-accept");
btnDemo.setOnAction(e -> {
onCancel();
Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_DEMO));
});

setActions(btnDemo, loginButtonSpinner, btnCancel);

holder.registerWeak(Accounts.OAUTH_CALLBACK.onOpenBrowserAuthorizationCode, event -> Platform.runLater(() -> {
if (step.get() instanceof Step.StartAuthorizationCodeLogin)
Expand Down
4 changes: 4 additions & 0 deletions HMCL/src/main/resources/assets/lang/I18N.properties
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ account.choose=Choose a Player
account.create=Add Account
account.create.microsoft=Add a Microsoft Account
account.create.offline=Add an Offline Account
account.create.demo=Add a Demo Account
account.create.authlibInjector=Add an authlib-injector Account
account.email=Email
account.empty=No Accounts
Expand Down Expand Up @@ -93,6 +94,7 @@ account.login.retry=Retry
account.login.refresh=Log in again
account.login.refresh.microsoft.hint=You need to log in to your Microsoft account again because the account authorization is invalid.
account.login.restricted=Sign in to your Microsoft account to enable this feature
account.demo=Demo Mode
account.logout=Logout
account.register=Register
account.manage=Account List
Expand Down Expand Up @@ -141,6 +143,8 @@ account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft play
\n\
This option is for advanced users only. We do not recommend changing this option unless you know what you are doing.
account.methods.offline.uuid.malformed=Invalid format.
account.methods.demo=Demo Account
account.methods.demo.hint=The Demo Mode is a demo of Minecraft for users who have not purchased the game yet and would like to try it out before making a purchase.
account.methods.ban_query=Ban Query
account.missing=No Accounts
account.missing.add=Click here to add one.
Expand Down
4 changes: 4 additions & 0 deletions HMCL/src/main/resources/assets/lang/I18N_zh.properties
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ account.choose=請選取角色
account.create=建立帳戶
account.create.microsoft=新增 Microsoft 帳戶
account.create.offline=新增離線模式帳戶
account.create.demo=添加試玩模式帳戶
account.create.authlibInjector=新增 authlib-injector 帳戶
account.email=電子信箱
account.empty=沒有帳戶
Expand Down Expand Up @@ -96,6 +97,7 @@ account.login.retry=再次重新整理帳戶
account.login.refresh=重新登入
account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新增加 Microsoft 帳戶
account.login.restricted=登入微軟帳戶以啟用此功能
account.demo=試玩模式
account.logout=登出
account.register=註冊
account.manage=帳戶清單
Expand Down Expand Up @@ -139,6 +141,8 @@ account.methods.offline.name.invalid=遊戲使用者名稱建議僅使用英文
account.methods.offline.uuid=UUID
account.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一標識符。每個啟動器生成 UUID 的方式可能不同。\n透過修改 UUID 選項至原啟動器所生成的 UUID,你可以保證在切換啟動器後,遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色,從而保留原來角色的背包物品。\nUUID 選項為進階選項。除非你知道你在做什麼,否則你不需要調整該選項。
account.methods.offline.uuid.malformed=格式錯誤
account.methods.demo=試玩模式
account.methods.demo.hint=演示模式是 Minecraft 的一個演示版本,供尚未購買遊戲且希望在購買前試玩的用戶使用。
account.missing=沒有遊戲帳戶
account.missing.add=按一下此處新增帳戶
account.move_to_global=轉換為全域帳戶\n該帳戶的資訊會儲存至系統目前使用者目錄的配置檔案中
Expand Down
4 changes: 4 additions & 0 deletions HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ account.choose=选择一个角色
account.create=添加账户
account.create.microsoft=添加微软账户
account.create.offline=添加离线模式账户
account.create.demo=添加试玩账户
account.create.authlibInjector=添加外置登录账户 (authlib-injector)
account.email=邮箱
account.empty=没有账户
Expand Down Expand Up @@ -97,6 +98,7 @@ account.login.retry=再次刷新账户
account.login.refresh=重新登录
account.login.refresh.microsoft.hint=由于账户授权失效,你需要重新添加微软账户。
account.login.restricted=登录微软账户以启用此功能
account.demo=试玩账户
account.logout=登出
account.register=注册
account.manage=账户列表
Expand Down Expand Up @@ -141,6 +143,8 @@ account.methods.offline.name.invalid=游戏用户名建议仅使用英文字母
account.methods.offline.uuid=UUID
account.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一标识符。每个启动器生成 UUID 的方式可能不同。通过将 UUID 修改为原启动器所生成的 UUID,你可以保证在切换启动器后,游戏还能将你的游戏角色识别为给定 UUID 所对应的角色,从而保留原角色的背包物品。UUID 选项为高级选项。除非你知道你在做什么,否则你不需要调整该选项。
account.methods.offline.uuid.malformed=格式错误
account.methods.demo=试玩账户
account.methods.demo.hint=试玩模式是提供给尚未购买Minecraft,但想要试玩的玩家的版本。
account.missing=没有游戏账户
account.missing.add=点击此处添加账户
account.move_to_global=转换为全局账户\n该账户的信息会保存至系统当前用户文件夹的配置文件中
Expand Down
147 changes: 147 additions & 0 deletions HMCLCore/src/main/java/org/jackhuang/hmcl/auth/demo/DemoAccount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.auth.demo;

import javafx.beans.binding.ObjectBinding;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorArtifactProvider;
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.LaunchOptions;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;

import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import static java.util.Objects.requireNonNull;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;

/**
*
* @author huang
*/
public class DemoAccount extends Account {

private final AuthlibInjectorArtifactProvider downloader;
private final String username;
private final UUID uuid;

protected DemoAccount(AuthlibInjectorArtifactProvider downloader, String username, UUID uuid) {
this.downloader = requireNonNull(downloader);
this.username = requireNonNull(username);
this.uuid = requireNonNull(uuid);

if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("Username cannot be blank");
}
}

public AuthlibInjectorArtifactProvider getDownloader() {
return downloader;
}

@Override
public UUID getUUID() {
return uuid;
}

@Override
public String getUsername() {
return username;
}

@Override
public String getCharacter() {
return username;
}

@Override
public String getIdentifier() {
return username + ":" + username;
}

public AuthInfo logIn() throws AuthenticationException {
// Using "legacy" user type here because "mojang" user type may cause "invalid session token" or "disconnected" when connecting to a game server.
return new DemoAuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), AuthInfo.USER_TYPE_MSA, "{}");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

代码实现与注释描述不符。注释提到使用 "legacy" 用户类型以避免连接服务器时的验证问题,但代码中实际使用的是 AuthInfo.USER_TYPE_MSA ("msa")。建议根据注释意图将其改为 AuthInfo.USER_TYPE_LEGACY

Suggested change
return new DemoAuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), AuthInfo.USER_TYPE_MSA, "{}");
return new DemoAuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), AuthInfo.USER_TYPE_LEGACY, "{}");

}

private class DemoAuthInfo extends AuthInfo {
public DemoAuthInfo(String username, UUID uuid, String accessToken, String userType, String userProperties) {
super(username, uuid, accessToken, userType, userProperties);
}

@Override
public Arguments getLaunchArguments(LaunchOptions options) {
Arguments arguments = new Arguments();
arguments = arguments.addGameArguments("--demo");
return arguments;
}

@Override
public void close() throws IOException {
// Do nothing.
}
}

@Override
public AuthInfo playOffline() throws AuthenticationException {
return logIn();
}

@Override
public Map<Object, Object> toStorage() {
return mapOf(
pair("uuid", UUIDTypeAdapter.fromUUID(uuid)),
pair("username", username)
);
}

@Override
public ObjectBinding<Optional<Map<TextureType, Texture>>> getTextures() {
return super.getTextures();
}

@Override
public String toString() {
return new ToStringBuilder(this)
.append("username", username)
.append("uuid", uuid)
.toString();
}

@Override
public int hashCode() {
return username.hashCode();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

hashCode 的实现应包含 isPortable() 属性,以保持与 equals 方法的一致性。此外,建议同时包含 uuid 以增强唯一性校验。

Suggested change
return username.hashCode();
return java.util.Objects.hash(isPortable(), username, uuid);

}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof DemoAccount))
return false;
DemoAccount another = (DemoAccount) obj;
return isPortable() == another.isPortable() && username.equals(another.username);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

equals 方法中,建议同时校验 uuid 属性,以确保账户的唯一性判断更加严谨。

Suggested change
return isPortable() == another.isPortable() && username.equals(another.username);
return isPortable() == another.isPortable() && username.equals(another.username) && uuid.equals(another.uuid);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.auth.demo;

import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorArtifactProvider;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;

import java.util.Map;
import java.util.UUID;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.Lang.tryCast;

/**
*
* @author huangyuhui
*/
public final class DemoAccountFactory extends AccountFactory<DemoAccount> {
private final AuthlibInjectorArtifactProvider downloader;

public DemoAccountFactory(AuthlibInjectorArtifactProvider downloader) {
this.downloader = downloader;
}

@Override
public AccountLoginType getLoginType() {
return AccountLoginType.NONE;
}

public DemoAccount create(String username, UUID uuid) {
return new DemoAccount(downloader, username, uuid);
}

@Override
public DemoAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) {
username = "Player";
UUID uuid;
uuid = getUUIDFromUserName(username);
return new DemoAccount(downloader, username, uuid);
}

@Override
public DemoAccount fromStorage(Map<Object, Object> storage) {
String username = tryCast(storage.get("username"), String.class)
.orElseThrow(() -> new IllegalStateException("Demo account configuration malformed."));
UUID uuid = tryCast(storage.get("uuid"), String.class)
.map(UUIDTypeAdapter::fromString)
.orElse(getUUIDFromUserName(username));

return new DemoAccount(downloader, username, uuid);
}

public static UUID getUUIDFromUserName(String username) {
return UUID.nameUUIDFromBytes(("DemoPlayer:" + username).getBytes(UTF_8));
}

}