向非官方构建添加提示 (#2069)

* update

* Add GitHub SHA to manifest

* Add unofficial to build channel

* Optimize IntegrityChecker

* update hint

* Add hint
This commit is contained in:
Glavo
2023-02-06 22:47:23 +08:00
committed by GitHub
parent 8514eb904d
commit bb66be7c3a
11 changed files with 83 additions and 28 deletions

View File

@@ -11,18 +11,21 @@ plugins {
id("com.github.johnrengelman.shadow") version "7.1.2" id("com.github.johnrengelman.shadow") version "7.1.2"
} }
val isOfficial = System.getenv("HMCL_SIGNATURE_KEY") != null
|| (System.getenv("GITHUB_REPOSITORY_OWNER") == "huanghongxun" && System.getenv("GITHUB_BASE_REF").isNullOrEmpty())
val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number -> val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number ->
val offset = System.getenv("BUILD_NUMBER_OFFSET")?.toInt() ?: 0 val offset = System.getenv("BUILD_NUMBER_OFFSET")?.toInt() ?: 0
if (number != null) { if (number != null) {
(number - offset).toString() (number - offset).toString()
} else { } else {
val shortCommit = System.getenv("GITHUB_SHA")?.toLowerCase()?.substring(0, 7) val shortCommit = System.getenv("GITHUB_SHA")?.toLowerCase()?.substring(0, 7)
val prefix = if (System.getenv("GITHUB_REPOSITORY_OWNER") == "huanghongxun") "dev" else "unofficial" val prefix = if (isOfficial) "dev" else "unofficial"
if (!shortCommit.isNullOrEmpty()) "$prefix-$shortCommit" else "SNAPSHOT" if (!shortCommit.isNullOrEmpty()) "$prefix-$shortCommit" else "SNAPSHOT"
} }
} }
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.5" val versionRoot = System.getenv("VERSION_ROOT") ?: "3.5"
val versionType = System.getenv("VERSION_TYPE") ?: "nightly" val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" else "unofficial"
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: "" val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: "" val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
@@ -131,6 +134,10 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
"javafx.controls/javafx.scene.control.skin" "javafx.controls/javafx.scene.control.skin"
).joinToString(" ") ).joinToString(" ")
) )
System.getenv("GITHUB_SHA")?.also {
attributes("GitHub-SHA" to it)
}
} }
doLast { doLast {

View File

@@ -45,6 +45,7 @@ public final class Metadata {
public static final String EULA_URL = "https://hmcl.huangyuhui.net/eula"; public static final String EULA_URL = "https://hmcl.huangyuhui.net/eula";
public static final String BUILD_CHANNEL = JarUtils.getManifestAttribute("Build-Channel", "nightly"); public static final String BUILD_CHANNEL = JarUtils.getManifestAttribute("Build-Channel", "nightly");
public static final String GITHUB_SHA = JarUtils.getManifestAttribute("GitHub-SHA", null);
public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft"); public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
public static final Path HMCL_DIRECTORY; public static final Path HMCL_DIRECTORY;

View File

@@ -58,6 +58,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.WeakListenerHolder;
import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.BindingMapping;
@@ -289,28 +290,37 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
FlowPane box = new FlowPane(); FlowPane box = new FlowPane();
box.setHgap(8); box.setHgap(8);
JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth")); JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth"));
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a")); birthLink.setExternalLink("https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a");
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile")); JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
profileLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/editprof.aspx")); profileLink.setExternalLink("https://account.live.com/editprof.aspx");
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase")); JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase"));
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize")); JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
deauthorizeLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A")); deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A");
JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password")); JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password"));
forgotpasswordLink.setOnAction(e -> FXUtils.openLink("https://www.minecraft.net/password/forgot")); forgotpasswordLink.setExternalLink("https://www.minecraft.net/password/forgot");
JFXHyperlink createProfileLink = new JFXHyperlink(i18n("account.methods.microsoft.makegameidsettings")); JFXHyperlink createProfileLink = new JFXHyperlink(i18n("account.methods.microsoft.makegameidsettings"));
createProfileLink.setOnAction(e -> FXUtils.openLink("https://www.minecraft.net/msaprofile/mygames/editprofile")); createProfileLink.setExternalLink("https://www.minecraft.net/msaprofile/mygames/editprofile");
purchaseLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.PURCHASE_URL));
box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink, forgotpasswordLink, createProfileLink); box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink, forgotpasswordLink, createProfileLink);
GridPane.setColumnSpan(box, 2); GridPane.setColumnSpan(box, 2);
vbox.getChildren().setAll(hintPane, box); if (!IntegrityChecker.isOfficial()) {
HintPane unofficialHint = new HintPane(MessageDialogPane.MessageType.WARNING);
unofficialHint.setText(i18n("unofficial.hint"));
vbox.getChildren().add(unofficialHint);
}
vbox.getChildren().addAll(hintPane, box);
btnAccept.setDisable(false); btnAccept.setDisable(false);
} else { } else {
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING); HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
hintPane.setText(i18n("account.methods.microsoft.snapshot")); hintPane.setSegment(i18n("account.methods.microsoft.snapshot"));
vbox.getChildren().setAll(hintPane); JFXHyperlink officialWebsite = new JFXHyperlink(i18n("account.methods.microsoft.snapshot.website"));
officialWebsite.setExternalLink("https://hmcl.huangyuhui.net");
vbox.getChildren().setAll(hintPane, officialWebsite);
btnAccept.setDisable(true); btnAccept.setDisable(true);
} }
@@ -389,6 +399,15 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
int rowIndex = 0; int rowIndex = 0;
if (!IntegrityChecker.isOfficial() && !(factory instanceof OfflineAccountFactory)) {
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
hintPane.setSegment(i18n("unofficial.hint"));
GridPane.setColumnSpan(hintPane, 2);
add(hintPane, 0, rowIndex);
rowIndex++;
}
if (factory instanceof BoundAuthlibInjectorAccountFactory) { if (factory instanceof BoundAuthlibInjectorAccountFactory) {
this.server = ((BoundAuthlibInjectorAccountFactory) factory).getServer(); this.server = ((BoundAuthlibInjectorAccountFactory) factory).getServer();

View File

@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.construct;
import javafx.scene.control.Hyperlink; import javafx.scene.control.Hyperlink;
import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.SVG;
public class JFXHyperlink extends Hyperlink { public class JFXHyperlink extends Hyperlink {
@@ -34,5 +35,9 @@ public class JFXHyperlink extends Hyperlink {
setGraphic(SVG.launchOutline(Theme.blackFillBinding(), 16, 16)); setGraphic(SVG.launchOutline(Theme.blackFillBinding(), 16, 16));
} }
public void setExternalLink(String externalLink) {
this.setOnAction(e -> FXUtils.openLink(externalLink));
}
} }

View File

@@ -17,7 +17,9 @@
*/ */
package org.jackhuang.hmcl.upgrade; package org.jackhuang.hmcl.upgrade;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.io.JarUtils; import org.jackhuang.hmcl.util.io.JarUtils;
@@ -65,7 +67,7 @@ public final class IntegrityChecker {
byte[] signature = null; byte[] signature = null;
Map<String, byte[]> fileFingerprints = new TreeMap<>(); Map<String, byte[]> fileFingerprints = new TreeMap<>();
try (ZipFile zip = new ZipFile(jarPath.toFile())) { try (ZipFile zip = new ZipFile(jarPath.toFile())) {
for (ZipEntry entry : zip.stream().toArray(ZipEntry[]::new)) { for (ZipEntry entry : Lang.toIterable(zip.entries())) {
String filename = entry.getName(); String filename = entry.getName();
try (InputStream in = zip.getInputStream(entry)) { try (InputStream in = zip.getInputStream(entry)) {
if (in == null) { if (in == null) {
@@ -106,16 +108,22 @@ public final class IntegrityChecker {
} }
} }
private static Boolean selfVerified = null; private static volatile Boolean selfVerified = null;
/** /**
* Checks whether the current application is verified. * Checks whether the current application is verified.
* This method is blocking. * This method is blocking.
*/ */
public static synchronized boolean isSelfVerified() { public static boolean isSelfVerified() {
if (selfVerified != null) { if (selfVerified != null) {
return selfVerified; return selfVerified;
} }
synchronized (IntegrityChecker.class) {
if (selfVerified != null) {
return selfVerified;
}
try { try {
verifySelf(); verifySelf();
LOG.info("Successfully verified current JAR"); LOG.info("Successfully verified current JAR");
@@ -124,8 +132,14 @@ public final class IntegrityChecker {
LOG.log(Level.WARNING, "Failed to verify myself, is the JAR corrupt?", e); LOG.log(Level.WARNING, "Failed to verify myself, is the JAR corrupt?", e);
selfVerified = false; selfVerified = false;
} }
return selfVerified; return selfVerified;
} }
}
public static boolean isOfficial() {
return isSelfVerified() || (Metadata.GITHUB_SHA != null && Metadata.BUILD_CHANNEL.equals("nightly"));
}
private static void verifySelf() throws IOException { private static void verifySelf() throws IOException {
Path self = JarUtils.thisJar().orElseThrow(() -> new IOException("Failed to find current HMCL location")); Path self = JarUtils.thisJar().orElseThrow(() -> new IOException("Failed to find current HMCL location"));

View File

@@ -112,7 +112,8 @@ account.methods.microsoft.manual=Your device code is <b>%1$s</b>, please click h
If the token used to log in to the Microsoft account is leaked, you can click on "Unauthorize the account" to deauthorize it. If the token used to log in to the Microsoft account is leaked, you can click on "Unauthorize the account" to deauthorize it.
account.methods.microsoft.makegameidsettings=Create Profile account.methods.microsoft.makegameidsettings=Create Profile
account.methods.microsoft.profile=Account Profile account.methods.microsoft.profile=Account Profile
account.methods.microsoft.snapshot=HMCL Snapshot version does not support Microsoft login, please use the stable version or beta version to log in. account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build for login.
account.methods.microsoft.snapshot.website=Official Website
account.methods.microsoft.waiting_browser=Waiting for login to complete in the opened window... account.methods.microsoft.waiting_browser=Waiting for login to complete in the opened window...
account.methods.offline=Offline account.methods.offline=Offline
account.methods.offline.name.special_characters=Recommended to use letters, numbers and underscores for naming account.methods.offline.name.special_characters=Recommended to use letters, numbers and underscores for naming
@@ -1161,6 +1162,8 @@ sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher which allows
system.architecture=Architecture system.architecture=Architecture
system.operating_system=Operating System system.operating_system=Operating System
unofficial.hint=You are using an unofficial build of HMCL. We cannot guarantee its security.
update=Update update=Update
update.accept=Update update.accept=Update
update.changelog=Changelog update.changelog=Changelog

View File

@@ -107,7 +107,7 @@ account.methods.microsoft.manual=El código de su dispositivo es <b>%1$s</b>, po
\n\ \n\
Si el token utilizado para acceder a la cuenta de Microsoft se ha filtrado, puedes hacer clic en "Desautorizar la cuenta" para desautorizarla. Si el token utilizado para acceder a la cuenta de Microsoft se ha filtrado, puedes hacer clic en "Desautorizar la cuenta" para desautorizarla.
account.methods.microsoft.profile=Perfil de la cuenta account.methods.microsoft.profile=Perfil de la cuenta
account.methods.microsoft.snapshot=La versión Snapshot de HMCL no admite el inicio de sesión de Microsoft, por favor, utilice la versión estable o la versión beta para iniciar sesión. account.methods.microsoft.snapshot=Estás usando una construcción no oficial de hmcls, por favor descargue la construcción oficial para iniciar sesión en microsoft.
account.methods.microsoft.waiting_browser=Esperando a que se complete el inicio de sesión en la ventana abierta... account.methods.microsoft.waiting_browser=Esperando a que se complete el inicio de sesión en la ventana abierta...
account.methods.offline=Sin conexión account.methods.offline=Sin conexión
account.methods.offline.uuid=UUID account.methods.offline.uuid=UUID

View File

@@ -100,7 +100,7 @@ account.methods.microsoft.makegameidsettings=プロファイルを作成
account.methods.microsoft.hint=「ログイン」ボタンをクリックして、新しく開いたブラウザウィンドウでログインプロセスを続行する必要があります。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。 account.methods.microsoft.hint=「ログイン」ボタンをクリックして、新しく開いたブラウザウィンドウでログインプロセスを続行する必要があります。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。
account.methods.microsoft.manual=「ログイン」ボタンをクリックした後、新しく開いたブラウザウィンドウで認証を完了する必要があります。ブラウザウィンドウが表示されない場合は、ここをクリックしてURLをコピーし、ブラウザで手動で開くことができます。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。 account.methods.microsoft.manual=「ログイン」ボタンをクリックした後、新しく開いたブラウザウィンドウで認証を完了する必要があります。ブラウザウィンドウが表示されない場合は、ここをクリックしてURLをコピーし、ブラウザで手動で開くことができます。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。
account.methods.microsoft.profile=アカウントプロファイル.. account.methods.microsoft.profile=アカウントプロファイル..
account.methods.microsoft.snapshot=HMCLスナップショットバージョンはMicrosoftログインをサポートしていません account.methods.microsoft.snapshot=非公式構築 HMCL を使用しているので、公式構築をダウンロードしてマイクロソフトにログインしてください
account.methods.microsoft.waiting_browser=開いているブラウザウィンドウで認証を待機しています... account.methods.microsoft.waiting_browser=開いているブラウザウィンドウで認証を待機しています...
account.methods.offline=オフライン account.methods.offline=オフライン
account.methods.offline.uuid=UUID account.methods.offline.uuid=UUID

View File

@@ -100,7 +100,7 @@ account.methods.microsoft.hint=Вам необходимо нажать кноп
account.methods.microsoft.manual=После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию. account.methods.microsoft.manual=После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
account.methods.microsoft.profile=Профиль аккаунта... account.methods.microsoft.profile=Профиль аккаунта...
account.methods.forgot_password=ЗАБЫЛ ПАРОЛЬ account.methods.forgot_password=ЗАБЫЛ ПАРОЛЬ
account.methods.microsoft.snapshot=Лаунчер версии снапшот не поддерживает авторизацию через систему Microsoft. account.methods.microsoft.snapshot=Вы используете неофициальное построение HMCL, загрузите официальное построение для входа в Microsoft.
account.methods.microsoft.waiting_browser=Ожидание авторизации в открытом окне браузера... account.methods.microsoft.waiting_browser=Ожидание авторизации в открытом окне браузера...
account.methods.offline=Офлайн account.methods.offline=Офлайн
account.methods.offline.uuid=UUID account.methods.offline.uuid=UUID

View File

@@ -108,7 +108,8 @@ account.methods.microsoft.hint=你需要按照以下步驟添加:\n1.點擊“
account.methods.microsoft.manual=你需要按照以下步驟添加:\n1.點擊“登入”按鈕\n2.在網頁瀏覽器顯示的網站中輸入該代碼:<b>%1$s</b>(已自動拷貝,請點此再次拷貝),並點擊“下一步”\n3.按照網站的提示登入\n4.當網站顯示“ 是否允許此應用訪問你的信息?”的標識時,請點擊“是”\n5.當網站提示“大功告成”的標示時,即表示登入成功!然後等待賬戶完成添加即可\n-若網站提示“出现错误”的標識,請嘗試重新按照以上步驟登入\n-若賬戶添加失敗,請嘗試重新按照以上步驟登入\n-若網站未能打開,請手動在網頁瀏覽器中打開:%2$s\n<b>-若設備網絡環境不佳,可能登陸網站加載很慢甚至無法加載,此時請使用 VPN 或代理伺服器並重試</b>\n-若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期 account.methods.microsoft.manual=你需要按照以下步驟添加:\n1.點擊“登入”按鈕\n2.在網頁瀏覽器顯示的網站中輸入該代碼:<b>%1$s</b>(已自動拷貝,請點此再次拷貝),並點擊“下一步”\n3.按照網站的提示登入\n4.當網站顯示“ 是否允許此應用訪問你的信息?”的標識時,請點擊“是”\n5.當網站提示“大功告成”的標示時,即表示登入成功!然後等待賬戶完成添加即可\n-若網站提示“出现错误”的標識,請嘗試重新按照以上步驟登入\n-若賬戶添加失敗,請嘗試重新按照以上步驟登入\n-若網站未能打開,請手動在網頁瀏覽器中打開:%2$s\n<b>-若設備網絡環境不佳,可能登陸網站加載很慢甚至無法加載,此時請使用 VPN 或代理伺服器並重試</b>\n-若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期
account.methods.microsoft.profile=帳戶設置頁 account.methods.microsoft.profile=帳戶設置頁
account.methods.forgot_password=忘記密碼 account.methods.forgot_password=忘記密碼
account.methods.microsoft.snapshot=HMCL 快照版暫不提供微軟登入支持,請使用穩定版或測試版進行登入。 account.methods.microsoft.snapshot=你正在使用非官方構建的 HMCL請下載官方構建進行微軟登入。
account.methods.microsoft.snapshot.website=官方網站
account.methods.microsoft.waiting_browser=等待在新打開的瀏覽器窗口中完成登入... account.methods.microsoft.waiting_browser=等待在新打開的瀏覽器窗口中完成登入...
account.methods.offline=離線模式 account.methods.offline=離線模式
account.methods.offline.name.special_characters=建議使用英文字符、數字以及下劃線命名! account.methods.offline.name.special_characters=建議使用英文字符、數字以及下劃線命名!
@@ -997,6 +998,8 @@ sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟
system.architecture=架構 system.architecture=架構
system.operating_system=操作系統 system.operating_system=操作系統
unofficial.hint=你正在使用非官方構建的 HMCL我們無法保證其安全性請注意甄別。
update=啟動器更新 update=啟動器更新
update.accept=更新 update.accept=更新
update.changelog=更新日誌 update.changelog=更新日誌

View File

@@ -108,7 +108,8 @@ account.methods.microsoft.hint=你需要按照以下步骤添加:\n1.点击“
account.methods.microsoft.manual=你需要按照以下步骤添加:\n1.点击“登录”按钮\n2.在网页浏览器显示的网站中输入:<b>%1$s</b>(已自动拷贝,点此再次拷贝),并点击“下一步”\n3.按照网站的提示登录\n4.当网站提示“是否允许此应用访问你的信息?”的标识时,请点击“是”\n5.当网站提示“大功告成”的标识时,即表示登录成功!然后等待账户完成添加即可\n-若网站提示“出现错误”的标识时,请重新按照以上步骤重新添加\n-若账户添加失败,请重新按照以上步骤重新添加\n-若网站未能显示,请手动在网页浏览器中打开:%2$s\n<b>-若设备网络环境不佳,可能登录网站加载很慢甚至无法加载,此时请使用 VPN 或代理服务器并重试</b>\n-若登录微软账户的 Token (不是密码)泄露,可点击下方“解除账户绑定”解除登录授权,然后等待 Token 过期 account.methods.microsoft.manual=你需要按照以下步骤添加:\n1.点击“登录”按钮\n2.在网页浏览器显示的网站中输入:<b>%1$s</b>(已自动拷贝,点此再次拷贝),并点击“下一步”\n3.按照网站的提示登录\n4.当网站提示“是否允许此应用访问你的信息?”的标识时,请点击“是”\n5.当网站提示“大功告成”的标识时,即表示登录成功!然后等待账户完成添加即可\n-若网站提示“出现错误”的标识时,请重新按照以上步骤重新添加\n-若账户添加失败,请重新按照以上步骤重新添加\n-若网站未能显示,请手动在网页浏览器中打开:%2$s\n<b>-若设备网络环境不佳,可能登录网站加载很慢甚至无法加载,此时请使用 VPN 或代理服务器并重试</b>\n-若登录微软账户的 Token (不是密码)泄露,可点击下方“解除账户绑定”解除登录授权,然后等待 Token 过期
account.methods.microsoft.profile=帐户设置页 account.methods.microsoft.profile=帐户设置页
account.methods.forgot_password=忘记密码 account.methods.forgot_password=忘记密码
account.methods.microsoft.snapshot=HMCL 快照版暂不提供微软登录支持,请使用推荐版本(稳定版)或测试版进行登录 account.methods.microsoft.snapshot=你正在使用非官方构建的 HMCL请下载官方构建进行微软登录
account.methods.microsoft.snapshot.website=官方网站
account.methods.microsoft.waiting_browser=等待在新打开的浏览器窗口中完成登录…… account.methods.microsoft.waiting_browser=等待在新打开的浏览器窗口中完成登录……
account.methods.offline=离线模式 account.methods.offline=离线模式
account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名! account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名!
@@ -999,6 +1000,8 @@ sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代
system.architecture=架构 system.architecture=架构
system.operating_system=操作系统 system.operating_system=操作系统
unofficial.hint=你正在使用非官方构建的 HMCL我们无法保证其安全性请注意甄别。
update=启动器更新 update=启动器更新
update.accept=更新 update.accept=更新
update.changelog=更新日志 update.changelog=更新日志