diff --git a/docs/content.md b/docs/content.md index 0b3f97fe..90f9a4f5 100644 --- a/docs/content.md +++ b/docs/content.md @@ -56,7 +56,7 @@ Please use [Optifine](https://optifine.net/) instead. ### Custom Main Menu [custom-main-menu] The [Custom Main Menu](https://mods.curse.com/mc-mods/minecraft/226406-custom-main-menu) mod is often used in mod packs to customize their Main Menu with a button layout fitting the background image, links to their website / bug tracker and similar. -If you are familiar with it, the button ids for the Replay Mod are: **17890234** (text: `replaymod.gui.replayviewer`), **17890235** (text: `replaymod.gui.replayviewer`) and **17890236** (text: `replaymod.gui.replaycenter`) +If you are familiar with it, the button ids for the Replay Mod are: **17890234** (text: `replaymod.gui.replayviewer`), **17890237** (text: `replaymod.gui.replayeditor`) and **17890236** (text: `replaymod.gui.replaycenter`) Due to the nature of this Custom Main Menu mod, buttons added to the Main Menu by 3rd party mods like the **Replay Mod** will not show up by default. Thus, to access the Replay Viewer/Editor/Center, you need to manually configure the position for those buttons. diff --git a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java index c6a4a3d1..d922c1e4 100644 --- a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java +++ b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java @@ -3,6 +3,7 @@ package com.replaymod.core.gui; import com.google.common.io.Files; import com.replaymod.replaystudio.PacketData; import com.replaymod.replaystudio.io.ReplayInputStream; +import com.replaymod.replaystudio.io.ReplayOutputStream; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; import com.replaymod.replaystudio.replay.ZipReplayFile; @@ -45,15 +46,19 @@ public class RestoreReplayGui extends AbstractGuiScreen { ReplayMetaData metaData = replayFile.getMetaData(); if (metaData != null && metaData.getDuration() == 0) { // Try to restore replay duration - try (ReplayInputStream in = replayFile.getPacketData()) { + // We need to re-write the packet data in case there are any incomplete packets dangling at the end + try (ReplayInputStream in = replayFile.getPacketData(); + ReplayOutputStream out = replayFile.writePacketData()) { PacketData last = null; while ((last = in.readPacket()) != null) { metaData.setDuration((int) last.getTime()); + out.write(last); } - replayFile.writeMetaData(metaData); } catch (Throwable t) { t.printStackTrace(); } + // Write back the actual duration + replayFile.writeMetaData(metaData); } replayFile.save(); replayFile.close(); diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index 2457ae15..8b27fff4 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -27,8 +27,20 @@ import org.lwjgl.util.ReadableDimension; import javax.annotation.Nullable; import javax.imageio.ImageIO; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; @@ -51,6 +63,42 @@ public class Utils { DEFAULT_THUMBNAIL = thumbnail; } + + /** + * Neither the root certificate of LetsEncrypt nor the root that cross-signed it is included in the default + * Java keystore prior to 8u101. + * Therefore whenever a connection to the replaymod.com site is made, this SSLContext has to be used instead. + * It has been constructed to include the necessary root certificates. + * @see #SSL_SOCKET_FACTORY + */ + public static final SSLContext SSL_CONTEXT; + + /** + * @see #SSL_CONTEXT + */ + public static final SSLSocketFactory SSL_SOCKET_FACTORY; + + static { + // Largely from https://community.letsencrypt.org/t/134/37 + try (InputStream in = Utils.class.getResourceAsStream("/dst_root_ca_x3.pem")){ + Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(in); + + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setCertificateEntry("1", certificate); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(null, trustManagerFactory.getTrustManagers(), null); + SSL_CONTEXT = ctx; + SSL_SOCKET_FACTORY = ctx.getSocketFactory(); + } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException(e); + } + } + public static String convertSecondsToShortString(int seconds) { int hours = seconds/(60*60); int min = seconds/60 - hours*60; diff --git a/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java b/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java index 98ef0760..62b027ab 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java +++ b/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java @@ -1,5 +1,6 @@ package com.replaymod.editor.gui; +import com.google.common.io.Files; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.gson.JsonObject; @@ -34,6 +35,7 @@ import de.johni0702.minecraft.gui.popup.GuiInfoPopup; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import net.minecraft.crash.CrashReport; +import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.lwjgl.util.ReadableDimension; @@ -119,10 +121,21 @@ public class GuiReplayEditor extends GuiScreen { public void save(File inputFile, PacketStream.FilterInfo...filters) { save(FilenameUtils.getBaseName(inputFile.getName()), (outputFile) -> { Studio studio = new ReplayStudio(); - try (ReplayFile outputReplay = new ZipReplayFile(studio, inputFile, outputFile); - ReplayOutputStream out = outputReplay.writePacketData()) { - // The input replay file MUST be closed before saving the output file - try (ReplayFile inputReplay = new ZipReplayFile(studio, inputFile); + File tmpDir = null; + try { + File actualOutputFile = outputFile; + if (outputFile.getCanonicalPath().equals(inputFile.getCanonicalPath())) { + // Input and output files are identical. Due to the way the ZipReplayFile stores its temporary + // data, the same replay file must not be opened twice for writing (tmp files will be deleted once + // either one is closed or, on Windows, will throw an exception when deleted). + tmpDir = Files.createTempDir(); + outputFile = new File(tmpDir, "replay.mcpr"); + LOGGER.debug("Output file is identical to input file, using temporary output file {} instead", + outputFile); + } + try (ReplayFile outputReplay = new ZipReplayFile(studio, inputFile, outputFile); + ReplayOutputStream out = outputReplay.writePacketData(); + ReplayFile inputReplay = new ZipReplayFile(studio, inputFile); ReplayInputStream in = inputReplay.getPacketData()) { ReplayMetaData metaData = inputReplay.getMetaData(); PacketStream stream = studio.createReplayStream(in, true); @@ -154,11 +167,21 @@ public class GuiReplayEditor extends GuiScreen { // Update duration of new replay metaData.setDuration((int) lastTimestamp); outputReplay.writeMetaData(metaData); + + out.close(); + outputReplay.save(); + } + if (outputFile != actualOutputFile) { + LOGGER.debug("Moving temporary output file {} to {}"); + FileUtils.forceDelete(actualOutputFile); + FileUtils.moveFile(outputFile, actualOutputFile); } - out.close(); - outputReplay.save(); } catch (IOException e) { throw new RuntimeException(e); + } finally { + if (tmpDir != null && !FileUtils.deleteQuietly(tmpDir)) { + LOGGER.warn("Failed to delete temporary directory {}", tmpDir); + } } }); } diff --git a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java index ae5fd79e..fda001f1 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java +++ b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java @@ -39,6 +39,10 @@ import static com.replaymod.editor.ReplayModEditor.LOGGER; import static java.util.Optional.ofNullable; public class GuiTrimPanel extends GuiPanel { + private static GuiNumberField newGuiNumberField() { + return new GuiNumberField().setMaxLength(2).setSize(20, 20).setValidateOnFocusChange(true); + } + // Special value indicating no replay files were found private static final File NO_REPLAY = new File("."); // Special value for the initial "Select Marker Keyframe" entry @@ -50,16 +54,16 @@ public class GuiTrimPanel extends GuiPanel { .setMinSize(new Dimension(200, 20)).onSelection(i -> updateSelectedReplay()) .setToString(f -> f == NO_REPLAY ? "" : FilenameUtils.getBaseName(f.getName())); - public final GuiNumberField startHour = new GuiNumberField().setSize(20, 20).setMaxLength(2); - public final GuiNumberField startMin = new GuiNumberField().setSize(20, 20).setMaxLength(2); - public final GuiNumberField startSec = new GuiNumberField().setSize(20, 20).setMaxLength(2); - public final GuiNumberField startMilli = new GuiNumberField().setSize(40, 20).setMaxLength(4); + public final GuiNumberField startHour = newGuiNumberField(); + public final GuiNumberField startMin = newGuiNumberField(); + public final GuiNumberField startSec = newGuiNumberField(); + public final GuiNumberField startMilli = newGuiNumberField().setSize(40, 20).setMaxLength(4); public final GuiDropdownMenu startMarker = new GuiDropdownMenu<>(); - public final GuiNumberField endHour = new GuiNumberField().setSize(20, 20).setMaxLength(2); - public final GuiNumberField endMin = new GuiNumberField().setSize(20, 20).setMaxLength(2); - public final GuiNumberField endSec = new GuiNumberField().setSize(20, 20).setMaxLength(2); - public final GuiNumberField endMilli = new GuiNumberField().setSize(40, 20).setMaxLength(4); + public final GuiNumberField endHour = newGuiNumberField(); + public final GuiNumberField endMin = newGuiNumberField(); + public final GuiNumberField endSec = newGuiNumberField(); + public final GuiNumberField endMilli = newGuiNumberField().setSize(40, 20).setMaxLength(4); public final GuiDropdownMenu endMarker = new GuiDropdownMenu<>(); public final GuiPanel timePanel = new GuiPanel(this) diff --git a/src/main/java/com/replaymod/extras/OpenEyeExtra.java b/src/main/java/com/replaymod/extras/OpenEyeExtra.java index e198a826..ad5c1004 100644 --- a/src/main/java/com/replaymod/extras/OpenEyeExtra.java +++ b/src/main/java/com/replaymod/extras/OpenEyeExtra.java @@ -17,6 +17,7 @@ import de.johni0702.minecraft.gui.utils.Colors; import net.minecraftforge.fml.common.Loader; import org.apache.commons.io.FileUtils; +import javax.net.ssl.HttpsURLConnection; import java.io.File; import java.io.FileOutputStream; import java.net.URL; @@ -24,8 +25,10 @@ import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; +import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; + public class OpenEyeExtra implements Extra { - private static final String DOWNLOAD_URL = "http://www.replaymod.com/dl/openeye/" + Loader.MC_VERSION; + private static final String DOWNLOAD_URL = "https://www.replaymod.com/dl/openeye/" + Loader.MC_VERSION; private static final Setting ASK_FOR_OPEN_EYE = new Setting<>("advanced", "askForOpenEye", null, true); private ReplayMod mod; @@ -69,7 +72,9 @@ public class OpenEyeExtra implements Extra { File targetFile = new File("mods/" + Loader.MC_VERSION, "OpenEye.jar"); FileUtils.forceMkdir(targetFile.getParentFile()); - ReadableByteChannel in = Channels.newChannel(new URL(DOWNLOAD_URL).openStream()); + HttpsURLConnection connection = (HttpsURLConnection) new URL(DOWNLOAD_URL).openConnection(); + connection.setSSLSocketFactory(SSL_SOCKET_FACTORY); + ReadableByteChannel in = Channels.newChannel(connection.getInputStream()); FileChannel out = new FileOutputStream(targetFile).getChannel(); out.transferFrom(in, 0, Long.MAX_VALUE); } catch (Throwable e) { diff --git a/src/main/java/com/replaymod/online/api/ApiClient.java b/src/main/java/com/replaymod/online/api/ApiClient.java index f8e8a1b1..a12cd822 100755 --- a/src/main/java/com/replaymod/online/api/ApiClient.java +++ b/src/main/java/com/replaymod/online/api/ApiClient.java @@ -15,13 +15,19 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import javax.imageio.ImageIO; +import javax.net.ssl.HttpsURLConnection; import java.awt.image.BufferedImage; -import java.io.*; -import java.net.HttpURLConnection; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.List; import java.util.function.Consumer; +import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; + public class ApiClient { private static final Minecraft mc = Minecraft.getMinecraft(); @@ -130,7 +136,11 @@ public class ApiClient { QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.get_thumbnail); builder.put("id", file); URL url = new URL(builder.toString()); - return ImageIO.read(url); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + connection.setSSLSocketFactory(SSL_SOCKET_FACTORY); + try (InputStream in = connection.getInputStream()) { + return ImageIO.read(in); + } } private boolean cancelDownload = false; @@ -143,7 +153,8 @@ public class ApiClient { builder.put("id", file); String url = builder.toString(); URL website = new URL(url); - HttpURLConnection con = (HttpURLConnection) website.openConnection(); + HttpsURLConnection con = (HttpsURLConnection) website.openConnection(); + con.setSSLSocketFactory(SSL_SOCKET_FACTORY); int fileSize = con.getContentLength(); diff --git a/src/main/java/com/replaymod/online/api/SimpleApiClient.java b/src/main/java/com/replaymod/online/api/SimpleApiClient.java index b2871c6c..c3add7a3 100755 --- a/src/main/java/com/replaymod/online/api/SimpleApiClient.java +++ b/src/main/java/com/replaymod/online/api/SimpleApiClient.java @@ -7,12 +7,14 @@ import com.google.gson.JsonParser; import com.replaymod.online.api.replay.holders.ApiError; import org.apache.commons.io.IOUtils; +import javax.net.ssl.HttpsURLConnection; import java.io.IOException; import java.io.InputStream; -import java.net.HttpURLConnection; import java.net.URL; import java.util.Map; +import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; + public class SimpleApiClient { private static final JsonParser jsonParser = new JsonParser(); @@ -72,10 +74,11 @@ public class SimpleApiClient { // read response String responseContent = null; InputStream is = null; - HttpURLConnection httpUrlConnection = null; + HttpsURLConnection httpUrlConnection = null; try { URL url = new URL(urlString); - httpUrlConnection = (HttpURLConnection) url.openConnection(); + httpUrlConnection = (HttpsURLConnection) url.openConnection(); + httpUrlConnection.setSSLSocketFactory(SSL_SOCKET_FACTORY); httpUrlConnection.setRequestMethod("GET"); diff --git a/src/main/java/com/replaymod/online/api/replay/FileUploader.java b/src/main/java/com/replaymod/online/api/replay/FileUploader.java index f369b310..8dce3faa 100755 --- a/src/main/java/com/replaymod/online/api/replay/FileUploader.java +++ b/src/main/java/com/replaymod/online/api/replay/FileUploader.java @@ -9,16 +9,18 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import javax.net.ssl.HttpsURLConnection; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; -import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.Set; import java.util.function.Consumer; +import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; + @RequiredArgsConstructor public class FileUploader { private static final Gson gson = new Gson(); @@ -48,7 +50,8 @@ public class FileUploader { postData += "&name=" + URLEncoder.encode(filename, "UTF-8"); String url = ReplayModApiMethods.upload_file + postData; - HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); + HttpsURLConnection con = (HttpsURLConnection) new URL(url).openConnection(); + con.setSSLSocketFactory(SSL_SOCKET_FACTORY); con.setUseCaches(false); con.setDoOutput(true); con.setRequestMethod("POST"); diff --git a/src/main/java/com/replaymod/online/api/replay/ReplayModApiMethods.java b/src/main/java/com/replaymod/online/api/replay/ReplayModApiMethods.java index 6277c1cd..524fbba1 100755 --- a/src/main/java/com/replaymod/online/api/replay/ReplayModApiMethods.java +++ b/src/main/java/com/replaymod/online/api/replay/ReplayModApiMethods.java @@ -2,7 +2,7 @@ package com.replaymod.online.api.replay; public class ReplayModApiMethods { - public static final String REPLAYMOD_BASE_URL = "http://ReplayMod.com/api/"; + public static final String REPLAYMOD_BASE_URL = "https://ReplayMod.com/api/"; public static final String register = REPLAYMOD_BASE_URL+"register"; public static final String check_authkey = REPLAYMOD_BASE_URL+"check_authkey"; diff --git a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java index 0c9825dd..cec0ebfa 100644 --- a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java +++ b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java @@ -9,6 +9,7 @@ import com.replaymod.replaystudio.replay.ReplayFile; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreenWorking; import net.minecraft.client.gui.GuiYesNo; +import net.minecraft.client.gui.GuiYesNoCallback; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.multiplayer.ServerList; import net.minecraft.client.network.NetHandlerPlayClient; @@ -26,6 +27,7 @@ import javax.annotation.Nonnull; import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.util.HashMap; import java.util.Map; /** @@ -52,6 +54,9 @@ public class ResourcePackRecorder { boolean doWrite = false; // Whether we are the first and have to write it synchronized (replayFile) { // Need to read, modify and write the resource pack index atomically Map index = replayFile.getResourcePackIndex(); + if (index == null) { + index = new HashMap<>(); + } if (!index.containsValue(hash)) { // Hash is unknown, we have to write the resource pack ourselves doWrite = true; @@ -107,19 +112,24 @@ public class ResourcePackRecorder { } else if (serverData != null && serverData.getResourceMode() != ServerData.ServerResourceMode.PROMPT) { netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED)); } else { - mc.addScheduledTask(() -> mc.displayGuiScreen(new GuiYesNo((result, id) -> { - if (serverData != null) { - serverData.setResourceMode(result ? ServerData.ServerResourceMode.ENABLED : ServerData.ServerResourceMode.DISABLED); - } - if (result) { - netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED)); - downloadResourcePackFuture(requestId, url, hash); - } else { - netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED)); - } + // Lambdas MUST NOT be used with methods that need re-obfuscation in FG prior to 2.2 (will result in AbstractMethodError) + //noinspection Convert2Lambda + mc.addScheduledTask(() -> mc.displayGuiScreen(new GuiYesNo(new GuiYesNoCallback() { + @Override + public void confirmClicked(boolean result, int id) { + if (serverData != null) { + serverData.setResourceMode(result ? ServerData.ServerResourceMode.ENABLED : ServerData.ServerResourceMode.DISABLED); + } + if (result) { + netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED)); + ResourcePackRecorder.this.downloadResourcePackFuture(requestId, url, hash); + } else { + netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED)); + } - ServerList.func_147414_b(serverData); - mc.displayGuiScreen(null); + ServerList.func_147414_b(serverData); + mc.displayGuiScreen(null); + } }, I18n.format("multiplayer.texturePrompt.line1"), I18n.format("multiplayer.texturePrompt.line2"), 0))); } } diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 14cd84fc..3c2faa88 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -80,8 +80,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { } }).setMinSize(new Dimension(0, 20)).setValues(RenderSettings.EncodingPreset.values()); - public final GuiNumberField videoWidth = new GuiNumberField().setSize(50, 20).setMinValue(1); - public final GuiNumberField videoHeight = new GuiNumberField().setSize(50, 20).setMinValue(1); + public final GuiNumberField videoWidth = new GuiNumberField().setSize(50, 20).setMinValue(1).setValidateOnFocusChange(true); + public final GuiNumberField videoHeight = new GuiNumberField().setSize(50, 20).setMinValue(1).setValidateOnFocusChange(true); public final GuiSlider frameRateSlider = new GuiSlider().onValueChanged(new Runnable() { @Override public void run() { @@ -93,7 +93,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(2)) .addElements(new HorizontalLayout.Data(0.5), videoWidth, new GuiLabel().setText("*"), videoHeight); - public final GuiNumberField bitRateField = new GuiNumberField().setValue(10).setSize(50, 20); + public final GuiNumberField bitRateField = new GuiNumberField().setValue(10).setSize(50, 20).setValidateOnFocusChange(true); public final GuiDropdownMenu bitRateUnit = new GuiDropdownMenu() .setSize(50, 20).setValues("bps", "kbps", "mbps").setSelected("mbps"); diff --git a/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java b/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java index 23e9e5b5..cc94f3cc 100644 --- a/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java +++ b/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java @@ -16,6 +16,10 @@ import org.lwjgl.input.Keyboard; import org.lwjgl.util.ReadablePoint; public class GuiEditMarkerPopup extends AbstractGuiPopup implements Typeable { + private static GuiNumberField newGuiNumberField() { + return new GuiNumberField().setSize(150, 20).setValidateOnFocusChange(true); + } + private final ReplayHandler replayHandler; private final Marker marker; @@ -23,15 +27,15 @@ public class GuiEditMarkerPopup extends AbstractGuiPopup imp public final GuiTextField nameField = new GuiTextField().setSize(150, 20); // TODO: Replace with a min/sec/msec field - public final GuiNumberField timeField = new GuiNumberField().setSize(150, 20).setPrecision(0); + public final GuiNumberField timeField = newGuiNumberField().setPrecision(0); - public final GuiNumberField xField = new GuiNumberField().setSize(150, 20).setPrecision(10); - public final GuiNumberField yField = new GuiNumberField().setSize(150, 20).setPrecision(10); - public final GuiNumberField zField = new GuiNumberField().setSize(150, 20).setPrecision(10); + public final GuiNumberField xField = newGuiNumberField().setPrecision(10); + public final GuiNumberField yField = newGuiNumberField().setPrecision(10); + public final GuiNumberField zField = newGuiNumberField().setPrecision(10); - public final GuiNumberField yawField = new GuiNumberField().setSize(150, 20).setPrecision(5); - public final GuiNumberField pitchField = new GuiNumberField().setSize(150, 20).setPrecision(5); - public final GuiNumberField rollField = new GuiNumberField().setSize(150, 20).setPrecision(5); + public final GuiNumberField yawField = newGuiNumberField().setPrecision(5); + public final GuiNumberField pitchField = newGuiNumberField().setPrecision(5); + public final GuiNumberField rollField = newGuiNumberField().setPrecision(5); public final GuiPanel inputs = GuiPanel.builder() .layout(new GridLayout().setColumns(2).setSpacingX(7).setSpacingY(3)) diff --git a/src/main/resources/dst_root_ca_x3.pem b/src/main/resources/dst_root_ca_x3.pem new file mode 100644 index 00000000..b2e43c93 --- /dev/null +++ b/src/main/resources/dst_root_ca_x3.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE-----