[重要] 实现 #3095 微软登录界面对 XBox 400 错误给予提示 (#3121)

* Fix #3095

* Fix

* Update I18N_zh_CN.properties

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
This commit is contained in:
Burning_TNT 2024-07-20 01:56:13 +08:00 committed by GitHub
parent 1e472ea463
commit b7a5b484b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 57 additions and 24 deletions

View File

@ -54,17 +54,19 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating; import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.Lang.immutableListOf; import static org.jackhuang.hmcl.util.Lang.immutableListOf;
import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/** /**
* @author huangyuhui * @author huangyuhui
*/ */
public final class Accounts { public final class Accounts {
private Accounts() {} private Accounts() {
}
private static final AuthlibInjectorArtifactProvider AUTHLIB_INJECTOR_DOWNLOADER = createAuthlibInjectorArtifactProvider(); private static final AuthlibInjectorArtifactProvider AUTHLIB_INJECTOR_DOWNLOADER = createAuthlibInjectorArtifactProvider();
private static void triggerAuthlibInjectorUpdateCheck() { private static void triggerAuthlibInjectorUpdateCheck() {
if (AUTHLIB_INJECTOR_DOWNLOADER instanceof AuthlibInjectorDownloader) { if (AUTHLIB_INJECTOR_DOWNLOADER instanceof AuthlibInjectorDownloader) {
Schedulers.io().execute(() -> { Schedulers.io().execute(() -> {
@ -87,6 +89,7 @@ public final class Accounts {
// ==== login type / account factory mapping ==== // ==== login type / account factory mapping ====
private static final Map<String, AccountFactory<?>> type2factory = new HashMap<>(); private static final Map<String, AccountFactory<?>> type2factory = new HashMap<>();
private static final Map<AccountFactory<?>, String> factory2type = new HashMap<>(); private static final Map<AccountFactory<?>, String> factory2type = new HashMap<>();
static { static {
type2factory.put("offline", FACTORY_OFFLINE); type2factory.put("offline", FACTORY_OFFLINE);
type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR); type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR);
@ -130,7 +133,7 @@ public final class Accounts {
private static final String GLOBAL_PREFIX = "$GLOBAL:"; private static final String GLOBAL_PREFIX = "$GLOBAL:";
private static final ObservableList<Map<Object, Object>> globalAccountStorages = FXCollections.observableArrayList(); private static final ObservableList<Map<Object, Object>> globalAccountStorages = FXCollections.observableArrayList();
private static final ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account }); private static final ObservableList<Account> accounts = observableArrayList(account -> new Observable[]{account});
private static final ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<>(Accounts.class, "selectedAccount"); private static final ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<>(Accounts.class, "selectedAccount");
/** /**
@ -465,6 +468,8 @@ public final class Accounts {
} else { } else {
return i18n("account.methods.microsoft.error.unknown", errorCode); return i18n("account.methods.microsoft.error.unknown", errorCode);
} }
} else if (exception instanceof MicrosoftService.XBox400Exception) {
return i18n("account.methods.microsoft.error.wrong_verify_method");
} else if (exception instanceof MicrosoftService.NoMinecraftJavaEditionProfileException) { } else if (exception instanceof MicrosoftService.NoMinecraftJavaEditionProfileException) {
return i18n("account.methods.microsoft.error.no_character"); return i18n("account.methods.microsoft.error.no_character");
} else if (exception instanceof MicrosoftService.NoXuiException) { } else if (exception instanceof MicrosoftService.NoXuiException) {

View File

@ -107,6 +107,7 @@ account.methods.microsoft.error.country_unavailable=Xbox Live is not available i
account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please create one before continuing. account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please create one before continuing.
account.methods.microsoft.error.no_character=Your account does not own the Minecraft Java Edition.\nThe game profile may not have been created,\nplease click the link above to create it. account.methods.microsoft.error.no_character=Your account does not own the Minecraft Java Edition.\nThe game profile may not have been created,\nplease click the link above to create it.
account.methods.microsoft.error.unknown=Failed to log in, error: %d. account.methods.microsoft.error.unknown=Failed to log in, error: %d.
account.methods.microsoft.error.wrong_verify_method=Please log in using your account & password on the Microsoft account login page. Please do not use a verification code to log in.
account.methods.microsoft.logging_in=Logging in... account.methods.microsoft.logging_in=Logging in...
account.methods.microsoft.hint=Please click on the "login" button, and copy the code shown here later to finish the login process in the opened browser window.\n\ account.methods.microsoft.hint=Please click on the "login" button, and copy the code shown here later to finish the login process in the opened browser window.\n\
\n\ \n\

View File

@ -106,6 +106,7 @@ account.methods.microsoft.error.country_unavailable=你所在的國家或地區
account.methods.microsoft.error.missing_xbox_account=你的微軟帳號尚未關聯 XBox 帳號,你必須先創建 XBox 帳號,才能登入遊戲。 account.methods.microsoft.error.missing_xbox_account=你的微軟帳號尚未關聯 XBox 帳號,你必須先創建 XBox 帳號,才能登入遊戲。
account.methods.microsoft.error.no_character=該帳戶未包含 Minecraft Java 版購買記錄\n可能未創建遊戲檔案請點擊上方鏈接創建 account.methods.microsoft.error.no_character=該帳戶未包含 Minecraft Java 版購買記錄\n可能未創建遊戲檔案請點擊上方鏈接創建
account.methods.microsoft.error.unknown=登入失敗,錯誤碼:%d account.methods.microsoft.error.unknown=登入失敗,錯誤碼:%d
account.methods.microsoft.error.wrong_verify_method=請在 Microsoft 帳號登陸頁面使用帳號 + 密碼登入。請不要使用驗證碼登入。
account.methods.microsoft.logging_in=登入中... account.methods.microsoft.logging_in=登入中...
account.methods.microsoft.makegameidsettings=創建檔案/編輯檔案名稱 account.methods.microsoft.makegameidsettings=創建檔案/編輯檔案名稱
account.methods.microsoft.hint=你需要按照以下步驟添加賬戶:\n\ account.methods.microsoft.hint=你需要按照以下步驟添加賬戶:\n\

View File

@ -101,12 +101,13 @@ account.methods.microsoft=微软账户
account.methods.microsoft.birth=如何修改账户出生日期 account.methods.microsoft.birth=如何修改账户出生日期
account.methods.microsoft.close_page=已完成微软账户授权,接下来启动器还需要完成剩余登录步骤。你已经可以关闭本页面了。 account.methods.microsoft.close_page=已完成微软账户授权,接下来启动器还需要完成剩余登录步骤。你已经可以关闭本页面了。
account.methods.microsoft.deauthorize=解除账户授权 account.methods.microsoft.deauthorize=解除账户授权
account.methods.microsoft.error.add_family=由于你未满 18 岁,你的账户必须被加入到家庭中才能登录游戏。你也可以点击上方【账户设置页】更改你的账户的出生日期,使年龄满 18 岁以上以继续登录。\n你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.error.add_family=请点击上方【账户设置页】更改你的账户的出生日期,使年龄满 18 岁以上。或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.add_family_probably=检查你的账户设置,如果年龄未满 18 岁,你的账户必须被加入到家庭中才能登录游戏。你也可以点击上方链接更改你的账户的出生日期,使年龄满 18 岁以上以继续登录。\n你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.error.add_family_probably=点击上方【账户设置页】更改你的账户的出生日期,使年龄满 18 岁以上。或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 XBox Live 的支持。 account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 XBox Live 的支持。
account.methods.microsoft.error.missing_xbox_account=你的微软账户尚未关联 XBox 账户,你必须先创建 XBox 账户,才能登录游戏。\n你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.error.missing_xbox_account=请点击上方【创建档案】关联 XBox 账户。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.no_character=该账户未包含 Minecraft Java 版购买记录\n若已购买则可能未创建游戏档案请点击上方链接创建。\n若确定该账户完成了上述步骤请先在 Minecraft 官网minecraft.net登录一次账户然后再在启动器登录。\n你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.error.no_character=请确认你购买了 Minecraft: Java 版。若已购买,该账户未包含 Minecraft Java 版购买记录\n请点击【创建档案】创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.unknown=登录失败,错误码:%d account.methods.microsoft.error.unknown=未知问题。错误码:%d。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.wrong_verify_method=请在 Microsoft 账户登陆页面使用账户 + 密码登录。请不要使用验证码登录。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.logging_in=登录中…… account.methods.microsoft.logging_in=登录中……
account.methods.microsoft.makegameidsettings=创建档案/编辑档案名称 account.methods.microsoft.makegameidsettings=创建档案/编辑档案名称
account.methods.microsoft.hint=你需要按照以下步骤添加账户:\n\ account.methods.microsoft.hint=你需要按照以下步骤添加账户:\n\

View File

@ -21,8 +21,10 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.OAuth; import org.jackhuang.hmcl.auth.OAuth;
import org.jackhuang.hmcl.auth.ServerDisconnectException;
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException; import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
import org.jackhuang.hmcl.auth.yggdrasil.Texture; import org.jackhuang.hmcl.auth.yggdrasil.Texture;
@ -44,8 +46,8 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.threadPool; import static org.jackhuang.hmcl.util.Lang.threadPool;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class MicrosoftService { public class MicrosoftService {
private static final String SCOPE = "XboxLive.signin offline_access"; private static final String SCOPE = "XboxLive.signin offline_access";
@ -123,17 +125,25 @@ public class MicrosoftService {
String uhs = getUhs(xboxResponse, null); String uhs = getUhs(xboxResponse, null);
// Authenticate Minecraft with XSTS XBoxLiveAuthenticationResponse minecraftXstsResponse;
XBoxLiveAuthenticationResponse minecraftXstsResponse = HttpRequest try {
.POST("https://xsts.auth.xboxlive.com/xsts/authorize") minecraftXstsResponse = HttpRequest
.json(mapOf( .POST("https://xsts.auth.xboxlive.com/xsts/authorize")
pair("Properties", .json(mapOf(
mapOf(pair("SandboxId", "RETAIL"), pair("Properties",
pair("UserTokens", Collections.singletonList(xboxResponse.token)))), mapOf(pair("SandboxId", "RETAIL"),
pair("RelyingParty", "rp://api.minecraftservices.com/"), pair("TokenType", "JWT"))) pair("UserTokens", Collections.singletonList(xboxResponse.token)))),
.ignoreHttpErrorCode(401) pair("RelyingParty", "rp://api.minecraftservices.com/"), pair("TokenType", "JWT")))
.retry(5) .ignoreHttpErrorCode(401)
.getJson(XBoxLiveAuthenticationResponse.class); .retry(5)
.getJson(XBoxLiveAuthenticationResponse.class);
} catch (ResponseCodeException e) {
if (e.getResponseCode() == 400) {
throw new XBox400Exception();
}
throw e;
}
getUhs(minecraftXstsResponse, uhs); getUhs(minecraftXstsResponse, uhs);
@ -290,6 +300,9 @@ public class MicrosoftService {
public static final long ADD_FAMILY = 2148916238L; public static final long ADD_FAMILY = 2148916238L;
} }
public static class XBox400Exception extends AuthenticationException {
}
public static class NoMinecraftJavaEditionProfileException extends AuthenticationException { public static class NoMinecraftJavaEditionProfileException extends AuthenticationException {
} }

View File

@ -187,13 +187,18 @@ public abstract class HttpRequest {
os.write(bytes); os.write(bytes);
} }
URL url = new URL(this.url);
if (responseCodeTester != null) { if (responseCodeTester != null) {
responseCodeTester.accept(new URL(url), con.getResponseCode()); responseCodeTester.accept(url, con.getResponseCode());
} else { } else {
if (con.getResponseCode() / 100 != 2) { if (con.getResponseCode() / 100 != 2) {
if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) { if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) {
String data = NetworkUtils.readData(con); try {
throw new ResponseCodeException(new URL(url), con.getResponseCode(), data); throw new ResponseCodeException(url, con.getResponseCode(), NetworkUtils.readData(con));
} catch (IOException e) {
throw new ResponseCodeException(url, con.getResponseCode(), e);
}
} }
} }
} }

View File

@ -33,6 +33,13 @@ public final class ResponseCodeException extends IOException {
this.data = null; this.data = null;
} }
public ResponseCodeException(URL url, int responseCode, Throwable cause) {
super("Unable to request url " + url + ", response code: " + responseCode, cause);
this.url = url;
this.responseCode = responseCode;
this.data = null;
}
public ResponseCodeException(URL url, int responseCode, String data) { public ResponseCodeException(URL url, int responseCode, String data) {
super("Unable to request url " + url + ", response code: " + responseCode + ", data: " + data); super("Unable to request url " + url + ", response code: " + responseCode + ", data: " + data);
this.url = url; this.url = url;