package ganarchy.friendcode.client; import net.minecraft.client.gui.screen.LoadingDisplay; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.screen.ScreenTexts; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Util; import org.apache.commons.codec.binary.Base32; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.Locale; public class WaitingForFriendCodeScreen extends Screen { private final I2PSamPinger samPinger; public WaitingForFriendCodeScreen(I2PSamPinger samPinger) { super(Text.translatable("friendcode.opening")); this.samPinger = samPinger; } @Override protected void init() { // cancel friend code. this.addDrawableChild(new ButtonWidget( this.width / 2 - 155, this.height - 28, 310, 20, ScreenTexts.CANCEL, button -> this.close() )); } @Override public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { this.renderBackground(matrices); WaitingForFriendCodeScreen.drawCenteredText(matrices, this.textRenderer, this.title, this.width / 2, 50, 0xFFFFFF); final String string = LoadingDisplay.get(Util.getMeasuringTimeMs()); this.client.textRenderer.draw(matrices, string, (float)(this.client.currentScreen.width / 2 - this.client.textRenderer.getWidth(string) / 2), (float)(this.client.currentScreen.height / 2 + this.client.textRenderer.fontHeight), 0x808080); super.render(matrices, mouseX, mouseY, delta); } @Override public void close() { super.close(); this.samPinger.stopSam(); final MutableText text = Text.translatable("friendcode.publish.stopped"); this.client.inGameHud.getChatHud().addMessage(text); } @Override public void tick() { super.tick(); if (samPinger.status() != I2PSamPinger.Status.IDLE) { this.client.setScreen(null); switch (samPinger.status()) { case CONNECTION_FAILED -> { final MutableText text = Text.translatable("friendcode.publish.no_i2p"); this.client.inGameHud.getChatHud().addMessage(text); break; } case SETUP_FAILED, CONNECTION_CLOSED -> { final MutableText text = Text.translatable("friendcode.publish.failed"); this.client.inGameHud.getChatHud().addMessage(text); break; } case RUNNING -> { // convert from I2P base64 to URL-safe base64 so we don't have to write our own Base64 decoder final String pubkey_b64 = samPinger.pubkey().replace('~', '_'); final byte[] pubkey = Base64.getUrlDecoder().decode(pubkey_b64.getBytes(StandardCharsets.UTF_8)); // a .b32.i2p is just a Base32-encoded SHA256 final String b32; try { b32 = new Base32().encodeAsString(MessageDigest.getInstance("SHA-256").digest(pubkey)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } // but we wanna massage it a little final String b32_i2p = b32.replaceFirst("=+$", "").toLowerCase(Locale.ROOT) + ".b32.i2p"; final MutableText text = Text.translatable("friendcode.publish.started", b32_i2p); this.client.inGameHud.getChatHud().addMessage(text); break; } } } } }