From 9ed44d681f325a2b15c4f52261fdfb51245c0e1b Mon Sep 17 00:00:00 2001 From: FlorianMichael Date: Fri, 11 Oct 2024 15:20:24 +0200 Subject: [PATCH] Add Bedrock Realms screen Move out connection code into combined ConnectUtil Refactor around AccountsSave to migrate bedrock accounts Extend VFPListEntry and VFPScreen Closes https://github.com/ViaVersion/ViaFabricPlus/issues/554 --- .../viafabricplus/save/impl/AccountsSave.java | 46 ++-- .../viafabricplus/screen/VFPListEntry.java | 11 +- .../viafabricplus/screen/VFPScreen.java | 8 +- .../screen/base/ServerListScreen.java | 15 ++ .../screen/classic4j/BetaCraftScreen.java | 10 +- .../classic4j/ClassiCubeServerListScreen.java | 18 +- .../realms/AcceptInvitationCodeScreen.java | 63 +++++ .../screen/realms/BedrockRealmsScreen.java | 243 ++++++++++++++++++ .../settings/impl/BedrockSettings.java | 11 +- .../viafabricplus/util/ConnectionUtil.java | 49 ++++ .../assets/viafabricplus/lang/en_us.json | 18 +- 11 files changed, 448 insertions(+), 44 deletions(-) create mode 100644 src/main/java/de/florianmichael/viafabricplus/screen/realms/AcceptInvitationCodeScreen.java create mode 100644 src/main/java/de/florianmichael/viafabricplus/screen/realms/BedrockRealmsScreen.java create mode 100644 src/main/java/de/florianmichael/viafabricplus/util/ConnectionUtil.java diff --git a/src/main/java/de/florianmichael/viafabricplus/save/impl/AccountsSave.java b/src/main/java/de/florianmichael/viafabricplus/save/impl/AccountsSave.java index 6c522ccf..c5cc67a4 100644 --- a/src/main/java/de/florianmichael/viafabricplus/save/impl/AccountsSave.java +++ b/src/main/java/de/florianmichael/viafabricplus/save/impl/AccountsSave.java @@ -23,8 +23,11 @@ import com.google.gson.JsonObject; import de.florianmichael.classic4j.model.classicube.account.CCAccount; import de.florianmichael.viafabricplus.ViaFabricPlus; import de.florianmichael.viafabricplus.save.AbstractSave; +import de.florianmichael.viafabricplus.settings.impl.BedrockSettings; import net.raphimc.minecraftauth.MinecraftAuth; import net.raphimc.minecraftauth.step.bedrock.session.StepFullBedrockSession; +import net.raphimc.minecraftauth.step.msa.StepMsaToken; +import net.raphimc.minecraftauth.step.xbl.session.StepInitialXblSession; public class AccountsSave extends AbstractSave { @@ -38,7 +41,7 @@ public class AccountsSave extends AbstractSave { @Override public void write(JsonObject object) { if (bedrockAccount != null) { - object.add("bedrock", MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.toJson(bedrockAccount)); + object.add("bedrockV2", BedrockSettings.BEDROCK_DEVICE_CODE_LOGIN.toJson(bedrockAccount)); } if (classicubeAccount != null) { object.add("classicube", classicubeAccount.asJson()); @@ -47,31 +50,37 @@ public class AccountsSave extends AbstractSave { @Override public void read(JsonObject object) { - if (object.has("bedrock")) { + handleAccount("bedrock", object, account -> { + // Use old login flow, then get refresh token and login via new flow + final StepFullBedrockSession.FullBedrockSession oldSession = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.fromJson(account); + final StepInitialXblSession.InitialXblSession xblSession = oldSession.getMcChain().getXblXsts().getInitialXblSession(); + + final StepMsaToken.RefreshToken refreshToken = new StepMsaToken.RefreshToken(xblSession.getMsaToken().getRefreshToken()); + bedrockAccount = BedrockSettings.BEDROCK_DEVICE_CODE_LOGIN.getFromInput(MinecraftAuth.createHttpClient(), refreshToken); + }); + handleAccount("bedrockV2", object, account -> bedrockAccount = BedrockSettings.BEDROCK_DEVICE_CODE_LOGIN.fromJson(account)); + handleAccount("classicube", object, account -> classicubeAccount = CCAccount.fromJson(account)); + } + + private void handleAccount(final String name, final JsonObject object, final AccountConsumer output) { + if (object.has(name)) { try { - bedrockAccount = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.fromJson(object.get("bedrock").getAsJsonObject()); + output.accept(object.get(name).getAsJsonObject()); } catch (Exception e) { - ViaFabricPlus.global().getLogger().error("Failed to read bedrock account!", e); - } - } - if (object.has("classicube")) { - try { - classicubeAccount = CCAccount.fromJson(object.get("classicube").getAsJsonObject()); - } catch (Exception e) { - ViaFabricPlus.global().getLogger().error("Failed to read classicube account!", e); + ViaFabricPlus.global().getLogger().error("Failed to read {} account!", name, e); } } } public StepFullBedrockSession.FullBedrockSession refreshAndGetBedrockAccount() { - if (bedrockAccount == null) return null; - + if (bedrockAccount == null) { + return null; + } try { - bedrockAccount = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.refresh(MinecraftAuth.createHttpClient(), bedrockAccount); + bedrockAccount = BedrockSettings.BEDROCK_DEVICE_CODE_LOGIN.refresh(MinecraftAuth.createHttpClient(), bedrockAccount); } catch (Throwable t) { throw new RuntimeException("Failed to refresh Bedrock chain data. Please re-login to Bedrock!", t); } - return bedrockAccount; } @@ -91,4 +100,11 @@ public class AccountsSave extends AbstractSave { this.classicubeAccount = classicubeAccount; } + @FunctionalInterface + interface AccountConsumer { + + void accept(JsonObject account) throws Exception; + + } + } diff --git a/src/main/java/de/florianmichael/viafabricplus/screen/VFPListEntry.java b/src/main/java/de/florianmichael/viafabricplus/screen/VFPListEntry.java index b389c009..f9195e76 100644 --- a/src/main/java/de/florianmichael/viafabricplus/screen/VFPListEntry.java +++ b/src/main/java/de/florianmichael/viafabricplus/screen/VFPListEntry.java @@ -62,18 +62,23 @@ public abstract class VFPListEntry extends AlwaysSelectedEntryListWidget.Entry (entryWidth - offset)) { final double time = (double) Util.getMeasuringTimeMs() / 1000.0; final double interpolateEnd = fontWidth - (entryWidth - offset - (SCISSORS_OFFSET + SLOT_MARGIN)); diff --git a/src/main/java/de/florianmichael/viafabricplus/screen/VFPScreen.java b/src/main/java/de/florianmichael/viafabricplus/screen/VFPScreen.java index 3af7c7bc..c6a27c32 100644 --- a/src/main/java/de/florianmichael/viafabricplus/screen/VFPScreen.java +++ b/src/main/java/de/florianmichael/viafabricplus/screen/VFPScreen.java @@ -163,10 +163,16 @@ public class VFPScreen extends Screen { */ public void renderSubtitle(final DrawContext context) { if (subtitle != null && subtitlePressAction == null) { - context.drawCenteredTextWithShadow(textRenderer, subtitle, width / 2, (textRenderer.fontHeight + 2) * 2 + 3, -1); + final int startY = (textRenderer.fontHeight + 2) * 2 + 3; + context.drawCenteredTextWithShadow(textRenderer, subtitle, width / 2, subtitleCentered() ? this.height / 2 - startY : startY, -1); } } + protected boolean subtitleCentered() { + // To be overriden + return false; + } + /** * Plays Minecraft's button click sound */ diff --git a/src/main/java/de/florianmichael/viafabricplus/screen/base/ServerListScreen.java b/src/main/java/de/florianmichael/viafabricplus/screen/base/ServerListScreen.java index 8fea8839..1d6cd11c 100644 --- a/src/main/java/de/florianmichael/viafabricplus/screen/base/ServerListScreen.java +++ b/src/main/java/de/florianmichael/viafabricplus/screen/base/ServerListScreen.java @@ -25,6 +25,7 @@ import de.florianmichael.viafabricplus.screen.VFPScreen; import de.florianmichael.viafabricplus.screen.classic4j.BetaCraftScreen; import de.florianmichael.viafabricplus.screen.classic4j.ClassiCubeLoginScreen; import de.florianmichael.viafabricplus.screen.classic4j.ClassiCubeServerListScreen; +import de.florianmichael.viafabricplus.screen.realms.BedrockRealmsScreen; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; @@ -78,6 +79,20 @@ public class ServerListScreen extends VFPScreen { betaCraftBuilder = betaCraftBuilder.tooltip(Tooltip.of(Text.translatable("betacraft.viafabricplus.warning"))); } this.addDrawableChild(betaCraftBuilder.build()); + + ButtonWidget.Builder bedrockRealmsBuilder = ButtonWidget.builder(BedrockRealmsScreen.INSTANCE.getTitle(), button -> { + BedrockRealmsScreen.INSTANCE.open(this); + }).position(this.width / 2 - 50, this.height / 2 - 25 + 40 + 6).size(98, 20); + final boolean missingAccount = ViaFabricPlus.global().getSaveManager().getAccountsSave().getBedrockAccount() == null; // Only check for presence, later validate + if (missingAccount) { + bedrockRealmsBuilder.tooltip(Tooltip.of(Text.translatable("bedrock_realms.viafabricplus.warning"))); + } + + ButtonWidget bedrockRealmsButton = bedrockRealmsBuilder.build(); + this.addDrawableChild(bedrockRealmsButton); + if (missingAccount) { + bedrockRealmsButton.active = false; + } } @Override diff --git a/src/main/java/de/florianmichael/viafabricplus/screen/classic4j/BetaCraftScreen.java b/src/main/java/de/florianmichael/viafabricplus/screen/classic4j/BetaCraftScreen.java index 228594b4..e40fd2ea 100644 --- a/src/main/java/de/florianmichael/viafabricplus/screen/classic4j/BetaCraftScreen.java +++ b/src/main/java/de/florianmichael/viafabricplus/screen/classic4j/BetaCraftScreen.java @@ -26,15 +26,13 @@ import de.florianmichael.viafabricplus.screen.VFPList; import de.florianmichael.viafabricplus.screen.VFPListEntry; import de.florianmichael.viafabricplus.screen.VFPScreen; import de.florianmichael.viafabricplus.screen.settings.TitleRenderer; +import de.florianmichael.viafabricplus.util.ConnectionUtil; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ConfirmLinkScreen; -import net.minecraft.client.gui.screen.multiplayer.ConnectScreen; import net.minecraft.client.gui.screen.option.ControlsListWidget; import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.network.ServerAddress; -import net.minecraft.client.network.ServerInfo; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -66,7 +64,6 @@ public class BetaCraftScreen extends VFPScreen { @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { - this.renderBackground(context, mouseX, mouseY, delta); super.render(context, mouseX, mouseY, delta); this.renderTitle(context); @@ -116,10 +113,7 @@ public class BetaCraftScreen extends VFPScreen { @Override public void mappedMouseClicked(double mouseX, double mouseY, int button) { - final ServerAddress serverAddress = ServerAddress.parse(server.socket()); - final ServerInfo entry = new ServerInfo(server.name(), serverAddress.getAddress(), ServerInfo.ServerType.OTHER); - - ConnectScreen.connect(MinecraftClient.getInstance().currentScreen, MinecraftClient.getInstance(), serverAddress, entry, false, null); + ConnectionUtil.connect(server.name(), server.socket()); super.mappedMouseClicked(mouseX, mouseY, button); } diff --git a/src/main/java/de/florianmichael/viafabricplus/screen/classic4j/ClassiCubeServerListScreen.java b/src/main/java/de/florianmichael/viafabricplus/screen/classic4j/ClassiCubeServerListScreen.java index ba97e047..72f4bb83 100644 --- a/src/main/java/de/florianmichael/viafabricplus/screen/classic4j/ClassiCubeServerListScreen.java +++ b/src/main/java/de/florianmichael/viafabricplus/screen/classic4j/ClassiCubeServerListScreen.java @@ -23,20 +23,16 @@ import de.florianmichael.classic4j.ClassiCubeHandler; import de.florianmichael.classic4j.api.LoginProcessHandler; import de.florianmichael.classic4j.model.classicube.server.CCServerInfo; import de.florianmichael.viafabricplus.ViaFabricPlus; -import de.florianmichael.viafabricplus.injection.access.IServerInfo; -import de.florianmichael.viafabricplus.protocoltranslator.impl.provider.vialegacy.ViaFabricPlusClassicMPPassProvider; import de.florianmichael.viafabricplus.screen.VFPList; import de.florianmichael.viafabricplus.screen.VFPListEntry; import de.florianmichael.viafabricplus.screen.VFPScreen; import de.florianmichael.viafabricplus.settings.impl.AuthenticationSettings; +import de.florianmichael.viafabricplus.util.ConnectionUtil; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.multiplayer.ConnectScreen; import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.network.ServerAddress; -import net.minecraft.client.network.ServerInfo; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.raphimc.vialegacy.api.LegacyProtocolVersion; @@ -131,16 +127,8 @@ public class ClassiCubeServerListScreen extends VFPScreen { @Override public void mappedMouseClicked(double mouseX, double mouseY, int button) { - final ServerAddress serverAddress = ServerAddress.parse(classiCubeServerInfo.ip() + ":" + classiCubeServerInfo.port()); - final ServerInfo entry = new ServerInfo(classiCubeServerInfo.name(), serverAddress.getAddress(), ServerInfo.ServerType.OTHER); - ViaFabricPlusClassicMPPassProvider.classicubeMPPass = classiCubeServerInfo.mpPass(); - - if (AuthenticationSettings.global().automaticallySelectCPEInClassiCubeServerList.getValue()) { - ((IServerInfo) entry).viaFabricPlus$forceVersion(LegacyProtocolVersion.c0_30cpe); - } - - ConnectScreen.connect(MinecraftClient.getInstance().currentScreen, MinecraftClient.getInstance(), serverAddress, entry, false, null); - super.mappedMouseClicked(mouseX, mouseY, button); + final boolean selectCPE = AuthenticationSettings.global().automaticallySelectCPEInClassiCubeServerList.getValue(); + ConnectionUtil.connect(classiCubeServerInfo.name(), classiCubeServerInfo.ip() + ":" + classiCubeServerInfo.port(), selectCPE ? LegacyProtocolVersion.c0_30cpe : null); } @Override diff --git a/src/main/java/de/florianmichael/viafabricplus/screen/realms/AcceptInvitationCodeScreen.java b/src/main/java/de/florianmichael/viafabricplus/screen/realms/AcceptInvitationCodeScreen.java new file mode 100644 index 00000000..25bc8c94 --- /dev/null +++ b/src/main/java/de/florianmichael/viafabricplus/screen/realms/AcceptInvitationCodeScreen.java @@ -0,0 +1,63 @@ +/* + * This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus + * Copyright (C) 2021-2024 FlorianMichael/EnZaXD and RK_01/RaphiMC + * Copyright (C) 2023-2024 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 . + */ + +package de.florianmichael.viafabricplus.screen.realms; + +import de.florianmichael.viafabricplus.screen.VFPScreen; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.text.Text; + +import java.util.function.Consumer; + +public class AcceptInvitationCodeScreen extends VFPScreen { + + private final Consumer serviceHandler; + + public AcceptInvitationCodeScreen(Consumer serviceHandler) { + super(Text.translatable("screen.viafabricplus.accept_invite"), true); + + this.serviceHandler = serviceHandler; + } + + @Override + protected void init() { + super.init(); + setupDefaultSubtitle(); + + final TextFieldWidget codeField = new TextFieldWidget(textRenderer, this.width / 2 - 100, this.height / 2 - 10, 200, 20, Text.empty()); + codeField.setPlaceholder(Text.translatable("base.viafabricplus.code")); + + this.addDrawableChild(codeField); + + this.addDrawableChild(ButtonWidget.builder(Text.translatable("base.viafabricplus.accept"), button -> { + this.serviceHandler.accept(codeField.getText()); + close(); + }).position(this.width / 2 - ButtonWidget.DEFAULT_WIDTH / 2, this.height / 2 + 20).build()); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + + this.renderTitle(context); + } + +} diff --git a/src/main/java/de/florianmichael/viafabricplus/screen/realms/BedrockRealmsScreen.java b/src/main/java/de/florianmichael/viafabricplus/screen/realms/BedrockRealmsScreen.java new file mode 100644 index 00000000..e0e2dbde --- /dev/null +++ b/src/main/java/de/florianmichael/viafabricplus/screen/realms/BedrockRealmsScreen.java @@ -0,0 +1,243 @@ +/* + * This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus + * Copyright (C) 2021-2024 FlorianMichael/EnZaXD and RK_01/RaphiMC + * Copyright (C) 2023-2024 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 . + */ + +package de.florianmichael.viafabricplus.screen.realms; + +import de.florianmichael.viafabricplus.ViaFabricPlus; +import de.florianmichael.viafabricplus.screen.VFPList; +import de.florianmichael.viafabricplus.screen.VFPListEntry; +import de.florianmichael.viafabricplus.screen.VFPScreen; +import de.florianmichael.viafabricplus.util.ConnectionUtil; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.option.ControlsListWidget; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.service.realms.BedrockRealmsService; +import net.raphimc.minecraftauth.service.realms.model.RealmsWorld; +import net.raphimc.minecraftauth.step.bedrock.session.StepFullBedrockSession; +import net.raphimc.viabedrock.api.BedrockProtocolVersion; +import net.raphimc.viabedrock.protocol.data.ProtocolConstants; +import org.apache.logging.log4j.Level; + +import java.awt.*; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class BedrockRealmsScreen extends VFPScreen { + + public static final BedrockRealmsScreen INSTANCE = new BedrockRealmsScreen(); + + private BedrockRealmsService service; + private List realmsWorlds; + + private SlotList slotList; + private ButtonWidget joinButton; + private ButtonWidget leaveButton; + + public BedrockRealmsScreen() { + super(Text.translatable("screen.viafabricplus.bedrock_realms"), true); + } + + @Override + protected void init() { + super.init(); + + if (realmsWorlds != null) { + createView(); + return; + } + + setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.availability_check")); + CompletableFuture.runAsync(this::loadRealms); + } + + private void loadRealms() { + final StepFullBedrockSession.FullBedrockSession account = ViaFabricPlus.global().getSaveManager().getAccountsSave().refreshAndGetBedrockAccount(); + if (account == null) { // Just in case... + setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.warning")); + return; + } + service = new BedrockRealmsService(MinecraftAuth.createHttpClient(), ProtocolConstants.BEDROCK_VERSION_NAME, account.getRealmsXsts()); + service.isAvailable().thenAccept(state -> { + if (state) { + service.getWorlds().thenAccept(realmsWorlds -> { + this.realmsWorlds = realmsWorlds; + createView(); + }).exceptionally(throwable -> error("Failed to load realm worlds", throwable)); + } else { + setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.unavailable")); + } + }).exceptionally(throwable -> error("Failed to check realms availability", throwable)); + } + + private Void error(final String message, final Throwable throwable) { + setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.error")); + ViaFabricPlus.global().getLogger().log(Level.ERROR, message, throwable); + return null; + } + + private void createView() { + if (!this.realmsWorlds.isEmpty()) { + setupDefaultSubtitle(); + } else { + setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.no_worlds")); + } + this.addDrawableChild(slotList = new SlotList(this.client, width, height, 3 + 3 /* start offset */ + (textRenderer.fontHeight + 2) * 3 /* title is 2 */, 30, (textRenderer.fontHeight + 2) * 4)); + + this.addDrawableChild(ButtonWidget.builder(ControlsListWidget.KeyBindingEntry.RESET_TEXT, button -> { + realmsWorlds = null; + client.setScreen(this); + }).position(width - 98 - 5, 5).size(98, 20).build()); + + final int slotWidth = 360 - 4; + + int xPos = width / 2 - slotWidth / 2; + this.addDrawableChild(joinButton = ButtonWidget.builder(Text.translatable("bedrock_realms.viafabricplus.join"), button -> { + final SlotEntry entry = (SlotEntry) slotList.getFocused(); + if (entry.realmsWorld.isExpired()) { + setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.expired")); + return; + } else if (!entry.realmsWorld.isCompatible()) { + setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.incompatible")); + return; + } + service.joinWorld(entry.realmsWorld).thenAccept(address -> { + client.execute(() -> ConnectionUtil.connect(address, BedrockProtocolVersion.bedrockLatest)); + }).exceptionally(throwable -> error("Failed to join realm", throwable)); + }).position(xPos, height - 20 - 5).size(115, 20).build()); + joinButton.active = false; + + xPos += 115 + 5; + this.addDrawableChild(leaveButton = ButtonWidget.builder(Text.translatable("bedrock_realms.viafabricplus.leave"), button -> { + final SlotEntry entry = (SlotEntry) slotList.getFocused(); + service.leaveInvitedRealm(entry.realmsWorld).thenAccept(unused -> { + this.realmsWorlds.remove(entry.realmsWorld); + INSTANCE.open(prevScreen); + }).exceptionally(throwable -> error("Failed to leave realm", throwable)); + }).position(xPos, height - 20 - 5).size(115, 20).build()); + leaveButton.active = false; + + xPos += 115 + 5; + this.addDrawableChild(ButtonWidget.builder(Text.translatable("bedrock_realms.viafabricplus.invite"), button -> { + final AcceptInvitationCodeScreen screen = new AcceptInvitationCodeScreen(code -> service.acceptInvite(code).thenAccept(world -> { + this.realmsWorlds.add(world); + INSTANCE.open(this); + }).exceptionally(throwable -> error("Failed to accept invite", throwable))); + screen.open(this); + }).position(xPos, height - 20 - 5).size(115, 20).build()); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + + this.renderTitle(context); + } + + @Override + public void tick() { + super.tick(); + + if (slotList != null && joinButton != null && leaveButton != null) { + joinButton.active = slotList.getFocused() instanceof SlotEntry; + leaveButton.active = slotList.getFocused() instanceof SlotEntry; + } + } + + @Override + protected boolean subtitleCentered() { + return slotList == null; + } + + public class SlotList extends VFPList { + private static double scrollAmount; + + public SlotList(MinecraftClient minecraftClient, int width, int height, int top, int bottom, int entryHeight) { + super(minecraftClient, width, height, top, bottom, entryHeight); + + for (RealmsWorld realmsWorld : BedrockRealmsScreen.this.realmsWorlds) { + this.addEntry(new SlotEntry(this, realmsWorld)); + } + initScrollAmount(scrollAmount); + } + + @Override + protected void updateSlotAmount(double amount) { + scrollAmount = amount; + } + + @Override + public int getRowWidth() { + return super.getRowWidth() + 140; + } + + } + + public class SlotEntry extends VFPListEntry { + + private final SlotList slotList; + private final RealmsWorld realmsWorld; + + public SlotEntry(SlotList slotList, RealmsWorld realmsWorld) { + this.slotList = slotList; + this.realmsWorld = realmsWorld; + } + + @Override + public Text getNarration() { + return Text.of(realmsWorld.getName()); + } + + @Override + public void mappedRender(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + + String name = ""; + final String ownerName = realmsWorld.getOwnerName(); + if (ownerName != null && !ownerName.trim().isEmpty()) { + name += ownerName + " - "; + } + final String worldName = realmsWorld.getName(); + if (worldName != null && !worldName.trim().isEmpty()) { + name += worldName; + } + name += " (" + realmsWorld.getState() + ")"; + + context.drawTextWithShadow(textRenderer, name, 3, 3, slotList.getFocused() == this ? Color.ORANGE.getRGB() : -1); + + String version = realmsWorld.getWorldType(); + final String activeVersion = realmsWorld.getActiveVersion(); + if (activeVersion != null && !activeVersion.trim().isEmpty()) { + version += " - " + activeVersion; + } + + context.drawTextWithShadow(textRenderer, version, entryWidth - textRenderer.getWidth(version) - 4 - 3, 3, -1); + + final String motd = realmsWorld.getMotd(); + if (motd != null) { + renderScrollableText(Text.of(motd), entryHeight - textRenderer.fontHeight - 3, 3 * 2); + } + } + + } + +} diff --git a/src/main/java/de/florianmichael/viafabricplus/settings/impl/BedrockSettings.java b/src/main/java/de/florianmichael/viafabricplus/settings/impl/BedrockSettings.java index 7d721d7d..c986f574 100644 --- a/src/main/java/de/florianmichael/viafabricplus/settings/impl/BedrockSettings.java +++ b/src/main/java/de/florianmichael/viafabricplus/settings/impl/BedrockSettings.java @@ -34,8 +34,10 @@ import net.minecraft.text.Text; import net.minecraft.util.Util; import net.raphimc.minecraftauth.MinecraftAuth; import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.bedrock.session.StepFullBedrockSession; import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode; import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCodeMsaCode; +import net.raphimc.minecraftauth.util.MicrosoftConstants; import net.raphimc.minecraftauth.util.logging.ConsoleLogger; import net.raphimc.minecraftauth.util.logging.ILogger; @@ -47,6 +49,13 @@ public class BedrockSettings extends SettingGroup { private static final BedrockSettings INSTANCE = new BedrockSettings(); + public static final AbstractStep BEDROCK_DEVICE_CODE_LOGIN = MinecraftAuth.builder() + .withClientId(MicrosoftConstants.BEDROCK_ANDROID_TITLE_ID).withScope(MicrosoftConstants.SCOPE_TITLE_AUTH) + .deviceCode() + .withDeviceToken("Android") + .sisuTitleAuthentication(MicrosoftConstants.BEDROCK_XSTS_RELYING_PARTY) + .buildMinecraftBedrockChainStep(true, true); + private Thread thread; private final ButtonSetting clickToSetBedrockAccount = new ButtonSetting(this, Text.translatable("bedrock_settings.viafabricplus.click_to_set_bedrock_account"), () -> { thread = new Thread(this::openBedrockAccountLogin); @@ -90,7 +99,7 @@ public class BedrockSettings extends SettingGroup { final MinecraftClient client = MinecraftClient.getInstance(); final Screen prevScreen = client.currentScreen; try { - accountsSave.setBedrockAccount(MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.getFromInput(GUI_LOGGER, MinecraftAuth.createHttpClient(), new StepMsaDeviceCode.MsaDeviceCodeCallback(msaDeviceCode -> { + accountsSave.setBedrockAccount(BEDROCK_DEVICE_CODE_LOGIN.getFromInput(GUI_LOGGER, MinecraftAuth.createHttpClient(), new StepMsaDeviceCode.MsaDeviceCodeCallback(msaDeviceCode -> { VFPScreen.setScreen(new ConfirmScreen(copyUrl -> { if (copyUrl) { client.keyboard.setClipboard(msaDeviceCode.getDirectVerificationUri()); diff --git a/src/main/java/de/florianmichael/viafabricplus/util/ConnectionUtil.java b/src/main/java/de/florianmichael/viafabricplus/util/ConnectionUtil.java new file mode 100644 index 00000000..09d57b23 --- /dev/null +++ b/src/main/java/de/florianmichael/viafabricplus/util/ConnectionUtil.java @@ -0,0 +1,49 @@ +/* + * This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus + * Copyright (C) 2021-2024 FlorianMichael/EnZaXD and RK_01/RaphiMC + * Copyright (C) 2023-2024 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 . + */ + +package de.florianmichael.viafabricplus.util; + +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import de.florianmichael.viafabricplus.injection.access.IServerInfo; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.multiplayer.ConnectScreen; +import net.minecraft.client.network.ServerAddress; +import net.minecraft.client.network.ServerInfo; + +public class ConnectionUtil { + + public static void connect(final String address, final ProtocolVersion version) { + connect(address, address, version); + } + + public static void connect(final String name, final String address) { + connect(name, address, null); + } + + public static void connect(final String name, final String address, final ProtocolVersion version) { + final ServerAddress serverAddress = ServerAddress.parse(address); + final ServerInfo entry = new ServerInfo(name, serverAddress.getAddress(), ServerInfo.ServerType.OTHER); + + if (version != null) { + ((IServerInfo) entry).viaFabricPlus$forceVersion(version); + } + ConnectScreen.connect(MinecraftClient.getInstance().currentScreen, MinecraftClient.getInstance(), serverAddress, entry, false, null); + } + +} diff --git a/src/main/resources/assets/viafabricplus/lang/en_us.json b/src/main/resources/assets/viafabricplus/lang/en_us.json index b09b6a02..a118f67d 100644 --- a/src/main/resources/assets/viafabricplus/lang/en_us.json +++ b/src/main/resources/assets/viafabricplus/lang/en_us.json @@ -28,6 +28,8 @@ "base.viafabricplus.name": "Name", "base.viafabricplus.password": "Password", "base.viafabricplus.login": "Login", + "base.viafabricplus.code": "Code", + "base.viafabricplus.accept": "Accept", "setting_group_name.viafabricplus.authentication": "Authentication", "setting_group_name.viafabricplus.visual": "Visual", @@ -41,6 +43,8 @@ "screen.viafabricplus.classicube_login": "Classicube Login", "screen.viafabricplus.classicube_mfa": "Classicube MFA", "screen.viafabricplus.settings": "Settings", + "screen.viafabricplus.bedrock_realms": "Bedrock Realms", + "screen.viafabricplus.accept_invite": "Accept Invite", "general_settings.viafabricplus.save_selected_protocol_version": "Save selected protocol version", "general_settings.viafabricplus.extra_information_in_debug_hud": "Show extra information in Debug HUD", @@ -147,5 +151,17 @@ "minecraftauth_library.viafabricplus.xblsisuauthentication": "Authenticating with Xbox Live using SISU...", "minecraftauth_library.viafabricplus.mcchain": "Authenticating with Minecraft Services...", "minecraftauth_library.viafabricplus.xblxststoken": "Requesting XSTS Token...", - "minecraftauth_library.viafabricplus.playfabtoken": "Authenticating with PlayFab..." + "minecraftauth_library.viafabricplus.playfabtoken": "Authenticating with PlayFab...", + "minecraftauth_library.viafabricplus.realmsxsts": "Requesting Realms XSTS Token...", + + "bedrock_realms.viafabricplus.warning": "This feature requires a Bedrock Edition account to be set in the settings!", + "bedrock_realms.viafabricplus.availability_check": "Checking availability...", + "bedrock_realms.viafabricplus.no_worlds": "No worlds found :(", + "bedrock_realms.viafabricplus.unavailable": "Your ViaFabricPlus is outdated and not compatible with Minecraft Realms.", + "bedrock_realms.viafabricplus.error": "An error occurred, see the logs for more information", + "bedrock_realms.viafabricplus.join": "Join realm", + "bedrock_realms.viafabricplus.leave": "Leave realm", + "bedrock_realms.viafabricplus.invite": "Accept invite code", + "bedrock_realms.viafabricplus.expired": "The Realm is expired", + "bedrock_realms.viafabricplus.incompatible": "The Realms Minecraft version is either too old or too new" }