Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1c802d2
update
CiiLu Jan 25, 2026
e773c71
update
CiiLu Jan 25, 2026
3e356f2
update
CiiLu Jan 25, 2026
8b97f89
update
CiiLu Jan 25, 2026
39ec4a2
update
CiiLu Jan 25, 2026
a8f3d52
update
CiiLu Jan 25, 2026
50b274f
update
CiiLu Jan 25, 2026
b392f4b
update
CiiLu Jan 25, 2026
0c7234e
update
CiiLu Jan 25, 2026
e3ccda8
update
CiiLu Jan 25, 2026
8cdb521
update
CiiLu Jan 25, 2026
1fa870f
update
CiiLu Jan 25, 2026
9a0be36
update
CiiLu Jan 25, 2026
fa28740
update
CiiLu Jan 25, 2026
88dc715
update
CiiLu Jan 25, 2026
ce27514
update
CiiLu Jan 25, 2026
34cf012
update
CiiLu Jan 25, 2026
9cc8f4a
update
CiiLu Jan 25, 2026
ffa97b0
update
CiiLu Jan 25, 2026
3b03e26
update
CiiLu Jan 25, 2026
a479b79
update
CiiLu Jan 25, 2026
b209c3b
update
CiiLu Jan 25, 2026
fda6c73
update
CiiLu Jan 25, 2026
ecae0b0
update
CiiLu Jan 25, 2026
2ccf290
update
CiiLu Jan 25, 2026
334a12d
update
CiiLu Jan 25, 2026
3720127
update
CiiLu Jan 25, 2026
e253b5b
Apply suggestions from code review
CiiLu Jan 25, 2026
3a9c72b
update
CiiLu Jan 25, 2026
7da9d9b
update
CiiLu Jan 25, 2026
5be2165
update
Glavo Jan 29, 2026
b1fe3d5
update
Glavo Jan 29, 2026
0e375e2
update
Glavo Jan 29, 2026
b8e564f
update
Glavo Jan 29, 2026
81974d8
Update HMCL/src/main/resources/assets/lang/I18N.properties
CiiLu Jan 30, 2026
d225bb9
update
CiiLu Jan 30, 2026
dd56e67
Merge branch 'HMCL-dev:main' into microsoft
CiiLu Feb 3, 2026
52fce1d
update
CiiLu Feb 3, 2026
63234ca
update
CiiLu Feb 3, 2026
e053ddd
update
CiiLu Feb 3, 2026
55e72cd
update
CiiLu Feb 3, 2026
4fd49ab
Merge remote-tracking branch 'upstream/main' into microsoft
Glavo Feb 9, 2026
d9db071
update
Glavo Feb 9, 2026
18ae99d
update
Glavo Feb 9, 2026
eed0e5e
update
Glavo Feb 9, 2026
dffc17e
update
Glavo Feb 9, 2026
c16eeec
Apply suggestion from @Copilot
Glavo Feb 9, 2026
bcd4151
update
Glavo Feb 9, 2026
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
24 changes: 12 additions & 12 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import org.jackhuang.hmcl.auth.OAuth;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
Expand All @@ -37,8 +37,8 @@

import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;

public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
private final int port;
Expand Down Expand Up @@ -104,8 +104,11 @@ public Response serve(IHTTPSession session) {
String html;
try {
html = IOUtils.readFullyAsString(OAuthServer.class.getResourceAsStream("/assets/microsoft_auth.html"))
.replace("%style%", Themes.getTheme().toColorScheme().toStyleSheet().replace("-monet", "--monet"))
.replace("%lang%", Locale.getDefault().toLanguageTag())
.replace("%close-page%", i18n("account.methods.microsoft.close_page"));
.replace("%success%", i18n("message.success"))
.replace("%ok%", i18n("button.ok"))
.replace("%close_page%", i18n("account.methods.microsoft.close_page"));
} catch (IOException e) {
LOG.error("Failed to load html", e);
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_HTML, "");
Expand All @@ -123,7 +126,8 @@ public Response serve(IHTTPSession session) {

public static class Factory implements OAuth.Callback {
public final EventManager<GrantDeviceCodeEvent> onGrantDeviceCode = new EventManager<>();
public final EventManager<OpenBrowserEvent> onOpenBrowser = new EventManager<>();
public final EventManager<OpenBrowserEvent> onOpenBrowserAuthorizationCode = new EventManager<>();
public final EventManager<OpenBrowserEvent> onOpenBrowserDevice = new EventManager<>();

@Override
public OAuth.Session startServer() throws IOException, AuthenticationException {
Expand All @@ -150,17 +154,13 @@ public void grantDeviceCode(String userCode, String verificationURI) {
}

@Override
public void openBrowser(String url) throws IOException {
public void openBrowser(OAuth.GrantFlow grantFlow, String url) throws IOException {
lastlyOpenedURL = url;

try {
Thread.sleep(1500);
} catch (InterruptedException ignored) {
switch (grantFlow) {
case AUTHORIZATION_CODE -> onOpenBrowserAuthorizationCode.fireEvent(new OpenBrowserEvent(this, url));
case DEVICE -> onOpenBrowserDevice.fireEvent(new OpenBrowserEvent(this, url));
}

FXUtils.openLink(url);

onOpenBrowser.fireEvent(new OpenBrowserEvent(this, url));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.ui.account.ClassicAccountLoginDialog;
import org.jackhuang.hmcl.ui.account.OAuthAccountLoginDialog;
import org.jackhuang.hmcl.ui.account.MicrosoftAccountLoginPane;

import java.util.Optional;
import java.util.concurrent.CancellationException;
Expand Down Expand Up @@ -49,10 +49,10 @@ public static AuthInfo logIn(Account account) throws CancellationException, Auth
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
runInFX(() -> {
OAuthAccountLoginDialog pane = new OAuthAccountLoginDialog((OAuthAccount) account, it -> {
MicrosoftAccountLoginPane pane = new MicrosoftAccountLoginPane(account, it -> {
res.set(it);
latch.countDown();
}, latch::countDown);
}, latch::countDown, false);
Controllers.dialog(pane);
});
latch.await();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public AccountListPageSkin(AccountListPage skinnable) {
microsoftItem.getStyleClass().add("navigation-drawer-item");
microsoftItem.setTitle(i18n("account.methods.microsoft"));
microsoftItem.setLeftIcon(SVG.MICROSOFT);
microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT)));
microsoftItem.setOnAction(e -> Controllers.dialog(new MicrosoftAccountLoginPane()));

AdvancedListItem offlineItem = new AdvancedListItem();
offlineItem.getStyleClass().add("navigation-drawer-item");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
import javafx.beans.NamedArg;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
Expand All @@ -45,7 +43,6 @@
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.game.OAuthServer;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.task.Schedulers;
Expand All @@ -54,7 +51,6 @@
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.WeakListenerHolder;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
import org.jackhuang.hmcl.util.StringUtils;
Expand Down Expand Up @@ -85,13 +81,11 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
private final JFXButton btnAccept;
private final SpinnerPane spinner;
private final Node body;

private final HBox actions;
private Node detailsPane; // AccountDetailsInputPane for Offline / Mojang / authlib-injector, Label for Microsoft
private final Pane detailsContainer;

private final BooleanProperty logging = new SimpleBooleanProperty();
private final ObjectProperty<OAuthServer.GrantDeviceCodeEvent> deviceCode = new SimpleObjectProperty<>();
private final WeakListenerHolder holder = new WeakListenerHolder();

private TaskExecutor loginTask;

Expand Down Expand Up @@ -146,10 +140,10 @@ public CreateAccountPane(AccountFactory<?> factory) {
btnCancel.setOnAction(e -> onCancel());
onEscPressed(this, btnCancel::fire);

HBox hbox = new HBox(spinner, btnCancel);
hbox.setAlignment(Pos.CENTER_RIGHT);
actions = new HBox(spinner, btnCancel);
actions.setAlignment(Pos.CENTER_RIGHT);

setActions(lblErrorMessage, hbox);
setActions(lblErrorMessage, actions);
}

if (showMethodSwitcher) {
Expand Down Expand Up @@ -226,7 +220,6 @@ private void onAccept() {

Runnable doCreate = () -> {
logging.set(true);
deviceCode.set(null);

loginTask = Task.supplyAsync(() -> factory.create(new DialogCharacterSelector(), username, password, null, additionalData))
.whenComplete(Schedulers.javafx(), account -> {
Expand Down Expand Up @@ -281,88 +274,22 @@ private void initDetailsPane() {
detailsContainer.getChildren().remove(detailsPane);
lblErrorMessage.setText("");
lblErrorMessage.setVisible(true);
actions.setVisible(true);
actions.setVisible(true);
}

if (factory == Accounts.FACTORY_MICROSOFT) {
VBox vbox = new VBox(8);
detailsPane = vbox;

if (Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
hintPane.setSegment(i18n("account.methods.microsoft.snapshot"));
vbox.getChildren().add(hintPane);
return;
}

if (!IntegrityChecker.isOfficial()) {
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
hintPane.setSegment(i18n("unofficial.hint"));
vbox.getChildren().add(hintPane);
}

VBox codeBox = new VBox(8);
Label hint = new Label(i18n("account.methods.microsoft.code"));
Label code = new Label();
code.setMouseTransparent(true);
code.setStyle("-fx-font-size: 24");
codeBox.getChildren().addAll(hint, code);
codeBox.setAlignment(Pos.CENTER);
vbox.getChildren().add(codeBox);

HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
HintPane errHintPane = new HintPane(MessageDialogPane.MessageType.ERROR);
errHintPane.setVisible(false);
errHintPane.setManaged(false);

codeBox.setVisible(false);
codeBox.setManaged(false);

FXUtils.onChangeAndOperate(deviceCode, deviceCode -> {
if (deviceCode != null) {
FXUtils.copyText(deviceCode.getUserCode());
code.setText(deviceCode.getUserCode());
hintPane.setSegment(i18n("account.methods.microsoft.manual", deviceCode.getVerificationUri()));
codeBox.setVisible(true);
codeBox.setManaged(true);
} else {
hintPane.setSegment(i18n("account.methods.microsoft.hint"));
codeBox.setVisible(false);
codeBox.setManaged(false);
}
hintPane.setText(i18n("account.methods.microsoft.hint"));
vbox.getChildren().addAll(new MicrosoftAccountLoginPane(true));
btnAccept.setOnAction(e -> {
fireEvent(new DialogCloseEvent());
Controllers.dialog(new MicrosoftAccountLoginPane());
});

lblErrorMessage.setVisible(false);
lblErrorMessage.textProperty().addListener((obs, oldVal, newVal) -> {
boolean hasError = !newVal.isEmpty();
errHintPane.setSegment(newVal);
errHintPane.setVisible(hasError);
errHintPane.setManaged(hasError);
hintPane.setVisible(!hasError);
hintPane.setManaged(!hasError);
codeBox.setVisible(!hasError && deviceCode.get() != null);
codeBox.setManaged(!hasError && deviceCode.get() != null);
});

FXUtils.onClicked(codeBox, () -> {
if (deviceCode.get() != null) FXUtils.copyText(deviceCode.get().getUserCode());
});

holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(value ->
runInFX(() -> deviceCode.set(value))
));

HBox linkBox = new HBox();
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
profileLink.setExternalLink("https://account.live.com/editprof.aspx");
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A");
JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password"));
forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx");
linkBox.getChildren().setAll(profileLink, purchaseLink, deauthorizeLink, forgotpasswordLink);

vbox.getChildren().addAll(hintPane, errHintPane, linkBox);
actions.setManaged(false);
actions.setVisible(false);
btnAccept.setDisable(false);
} else {
detailsPane = new AccountDetailsInputPane(factory, btnAccept::fire);
Expand Down
Loading