From 35eb9cad33c134f0edf80e142be3cf3360387b85 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 29 Jun 2017 10:45:56 +0200 Subject: [PATCH 01/27] Replace usage of FMLLog with the mod logger --- src/main/java/com/replaymod/online/ReplayModOnline.java | 8 ++++---- .../java/com/replaymod/online/gui/GuiReplayCenter.java | 5 +++-- .../com/replaymod/render/metadata/MetadataInjector.java | 5 +++-- src/main/java/com/replaymod/replay/ReplayModReplay.java | 6 +++--- .../com/replaymod/replay/gui/screen/GuiReplayViewer.java | 5 +++-- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/replaymod/online/ReplayModOnline.java b/src/main/java/com/replaymod/online/ReplayModOnline.java index 84762330..e2d42a2c 100644 --- a/src/main/java/com/replaymod/online/ReplayModOnline.java +++ b/src/main/java/com/replaymod/online/ReplayModOnline.java @@ -40,7 +40,7 @@ public class ReplayModOnline { private ReplayModReplay replayModule; - private Logger logger; + public static Logger LOGGER; private ApiClient apiClient; @@ -53,7 +53,7 @@ public class ReplayModOnline { @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { - logger = event.getModLog(); + LOGGER = event.getModLog(); core = ReplayMod.instance; replayModule = ReplayModReplay.instance; @@ -69,7 +69,7 @@ public class ReplayModOnline { public void init(FMLInitializationEvent event) { if (!getDownloadsFolder().exists()){ if (!getDownloadsFolder().mkdirs()) { - logger.warn("Failed to create downloads folder: " + getDownloadsFolder()); + LOGGER.warn("Failed to create downloads folder: " + getDownloadsFolder()); } } @@ -96,7 +96,7 @@ public class ReplayModOnline { } public Logger getLogger() { - return logger; + return LOGGER; } public ApiClient getApiClient() { diff --git a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java index 2470bd2d..ec71bd05 100644 --- a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java +++ b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java @@ -37,7 +37,6 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; -import net.minecraftforge.fml.common.FMLLog; import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.core.helpers.Strings; import org.lwjgl.util.Dimension; @@ -51,6 +50,8 @@ import java.util.Date; import java.util.HashSet; import java.util.Set; +import static com.replaymod.online.ReplayModOnline.LOGGER; + public class GuiReplayCenter extends GuiScreen { private final ReplayModOnline mod; private final ApiClient apiClient; @@ -336,7 +337,7 @@ public class GuiReplayCenter extends GuiScreen { } }); } catch (Exception e) { - FMLLog.getLogger().error("Could not load Replay File " + fileInfo.getId(), e); + LOGGER.error("Could not load Replay File {}", fileInfo.getId(), e); } } } diff --git a/src/main/java/com/replaymod/render/metadata/MetadataInjector.java b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java index eebb92d7..1b8d76ba 100644 --- a/src/main/java/com/replaymod/render/metadata/MetadataInjector.java +++ b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java @@ -4,7 +4,6 @@ import com.coremedia.iso.IsoFile; import com.coremedia.iso.boxes.*; import com.google.common.primitives.Bytes; import com.googlecode.mp4parser.BasicContainer; -import net.minecraftforge.fml.common.FMLLog; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -13,6 +12,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import static com.replaymod.render.ReplayModRender.LOGGER; + public class MetadataInjector { private static final String STITCHING_SOFTWARE = "Minecraft ReplayMod"; @@ -96,7 +97,7 @@ public class MetadataInjector { videoFileOutputStream = new FileOutputStream(videoFile); tempIsoFile.getBox(videoFileOutputStream.getChannel()); } catch(Exception e) { - FMLLog.getLogger().error("360 Degree Metadata couldn't be injected", e); + LOGGER.error("360 Degree Metadata couldn't be injected", e); } finally { IOUtils.closeQuietly(tempIsoFile); IOUtils.closeQuietly(videoFileOutputStream); diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 426d7027..3cb513ec 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -46,7 +46,7 @@ public class ReplayModReplay { private final CameraControllerRegistry cameraControllerRegistry = new CameraControllerRegistry(); - private Logger logger; + public static Logger LOGGER; protected ReplayHandler replayHandler; @@ -56,7 +56,7 @@ public class ReplayModReplay { @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { - logger = event.getModLog(); + LOGGER = event.getModLog(); core = ReplayMod.instance; core.getSettingsRegistry().register(Setting.class); @@ -195,7 +195,7 @@ public class ReplayModReplay { } public Logger getLogger() { - return logger; + return LOGGER; } public CameraControllerRegistry getCameraControllerRegistry() { diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index b9ba3961..3de20bd7 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -28,7 +28,6 @@ import de.johni0702.minecraft.gui.utils.Consumer; import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.client.resources.I18n; import net.minecraft.util.Util; -import net.minecraftforge.fml.common.FMLLog; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOCase; @@ -47,6 +46,8 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; +import static com.replaymod.replay.ReplayModReplay.LOGGER; + public class GuiReplayViewer extends GuiScreen { private final ReplayModReplay mod; @@ -91,7 +92,7 @@ public class GuiReplayViewer extends GuiScreen { obj.consume(() -> new GuiReplayEntry(file, metaData, theThumb)); } } catch (Exception e) { - FMLLog.getLogger().error("Could not load Replay File " + file.getName(), e); + LOGGER.error("Could not load Replay File {}", file.getName(), e); } } } catch (IOException e) { From 1a1e96c3758dcb341e305f4ed70a48b6c79d2c3d Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 14 Jul 2017 13:05:30 +0200 Subject: [PATCH 02/27] Fix server with RM installed refusing clients without RM (fixes #76) --- src/main/java/com/replaymod/compat/ReplayModCompat.java | 1 + src/main/java/com/replaymod/core/ReplayMod.java | 1 + src/main/java/com/replaymod/editor/ReplayModEditor.java | 1 + src/main/java/com/replaymod/extras/ReplayModExtras.java | 1 + src/main/java/com/replaymod/online/ReplayModOnline.java | 1 + src/main/java/com/replaymod/recording/ReplayModRecording.java | 1 + src/main/java/com/replaymod/render/ReplayModRender.java | 1 + src/main/java/com/replaymod/replay/ReplayModReplay.java | 1 + .../java/com/replaymod/simplepathing/ReplayModSimplePathing.java | 1 + 9 files changed, 9 insertions(+) diff --git a/src/main/java/com/replaymod/compat/ReplayModCompat.java b/src/main/java/com/replaymod/compat/ReplayModCompat.java index f6083e50..59f96999 100644 --- a/src/main/java/com/replaymod/compat/ReplayModCompat.java +++ b/src/main/java/com/replaymod/compat/ReplayModCompat.java @@ -10,6 +10,7 @@ import net.minecraftforge.fml.common.eventhandler.EventBus; @Mod(modid = ReplayModCompat.MOD_ID, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", useMetadata = true) public class ReplayModCompat { public static final String MOD_ID = "replaymod-compat"; diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 6a8d97bc..4a4c71ce 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -37,6 +37,7 @@ import java.util.Queue; useMetadata = true, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", updateJSON = "https://raw.githubusercontent.com/ReplayMod/ReplayMod/master/versions.json", guiFactory = "com.replaymod.core.gui.GuiFactory") public class ReplayMod { diff --git a/src/main/java/com/replaymod/editor/ReplayModEditor.java b/src/main/java/com/replaymod/editor/ReplayModEditor.java index 66a8aa93..7749f5c0 100644 --- a/src/main/java/com/replaymod/editor/ReplayModEditor.java +++ b/src/main/java/com/replaymod/editor/ReplayModEditor.java @@ -11,6 +11,7 @@ import org.apache.logging.log4j.Logger; @Mod(modid = ReplayModEditor.MOD_ID, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", useMetadata = true) public class ReplayModEditor { public static final String MOD_ID = "replaymod-editor"; diff --git a/src/main/java/com/replaymod/extras/ReplayModExtras.java b/src/main/java/com/replaymod/extras/ReplayModExtras.java index 3ec83875..5e414197 100644 --- a/src/main/java/com/replaymod/extras/ReplayModExtras.java +++ b/src/main/java/com/replaymod/extras/ReplayModExtras.java @@ -15,6 +15,7 @@ import java.util.List; @Mod(modid = ReplayModExtras.MOD_ID, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", useMetadata = true) public class ReplayModExtras { public static final String MOD_ID = "replaymod-extras"; diff --git a/src/main/java/com/replaymod/online/ReplayModOnline.java b/src/main/java/com/replaymod/online/ReplayModOnline.java index e2d42a2c..fa1e0cfb 100644 --- a/src/main/java/com/replaymod/online/ReplayModOnline.java +++ b/src/main/java/com/replaymod/online/ReplayModOnline.java @@ -29,6 +29,7 @@ import static net.minecraft.client.Minecraft.getMinecraft; @Mod(modid = ReplayModOnline.MOD_ID, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", useMetadata = true) public class ReplayModOnline { public static final String MOD_ID = "replaymod-online"; diff --git a/src/main/java/com/replaymod/recording/ReplayModRecording.java b/src/main/java/com/replaymod/recording/ReplayModRecording.java index b3b55768..b2458985 100644 --- a/src/main/java/com/replaymod/recording/ReplayModRecording.java +++ b/src/main/java/com/replaymod/recording/ReplayModRecording.java @@ -19,6 +19,7 @@ import org.lwjgl.input.Keyboard; @Mod(modid = ReplayModRecording.MOD_ID, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", useMetadata = true) public class ReplayModRecording { public static final String MOD_ID = "replaymod-recording"; diff --git a/src/main/java/com/replaymod/render/ReplayModRender.java b/src/main/java/com/replaymod/render/ReplayModRender.java index 0ce8f7a3..6b7f3668 100644 --- a/src/main/java/com/replaymod/render/ReplayModRender.java +++ b/src/main/java/com/replaymod/render/ReplayModRender.java @@ -21,6 +21,7 @@ import java.util.List; @Mod(modid = ReplayModRender.MOD_ID, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", useMetadata = true) public class ReplayModRender { public static final String MOD_ID = "replaymod-render"; diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 3cb513ec..8c72db89 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -35,6 +35,7 @@ import java.util.Optional; @Mod(modid = ReplayModReplay.MOD_ID, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", useMetadata = true) public class ReplayModReplay { public static final String MOD_ID = "replaymod-replay"; diff --git a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java index 9f445ca9..ecb1e436 100644 --- a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java +++ b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java @@ -19,6 +19,7 @@ import org.lwjgl.input.Keyboard; @Mod(modid = ReplayModSimplePathing.MOD_ID, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", useMetadata = true) public class ReplayModSimplePathing { public static final String MOD_ID = "replaymod-simplepathing"; From 7c719e03ccc7a7c7edc31d08faa632f186a41601 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 27 Jul 2017 15:20:34 +0200 Subject: [PATCH 03/27] Update ReplayStudio/MCProtocolLib (Fixes missing ARMOR_STAND MobType) --- ReplayStudio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReplayStudio b/ReplayStudio index 98bbf92b..feed546a 160000 --- a/ReplayStudio +++ b/ReplayStudio @@ -1 +1 @@ -Subproject commit 98bbf92b9d37fc01c9dd6620c00768c2350eb31d +Subproject commit feed546a9e1f467d286c700f78c4e05b5fb297ac From d56fa9b88fd4495c47be6a336972f2f2aa4560ea Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 1 Aug 2017 21:25:55 +0200 Subject: [PATCH 04/27] Update ReplayStudio/MCProtocolLib (Fixes missing ARMOR_STAND MobType) --- ReplayStudio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReplayStudio b/ReplayStudio index 12b4fdd4..62588cc2 160000 --- a/ReplayStudio +++ b/ReplayStudio @@ -1 +1 @@ -Subproject commit 12b4fdd40ec64d500b94960c4fc36734ef9ce5c5 +Subproject commit 62588cc29bb371ecb383059fd6a2f3b712160dc8 From fe1d9b828dbcf62d33dd93adc2d062285fed10b0 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 11 Aug 2017 14:54:10 +0200 Subject: [PATCH 05/27] Stop Forge from allowing the mod to load on other MC versions (fixes #82) --- build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1ee97ba0..3ff6b6ed 100755 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,9 @@ minecraft { runDir = "eclipse" mappings = "snapshot_nodoc_20141130" replace '@MOD_VERSION@', project.version - replace '@MC_VERSION@', project.minecraft.version + // Includes intentional whitespace to stop Forge from declaring the mod to be compatible with + // a newer srg-compatible MC version (that may be using a different protocol version) + replace '@MC_VERSION@', "[ ${project.minecraft.version} ]" } repositories { From 19629c32f01dd7b8d7fd4fa52a43cb58536ebd02 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 11 Aug 2017 15:10:30 +0200 Subject: [PATCH 06/27] Replace usages of System.out with logger --- .../java/com/replaymod/extras/youtube/YoutubeUploader.java | 6 ++++-- src/main/java/com/replaymod/render/VideoWriter.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java b/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java index 582246a2..79a53302 100644 --- a/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java +++ b/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java @@ -36,6 +36,8 @@ import java.security.GeneralSecurityException; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; +import static com.replaymod.extras.ReplayModExtras.LOGGER; + public class YoutubeUploader { private static final String CLIENT_ID = "743126594724-mfe7pj1k7e47uu5pk4503c8st9vj9ibu.apps.googleusercontent.com"; private static final String CLIENT_SECRET = "gMwcy3mRYCRamCIjJIYP7rqc"; @@ -137,7 +139,7 @@ public class YoutubeUploader { CommandLine commandLine = new CommandLine(settings.getExportCommand()); commandLine.addArguments(args); - System.out.println("Re-encoding for ODS with " + settings.getExportCommand() + args); + LOGGER.info("Re-encoding for ODS with {} {}", settings.getExportCommand(), args); Process process = new ProcessBuilder(commandLine.toStrings()).directory(videoFile.getParentFile()).start(); final AtomicBoolean active = new AtomicBoolean(true); @@ -149,7 +151,7 @@ public class YoutubeUploader { char c = (char) in.read(); if (c == '\r') { String str = sb.toString(); - System.out.println(str); + LOGGER.debug("[FFmpeg] {}", str); if (str.startsWith("frame=")) { str = str.substring(6).trim(); str = str.substring(0, str.indexOf(' ')); diff --git a/src/main/java/com/replaymod/render/VideoWriter.java b/src/main/java/com/replaymod/render/VideoWriter.java index c68dbda6..4fca0f4f 100755 --- a/src/main/java/com/replaymod/render/VideoWriter.java +++ b/src/main/java/com/replaymod/render/VideoWriter.java @@ -57,7 +57,7 @@ public class VideoWriter implements FrameConsumer { .replace("%FILTERS%", settings.getVideoFilters()); String executable = settings.getExportCommand().isEmpty() ? findFFmpeg() : settings.getExportCommand(); - System.out.println("Starting " + executable + " with args: " + commandArgs); + LOGGER.info("Starting {} with args: {}", executable, commandArgs); String[] cmdline = new CommandLine(executable).addArguments(commandArgs).toStrings(); process = new ProcessBuilder(cmdline).directory(outputFolder).start(); File exportLogFile = new File(Minecraft.getMinecraft().mcDataDir, "export.log"); From a2f8c88a3d89508210d5630215a68671f7c87f21 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 11 Aug 2017 15:19:14 +0200 Subject: [PATCH 07/27] Fix compression packets being recorded (fixes #80) --- .../java/com/replaymod/recording/packet/PacketListener.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index cd07c6e0..2d051c56 100755 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -91,6 +91,9 @@ public class PacketListener extends ChannelInboundHandlerAdapter { metaData.setPlayers(uuids.toArray(new String[uuids.size()])); saveMetaData(); } + if (packet instanceof S46PacketSetCompressionLevel) { + return; // Replay data is never compressed on the packet level + } byte[] bytes = getPacketData(packet); long now = System.currentTimeMillis(); From b9ea572e19d3fdc4ebf5f8bc9f8c8bb9ccfe621b Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 12 Aug 2017 11:34:06 +0200 Subject: [PATCH 08/27] Try to handle invalid ffmpeg arguments more gracefully (fixes #77) Instead of outright crashing, if the user has modified the ffmpeg arguments, they are given the chance to re-try with the defaults. --- .../com/replaymod/render/VideoWriter.java | 51 +++++++- .../replaymod/render/gui/GuiExportFailed.java | 109 ++++++++++++++++++ .../replaymod/render/gui/GuiRenderQueue.java | 66 +++++++---- .../render/gui/GuiRenderSettings.java | 22 ++-- .../render/rendering/VideoRenderer.java | 34 +++++- src/main/resources/assets/replaymod/lang | 2 +- 6 files changed, 244 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/replaymod/render/gui/GuiExportFailed.java diff --git a/src/main/java/com/replaymod/render/VideoWriter.java b/src/main/java/com/replaymod/render/VideoWriter.java index 4fca0f4f..08ddb9e4 100755 --- a/src/main/java/com/replaymod/render/VideoWriter.java +++ b/src/main/java/com/replaymod/render/VideoWriter.java @@ -2,6 +2,7 @@ package com.replaymod.render; import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.rendering.FrameConsumer; +import com.replaymod.render.rendering.VideoRenderer; import com.replaymod.render.utils.ByteBufferPool; import com.replaymod.render.utils.StreamPipe; import net.minecraft.client.Minecraft; @@ -32,6 +33,7 @@ import static org.apache.commons.lang3.Validate.isTrue; public class VideoWriter implements FrameConsumer { + private final VideoRenderer renderer; private final RenderSettings settings; private final Process process; private final OutputStream outputStream; @@ -41,8 +43,9 @@ public class VideoWriter implements FrameConsumer { private ByteArrayOutputStream ffmpegLog = new ByteArrayOutputStream(4096); - public VideoWriter(final RenderSettings settings) throws IOException { - this.settings = settings; + public VideoWriter(final VideoRenderer renderer) throws IOException { + this.renderer = renderer; + this.settings = renderer.getRenderSettings(); File outputFolder = settings.getOutputFile().getParentFile(); FileUtils.forceMkdir(outputFolder); @@ -59,7 +62,11 @@ public class VideoWriter implements FrameConsumer { String executable = settings.getExportCommand().isEmpty() ? findFFmpeg() : settings.getExportCommand(); LOGGER.info("Starting {} with args: {}", executable, commandArgs); String[] cmdline = new CommandLine(executable).addArguments(commandArgs).toStrings(); - process = new ProcessBuilder(cmdline).directory(outputFolder).start(); + try { + process = new ProcessBuilder(cmdline).directory(outputFolder).start(); + } catch (IOException e) { + throw new NoFFmpegException(e); + } File exportLogFile = new File(Minecraft.getMinecraft().mcDataDir, "export.log"); OutputStream exportLogOut = new TeeOutputStream(new FileOutputStream(exportLogFile), ffmpegLog); new StreamPipe(process.getInputStream(), exportLogOut).start(); @@ -152,6 +159,16 @@ public class VideoWriter implements FrameConsumer { if (aborted) { return; } + try { + // Check whether this is a failure right at the beginning of the rendering process + // or at some later point (ffmpeg won't print the output file until the first frame + // has been written to stdin, so we can't already check for invalid args in ). + getVideoFile(); + } catch (FFmpegStartupException e) { + // Possibly invalid ffmpeg arguments + renderer.setFailure(e); + return; + } CrashReport report = CrashReport.makeCrashReport(t, "Exporting frame"); CrashReportCategory exportDetails = report.makeCategory("Export details"); exportDetails.addCrashSection("Export command", settings.getExportCommand()); @@ -173,7 +190,7 @@ public class VideoWriter implements FrameConsumer { aborted = true; } - public File getVideoFile() { + public File getVideoFile() throws FFmpegStartupException { String log = ffmpegLog.toString(); for (String line : log.split("\n")) { if (line.startsWith("Output #0")) { @@ -181,6 +198,30 @@ public class VideoWriter implements FrameConsumer { return new File(settings.getOutputFile().getParentFile(), fileName); } } - throw new IllegalStateException("No output file found."); + throw new FFmpegStartupException(settings, log); + } + + public static class NoFFmpegException extends IOException { + public NoFFmpegException(Throwable cause) { + super(cause); + } + } + + public static class FFmpegStartupException extends IOException { + private final RenderSettings settings; + private final String log; + + public FFmpegStartupException(RenderSettings settings, String log) { + this.settings = settings; + this.log = log; + } + + public RenderSettings getSettings() { + return settings; + } + + public String getLog() { + return log; + } } } diff --git a/src/main/java/com/replaymod/render/gui/GuiExportFailed.java b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java new file mode 100644 index 00000000..b86e7118 --- /dev/null +++ b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java @@ -0,0 +1,109 @@ +package com.replaymod.render.gui; + +import com.replaymod.render.RenderSettings; +import com.replaymod.render.VideoWriter; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.container.GuiScreen; +import de.johni0702.minecraft.gui.container.GuiVerticalList; +import de.johni0702.minecraft.gui.element.GuiButton; +import de.johni0702.minecraft.gui.element.GuiElement; +import de.johni0702.minecraft.gui.element.GuiLabel; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import de.johni0702.minecraft.gui.layout.VerticalLayout; +import net.minecraft.crash.CrashReport; +import net.minecraft.crash.CrashReportCategory; +import net.minecraft.util.ReportedException; + +import java.util.Arrays; +import java.util.function.Consumer; + +import static com.replaymod.render.ReplayModRender.LOGGER; + +public class GuiExportFailed extends GuiScreen { + public static GuiExportFailed tryToRecover(VideoWriter.FFmpegStartupException e, Consumer doRestart) { + // Always log the error first + LOGGER.error("Rendering video:", e); + + RenderSettings settings = e.getSettings(); + // Check whether the user has configured some custom ffmpeg arguments + if (settings.getEncodingPreset().getValue().equals(settings.getExportArguments())) { + // If they haven't, then this is probably a faulty ffmpeg installation and there's nothing we can do + CrashReport crashReport = CrashReport.makeCrashReport(e, "Exporting video"); + CrashReportCategory details = crashReport.makeCategory("Export details"); + details.addCrashSection("Settings", settings); + details.addCrashSection("FFmpeg log", e.getLog()); + throw new ReportedException(crashReport); + } else { + // If they have, ask them whether it was intentional + GuiExportFailed gui = new GuiExportFailed(e, doRestart); + gui.display(); + return gui; + } + } + + private final GuiLabel logLabel = new GuiLabel(this) + .setI18nText("replaymod.gui.rendering.error.ffmpeglog"); + private final GuiVerticalList logList = new GuiVerticalList(this).setDrawShadow(true); + private final GuiButton resetButton = new GuiButton().setI18nLabel("gui.yes").setSize(100, 20); + private final GuiButton abortButton = new GuiButton().setI18nLabel("gui.no").setSize(100, 20); + private final GuiPanel info = new GuiPanel(this) + .setLayout(new VerticalLayout().setSpacing(4)) + .addElements(new VerticalLayout.Data(0.5), + new GuiLabel().setI18nText("replaymod.gui.rendering.error.ffmpegargs.1"), + new GuiLabel().setI18nText("replaymod.gui.rendering.error.ffmpegargs.2"), + new GuiLabel(), + new GuiPanel().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5)) + .addElements(null, resetButton, abortButton) + ); + + { + setLayout(new CustomLayout() { + @Override + protected void layout(GuiScreen container, int width, int height) { + pos(info, width/2 - width(info)/2, (height/2 - height(info) - 30) / 2 + 30); + pos(logLabel, width/2 - width(logLabel)/2, height/2 + 4); + pos(logList, 10, y(logLabel) + height(logLabel) + 4); + size(logList, width - 10 - x(logList), height - 10 - y(logList)); + } + }); + + setTitle(new GuiLabel().setI18nText("replaymod.gui.rendering.error.title")); + setBackground(Background.DIRT); + } + + public GuiExportFailed(VideoWriter.FFmpegStartupException e, Consumer doRestart) { + logList.getListPanel().addElements(null, + Arrays.stream(e.getLog().replace("\t", " ").split("\n")) + .map(l -> new GuiLabel().setText(l)) + .toArray(GuiElement[]::new)); + + resetButton.onClick(() -> { + RenderSettings oldSettings = e.getSettings(); + doRestart.accept(new RenderSettings( + oldSettings.getRenderMethod(), + oldSettings.getEncodingPreset(), + oldSettings.getVideoWidth(), + oldSettings.getVideoHeight(), + oldSettings.getFramesPerSecond(), + oldSettings.getBitRate(), + oldSettings.getOutputFile(), + oldSettings.isRenderNameTags(), + oldSettings.isStabilizeYaw(), + oldSettings.isStabilizePitch(), + oldSettings.isStabilizeRoll(), + oldSettings.getChromaKeyingColor(), + oldSettings.isInject360Metadata(), + oldSettings.getAntiAliasing(), + oldSettings.getExportCommand(), + oldSettings.getEncodingPreset().getValue(), + oldSettings.isHighPerformance() + )); + }); + + abortButton.onClick(() -> { + // Assume they know what they're doing + getMinecraft().displayGuiScreen(null); + }); + } +} diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java index 9a96835b..7801479f 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java @@ -1,8 +1,11 @@ package com.replaymod.render.gui; +import com.google.common.collect.Iterables; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +import com.replaymod.core.utils.Utils; import com.replaymod.render.ReplayModRender; +import com.replaymod.render.VideoWriter; import com.replaymod.render.rendering.VideoRenderer; import com.replaymod.render.utils.RenderJob; import com.replaymod.replay.ReplayHandler; @@ -11,6 +14,7 @@ import com.replaymod.replaystudio.util.I18n; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.RenderInfo; import de.johni0702.minecraft.gui.container.AbstractGuiClickableContainer; +import de.johni0702.minecraft.gui.container.AbstractGuiScreen; import de.johni0702.minecraft.gui.container.GuiContainer; import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiVerticalList; @@ -25,12 +29,10 @@ import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; import org.lwjgl.util.Dimension; import org.lwjgl.util.ReadableDimension; import javax.annotation.Nullable; -import java.io.IOException; import java.util.List; import static com.replaymod.render.ReplayModRender.LOGGER; @@ -62,7 +64,8 @@ public class GuiRenderQueue extends AbstractGuiPopup { renameButton, removeButton), closeButton); - private final GuiContainer container; + private final AbstractGuiScreen container; + private final ReplayHandler replayHandler; private Entry selectedEntry; { @@ -84,9 +87,10 @@ public class GuiRenderQueue extends AbstractGuiPopup { }).addElements(null, title, list, buttonPanel); } - public GuiRenderQueue(GuiContainer container, GuiRenderSettings guiRenderSettings, ReplayHandler replayHandler, Timeline timeline) { + public GuiRenderQueue(AbstractGuiScreen container, GuiRenderSettings guiRenderSettings, ReplayHandler replayHandler, Timeline timeline) { super(container); this.container = container; + this.replayHandler = replayHandler; LOGGER.trace("Opening render queue popup"); setBackgroundColor(Colors.DARK_TRANSPARENT); @@ -188,30 +192,46 @@ public class GuiRenderQueue extends AbstractGuiPopup { renderButton.onClick(() -> { LOGGER.trace("Render button clicked"); - // Close all GUIs (so settings in GuiRenderSettings are saved) - getMinecraft().displayGuiScreen(null); - // Start rendering - for (RenderJob renderJob : queue) { - LOGGER.info("Starting render job {}", renderJob); - try { - VideoRenderer videoRenderer = new VideoRenderer(renderJob.getSettings(), replayHandler, renderJob.getTimeline()); - videoRenderer.renderVideo(); - } catch (IOException e) { - LOGGER.error("Rendering video:", e); - GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"), - I18n.format("replaymod.gui.rendering.error.message")); - getMinecraft().displayGuiScreen(errorScreen); - return; - } catch (Throwable t) { - CrashReport crashReport = CrashReport.makeCrashReport(t, "Rendering video"); - throw new ReportedException(crashReport); - } - } + processQueue(queue); }); updateButtons(); } + private void processQueue(Iterable queue) { + // Close all GUIs (so settings in GuiRenderSettings are saved) + getMinecraft().displayGuiScreen(null); + // Start rendering + int jobsDone = 0; + for (RenderJob renderJob : queue) { + LOGGER.info("Starting render job {}", renderJob); + try { + VideoRenderer videoRenderer = new VideoRenderer(renderJob.getSettings(), replayHandler, renderJob.getTimeline()); + videoRenderer.renderVideo(); + } catch (VideoWriter.NoFFmpegException e) { + LOGGER.error("Rendering video:", e); + GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"), + I18n.format("replaymod.gui.rendering.error.message")); + getMinecraft().displayGuiScreen(errorScreen); + return; + } catch (VideoWriter.FFmpegStartupException e) { + int jobsToSkip = jobsDone; + GuiExportFailed.tryToRecover(e, newSettings -> { + // Update current job with fixed ffmpeg arguments + renderJob.setSettings(newSettings); + // Restart queue, skipping the already completed jobs + processQueue(Iterables.skip(queue, jobsToSkip)); + }); + return; + } catch (Throwable t) { + Utils.error(LOGGER, this, CrashReport.makeCrashReport(t, "Rendering video"), () -> {}); + container.display(); // Re-show the queue popup and the new error popup + return; + } + jobsDone++; + } + } + @Override public void open() { super.open(); diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 3c2faa88..f712dd0d 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -7,6 +7,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.InstanceCreator; import com.replaymod.render.RenderSettings; import com.replaymod.render.ReplayModRender; +import com.replaymod.render.VideoWriter; import com.replaymod.render.rendering.VideoRenderer; import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.path.Timeline; @@ -29,7 +30,6 @@ import de.johni0702.minecraft.gui.utils.Utils; import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.client.resources.I18n; import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Property; import org.lwjgl.util.Color; @@ -39,11 +39,13 @@ import org.lwjgl.util.ReadableDimension; import javax.annotation.Nullable; import java.io.File; -import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; +import static com.replaymod.core.utils.Utils.error; +import static com.replaymod.render.ReplayModRender.LOGGER; + public class GuiRenderSettings extends GuiScreen implements Closeable { public final GuiPanel contentPanel = new GuiPanel(this).setBackgroundColor(Colors.DARK_TRANSPARENT); public final GuiVerticalList settingsList = new GuiVerticalList(contentPanel).setDrawSlider(true); @@ -199,15 +201,21 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { try { VideoRenderer videoRenderer = new VideoRenderer(save(false), replayHandler, timeline); videoRenderer.renderVideo(); - } catch (IOException e) { - e.printStackTrace(); - + } catch (VideoWriter.NoFFmpegException e) { + LOGGER.error("Rendering video:", e); GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"), I18n.format("replaymod.gui.rendering.error.message")); getMinecraft().displayGuiScreen(errorScreen); + } catch (VideoWriter.FFmpegStartupException e) { + GuiExportFailed.tryToRecover(e, newSettings -> { + // Update settings with fixed ffmpeg arguments + exportArguments.setText(newSettings.getExportArguments()); + // Restart rendering, this will also save the changed ffmpeg arguments + renderButton.onClick(); + }); } catch (Throwable t) { - CrashReport crashReport = CrashReport.makeCrashReport(t, "Rendering video"); - throw new ReportedException(crashReport); + error(LOGGER, GuiRenderSettings.this, CrashReport.makeCrashReport(t, "Rendering video"), () -> {}); + display(); // Re-show the render settings gui and the new error popup } } }).setSize(100, 20).setI18nLabel("replaymod.gui.render"); diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index cf8ab6b2..9b36c0ad 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -39,6 +39,7 @@ import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import static com.google.common.collect.Iterables.getLast; +import static com.replaymod.render.ReplayModRender.LOGGER; import static net.minecraft.client.renderer.GlStateManager.*; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; @@ -66,6 +67,7 @@ public class VideoRenderer implements RenderInfo { private final GuiVideoRenderer gui; private boolean paused; private boolean cancelled; + private volatile Throwable failureCause; private Framebuffer guiFramebuffer; private int displayWidth, displayHeight; @@ -76,7 +78,7 @@ public class VideoRenderer implements RenderInfo { this.timeline = timeline; this.gui = new GuiVideoRenderer(this); this.renderingPipeline = Pipelines.newPipeline(settings.getRenderMethod(), this, - videoWriter = new VideoWriter(settings) { + videoWriter = new VideoWriter(this) { @Override public void consume(RGBFrame frame) { gui.updatePreview(frame); @@ -89,7 +91,7 @@ public class VideoRenderer implements RenderInfo { * Render this video. * @return {@code true} if rendering was successful, {@code false} if the user aborted rendering (or the window was closed) */ - public boolean renderVideo() { + public boolean renderVideo() throws Throwable { FMLCommonHandler.instance().bus().post(new ReplayRenderEvent.Pre(this)); setup(); @@ -138,6 +140,10 @@ public class VideoRenderer implements RenderInfo { FMLCommonHandler.instance().bus().post(new ReplayRenderEvent.Post(this)); + if (failureCause != null) { + throw failureCause; + } + return !cancelled; } @@ -250,7 +256,13 @@ public class VideoRenderer implements RenderInfo { ReplayMod.soundHandler.playRenderSuccessSound(); - new GuiRenderingDone(ReplayModRender.instance, videoWriter.getVideoFile(), totalFrames, settings).display(); + try { + if (!hasFailed()) { + new GuiRenderingDone(ReplayModRender.instance, videoWriter.getVideoFile(), totalFrames, settings).display(); + } + } catch (VideoWriter.FFmpegStartupException e) { + setFailure(e); + } // Finally, resize the Minecraft framebuffer to the actual width/height of the window mc.resize(displayWidth, displayHeight); @@ -326,7 +338,7 @@ public class VideoRenderer implements RenderInfo { return; } } - } while (paused); + } while (paused && !hasFailed()); } private boolean displaySizeChanged() { @@ -367,6 +379,20 @@ public class VideoRenderer implements RenderInfo { renderingPipeline.cancel(); } + public boolean hasFailed() { + return failureCause != null; + } + + public synchronized void setFailure(Throwable cause) { + if (this.failureCause != null) { + LOGGER.error("Further failure during failed rendering: ", cause); + } else { + LOGGER.error("Failure during rendering: ", cause); + this.failureCause = cause; + cancel(); + } + } + private class TimelinePlayer extends AbstractTimelinePlayer { public TimelinePlayer(ReplayHandler replayHandler) { super(replayHandler); diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index da6f6f8d..c50c65af 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit da6f6f8d123cbda3d04e027948e29161ce302bdc +Subproject commit c50c65af7e987dbff8e7e24c62f50a6bf9ff2360 From 8499c0f154648ebe6aca1ea42b5afea86b0a130a Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 12 Aug 2017 13:19:18 +0200 Subject: [PATCH 09/27] Fix name of invis armor stand with CustomNameVisible not rendering (fixes #72) --- .../replay/mixin/MixinArmorStandRenderer.java | 22 ------------------- .../resources/mixins.replay.replaymod.json | 1 - 2 files changed, 23 deletions(-) delete mode 100644 src/main/java/com/replaymod/replay/mixin/MixinArmorStandRenderer.java diff --git a/src/main/java/com/replaymod/replay/mixin/MixinArmorStandRenderer.java b/src/main/java/com/replaymod/replay/mixin/MixinArmorStandRenderer.java deleted file mode 100644 index 71c8f6d5..00000000 --- a/src/main/java/com/replaymod/replay/mixin/MixinArmorStandRenderer.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.replaymod.replay.mixin; - -import com.replaymod.replay.camera.CameraEntity; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.entity.ArmorStandRenderer; -import net.minecraft.entity.item.EntityArmorStand; -import net.minecraft.entity.player.EntityPlayer; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(ArmorStandRenderer.class) -public abstract class MixinArmorStandRenderer { - @Inject(method = "func_177099_b", at = @At("HEAD"), cancellable = true) - private void replayModReplay_canRenderInvisibleName(EntityArmorStand entity, CallbackInfoReturnable ci) { - EntityPlayer thePlayer = Minecraft.getMinecraft().thePlayer; - if (thePlayer instanceof CameraEntity && entity.isInvisible()) { - ci.setReturnValue(false); - } - } -} diff --git a/src/main/resources/mixins.replay.replaymod.json b/src/main/resources/mixins.replay.replaymod.json index 0bd237a7..8d02ed09 100644 --- a/src/main/resources/mixins.replay.replaymod.json +++ b/src/main/resources/mixins.replay.replaymod.json @@ -2,7 +2,6 @@ "required": true, "package": "com.replaymod.replay.mixin", "mixins": [ - "MixinArmorStandRenderer", "MixinGuiSpectator", "MixinPlayerControllerMP", "MixinRenderArrow", From 9292add8670e63bd9599fd314222e655db15bb07 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 12 Aug 2017 14:20:29 +0200 Subject: [PATCH 10/27] Use percent-encoding for replay file names (fixes #71) --- .../java/com/replaymod/core/utils/Utils.java | 22 +++++++++++++++++++ .../replaymod/editor/gui/GuiReplayEditor.java | 5 ++--- .../replaymod/editor/gui/GuiTrimPanel.java | 3 +-- .../replaymod/online/gui/GuiReplayCenter.java | 3 +-- .../online/gui/GuiSaveModifiedReplay.java | 3 ++- .../replaymod/online/gui/GuiUploadReplay.java | 3 +-- .../handler/ConnectionEventHandler.java | 3 ++- .../replay/gui/screen/GuiReplayViewer.java | 11 +++++----- 8 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index 3252d06c..fb32c44c 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -1,5 +1,6 @@ package com.replaymod.core.utils; +import com.google.common.net.PercentEscaper; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -16,11 +17,14 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiInfoPopup; import de.johni0702.minecraft.gui.utils.Colors; +import lombok.SneakyThrows; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.network.NetworkPlayerInfo; import net.minecraft.client.resources.DefaultPlayerSkin; import net.minecraft.crash.CrashReport; import net.minecraft.util.ResourceLocation; +import org.apache.commons.io.Charsets; +import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.Logger; import org.lwjgl.input.Keyboard; import org.lwjgl.util.Dimension; @@ -36,6 +40,8 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -137,6 +143,22 @@ public class Utils { return mail.matches("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$"); } + private static final PercentEscaper REPLAY_NAME_ENCODER = new PercentEscaper(".-_ ", false); + + public static String replayNameToFileName(String replayName) { + return REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr"; + } + + @SneakyThrows(UnsupportedEncodingException.class) + public static String fileNameToReplayName(String fileName) { + String baseName = FilenameUtils.getBaseName(fileName); + try { + return URLDecoder.decode(baseName, Charsets.UTF_8.name()); + } catch (IllegalArgumentException e) { + return baseName; + } + } + public static ResourceLocation getResourceLocationForPlayerUUID(UUID uuid) { NetworkPlayerInfo info = getMinecraft().getNetHandler().getPlayerInfo(uuid); ResourceLocation skinLocation; diff --git a/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java b/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java index 65ea1ed0..05322765 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java +++ b/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java @@ -36,7 +36,6 @@ 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; import javax.annotation.Nullable; @@ -125,7 +124,7 @@ public class GuiReplayEditor extends GuiScreen { } public void save(File inputFile, PacketStream.FilterInfo...filters) { - save(FilenameUtils.getBaseName(inputFile.getName()), (outputFile) -> { + save(Utils.fileNameToReplayName(inputFile.getName()), (outputFile) -> { Studio studio = new ReplayStudio(); File tmpDir = null; try { @@ -220,7 +219,7 @@ public class GuiReplayEditor extends GuiScreen { Utils.error(LOGGER, GuiReplayEditor.this, CrashReport.makeCrashReport(e, "Getting replay folder"), null); return; } - File targetFile = new File(replayFolder, name + ".mcpr"); + File targetFile = new File(replayFolder, Utils.replayNameToFileName(name)); if (targetFile.exists()) { LOGGER.trace("Selected file already exists, asking for confirmation"); Futures.addCallback(GuiYesNoPopup.open(GuiReplayEditor.this, diff --git a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java index fda001f1..3bff46f8 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java +++ b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java @@ -20,7 +20,6 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import net.minecraft.client.resources.I18n; import net.minecraft.crash.CrashReport; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOCase; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.lwjgl.util.Dimension; @@ -52,7 +51,7 @@ public class GuiTrimPanel extends GuiPanel { public final GuiDropdownMenu inputReplays = new GuiDropdownMenu(this) .setMinSize(new Dimension(200, 20)).onSelection(i -> updateSelectedReplay()) - .setToString(f -> f == NO_REPLAY ? "" : FilenameUtils.getBaseName(f.getName())); + .setToString(f -> f == NO_REPLAY ? "" : Utils.fileNameToReplayName(f.getName())); public final GuiNumberField startHour = newGuiNumberField(); public final GuiNumberField startMin = newGuiNumberField(); diff --git a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java index ec71bd05..252eaf41 100644 --- a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java +++ b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java @@ -37,7 +37,6 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; -import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.core.helpers.Strings; import org.lwjgl.util.Dimension; import org.lwjgl.util.ReadableDimension; @@ -418,7 +417,7 @@ public class GuiReplayCenter extends GuiScreen { this.downloaded = downloaded; ReplayMetaData metaData = fileInfo.getMetadata(); - name.setText(ChatFormatting.UNDERLINE + FilenameUtils.getBaseName(fileInfo.getName())); + name.setText(ChatFormatting.UNDERLINE + Utils.fileNameToReplayName(fileInfo.getName())); author.setI18nText("replaymod.gui.center.author", "" + ChatFormatting.GRAY + ChatFormatting.ITALIC, fileInfo.getOwner()); if (Strings.isEmpty(metaData.getServerName())) { diff --git a/src/main/java/com/replaymod/online/gui/GuiSaveModifiedReplay.java b/src/main/java/com/replaymod/online/gui/GuiSaveModifiedReplay.java index 5e7cedfb..27596349 100644 --- a/src/main/java/com/replaymod/online/gui/GuiSaveModifiedReplay.java +++ b/src/main/java/com/replaymod/online/gui/GuiSaveModifiedReplay.java @@ -2,6 +2,7 @@ package com.replaymod.online.gui; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +import com.replaymod.core.utils.Utils; import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiScreen; import de.johni0702.minecraft.gui.element.GuiButton; @@ -27,7 +28,7 @@ public class GuiSaveModifiedReplay extends GuiScreen { @Override public void run() { String resultName = name.getText().trim().replace("[^a-zA-Z0-9\\.\\- ]", "_"); - final File resultFile = new File(file.getParentFile(), resultName + ".mcpr"); + final File resultFile = new File(file.getParentFile(), Utils.replayNameToFileName(resultName)); if (resultFile.exists()) { Futures.addCallback(GuiYesNoPopup.open(GuiSaveModifiedReplay.this, new GuiLabel().setI18nText("replaymod.gui.replaymodified.warning1", resultName).setColor(Colors.BLACK), diff --git a/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java b/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java index 4421df29..3ceb46d6 100644 --- a/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java +++ b/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java @@ -38,7 +38,6 @@ import net.minecraft.client.settings.KeyBinding; import net.minecraft.crash.CrashReport; import net.minecraft.util.ReportedException; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import javax.annotation.Nullable; @@ -135,7 +134,7 @@ public class GuiUploadReplay extends GuiScreen { } // Apply to gui - name.setText(FilenameUtils.getBaseName(file.getName())); + name.setText(Utils.fileNameToReplayName(file.getName())); int secs = metaData.getDuration() / 1000; durationLabel.setI18nText("replaymod.gui.upload.duration", secs / 60, secs % 60); hideServerIP.setEnabled(!metaData.isSingleplayer()); diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index e63d1d13..ae01e5b7 100755 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -2,6 +2,7 @@ package com.replaymod.recording.handler; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.ModCompat; +import com.replaymod.core.utils.Utils; import com.replaymod.recording.Setting; import com.replaymod.recording.gui.GuiRecordingOverlay; import com.replaymod.recording.packet.PacketListener; @@ -74,7 +75,7 @@ public class ConnectionEventHandler { File folder = core.getReplayFolder(); String name = sdf.format(Calendar.getInstance().getTime()); - File currentFile = new File(folder, name + ".mcpr"); + File currentFile = new File(folder, Utils.replayNameToFileName(name)); ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), currentFile); replayFile.writeModInfo(ModCompat.getInstalledNetworkMods()); diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index 3de20bd7..e9bd1d4e 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -29,7 +29,6 @@ import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.client.resources.I18n; import net.minecraft.util.Util; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOCase; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.apache.logging.log4j.LogManager; @@ -156,7 +155,7 @@ public class GuiReplayViewer extends GuiScreen { @Override public void run() { final File file = list.getSelected().file; - String name = FilenameUtils.getBaseName(file.getName()); + String name = Utils.fileNameToReplayName(file.getName()); final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name); final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this, new GuiLabel().setI18nText("replaymod.gui.viewer.rename.name").setColor(Colors.BLACK), @@ -172,16 +171,16 @@ public class GuiReplayViewer extends GuiScreen { } }).onTextChanged(obj -> { popup.getYesButton().setEnabled(!nameField.getText().isEmpty() - && !new File(file.getParentFile(), nameField.getText() + ".mcpr").exists()); + && !new File(file.getParentFile(), Utils.replayNameToFileName(nameField.getText())).exists()); }); Futures.addCallback(popup.getFuture(), new FutureCallback() { @Override public void onSuccess(Boolean delete) { if (delete) { // Sanitize their input - String name = nameField.getText().trim().replace("[^a-zA-Z0-9\\.\\- ]", "_"); + String name = nameField.getText().trim(); // This file is what they want - File targetFile = new File(file.getParentFile(), name + ".mcpr"); + File targetFile = new File(file.getParentFile(), Utils.replayNameToFileName(name)); try { // Finally, try to move it FileUtils.moveFile(file, targetFile); @@ -303,7 +302,7 @@ public class GuiReplayViewer extends GuiScreen { public GuiReplayEntry(File file, ReplayMetaData metaData, BufferedImage thumbImage) { this.file = file; - name.setText(ChatFormatting.UNDERLINE + FilenameUtils.getBaseName(file.getName())); + name.setText(ChatFormatting.UNDERLINE + Utils.fileNameToReplayName(file.getName())); if (Strings.isEmpty(metaData.getServerName())) { server.setI18nText("replaymod.gui.iphidden").setColor(Colors.DARK_RED); } else { From aafeecc6705bd1f1db1b3abc8b71e2785b716b5f Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 12 Aug 2017 14:27:54 +0200 Subject: [PATCH 11/27] Fix initial login gui not closing on success (fixes #68) --- src/main/java/com/replaymod/online/ReplayModOnline.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/online/ReplayModOnline.java b/src/main/java/com/replaymod/online/ReplayModOnline.java index fa1e0cfb..44092066 100644 --- a/src/main/java/com/replaymod/online/ReplayModOnline.java +++ b/src/main/java/com/replaymod/online/ReplayModOnline.java @@ -83,7 +83,10 @@ public class ReplayModOnline { // Initial login prompt if (!core.getSettingsRegistry().get(Setting.SKIP_LOGIN_PROMPT)) { if (!isLoggedIn()) { - core.runLater(() -> new GuiLoginPrompt(apiClient, GuiScreen.wrap(getMinecraft().currentScreen), null, false).display()); + core.runLater(() -> { + GuiScreen parent = GuiScreen.wrap(getMinecraft().currentScreen); + new GuiLoginPrompt(apiClient, parent, parent, false).display(); + }); } } } From c2000b3edc2d06e7d0ca6419d0cd33c52c536af6 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 13 Aug 2017 12:03:06 +0200 Subject: [PATCH 12/27] Fix only front-facing chunks visible for ODS rendering (fixes #67) --- .../replaymod/render/mixin/MixinFrustum.java | 23 +++++++++++++++++++ .../resources/mixins.render.replaymod.json | 1 + 2 files changed, 24 insertions(+) create mode 100644 src/main/java/com/replaymod/render/mixin/MixinFrustum.java diff --git a/src/main/java/com/replaymod/render/mixin/MixinFrustum.java b/src/main/java/com/replaymod/render/mixin/MixinFrustum.java new file mode 100644 index 00000000..ed22976e --- /dev/null +++ b/src/main/java/com/replaymod/render/mixin/MixinFrustum.java @@ -0,0 +1,23 @@ +package com.replaymod.render.mixin; + +import com.replaymod.render.hooks.EntityRendererHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.culling.Frustum; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Frustum.class) +public abstract class MixinFrustum { + @Inject(method = "isBoxInFrustum", at = @At("HEAD"), cancellable = true) + public void isBoxInFrustum(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, CallbackInfoReturnable ci) { + EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); + if (handler != null && handler.omnidirectional && handler.data == null) { + // Normally the camera is always facing the direction of the omnidirectional image face that is currently + // getting rendered. With ODS however, the camera is always facing forwards and the turning happens in the + // vertex shader (non-trivial due to stereo). As such, all chunks need to be rendered all the time for ODS. + ci.setReturnValue(true); + } + } +} diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json index de808884..a2e940c8 100644 --- a/src/main/resources/mixins.render.replaymod.json +++ b/src/main/resources/mixins.render.replaymod.json @@ -4,6 +4,7 @@ "mixins": [ "MixinEffectRenderer", "MixinEntityRenderer", + "MixinFrustum", "MixinRender", "MixinRendererLivingEntity", "MixinRenderGlobal", From 1b9b13ee7ff79acc825fc4c37e79032b7944b18f Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 13 Aug 2017 12:14:12 +0200 Subject: [PATCH 13/27] Rename render success sound to all lowercase (required for 1.11) (fixes #66) --- .../com/replaymod/render/utils/SoundHandler.java | 2 +- .../{renderSuccess.wav => render_success.wav} | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename src/main/resources/assets/replaymod/{renderSuccess.wav => render_success.wav} (100%) diff --git a/src/main/java/com/replaymod/render/utils/SoundHandler.java b/src/main/java/com/replaymod/render/utils/SoundHandler.java index c0b98f5a..c0ea3f1d 100644 --- a/src/main/java/com/replaymod/render/utils/SoundHandler.java +++ b/src/main/java/com/replaymod/render/utils/SoundHandler.java @@ -12,7 +12,7 @@ import java.io.InputStream; public class SoundHandler { - private final ResourceLocation successSoundLocation = new ResourceLocation("replaymod", "renderSuccess.wav"); + private final ResourceLocation successSoundLocation = new ResourceLocation("replaymod", "render_success.wav"); public void playRenderSuccessSound() { playSound(successSoundLocation); diff --git a/src/main/resources/assets/replaymod/renderSuccess.wav b/src/main/resources/assets/replaymod/render_success.wav similarity index 100% rename from src/main/resources/assets/replaymod/renderSuccess.wav rename to src/main/resources/assets/replaymod/render_success.wav From 0c226b0236d89142c56704d44c50329622c46533 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 13 Aug 2017 12:53:44 +0200 Subject: [PATCH 14/27] Fix path at end of replay resulting in constant reloading (fixes #56) --- src/main/java/com/replaymod/replay/ReplaySender.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java index 6be29efc..4eb82d2f 100755 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -721,6 +721,9 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { } synchronized (this) { + if (timestamp == lastTimeStamp) { // Do nothing if we're already there + return; + } if (timestamp < lastTimeStamp) { // Restart the replay if we need to go backwards in time hasWorldLoaded = false; lastTimeStamp = 0; From 4704b2984e45dbce4f2eabc5fb3855c5836cfed3 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 14 Aug 2017 10:55:09 +0200 Subject: [PATCH 15/27] Replace the RenderPlayer hook (used subclassing) with a mixin (fixes #79) --- .../com/replaymod/core/LoadingPlugin.java | 1 + .../com/replaymod/extras/ReplayModExtras.java | 10 +++++ .../extras/playeroverview/PlayerOverview.java | 8 ---- .../playeroverview/PlayerRenderHook.java | 20 ---------- .../playeroverview/mixin/MixinRender.java | 40 +++++++++++++++++++ ...ixins.extras.playeroverview.replaymod.json | 11 +++++ 6 files changed, 62 insertions(+), 28 deletions(-) delete mode 100644 src/main/java/com/replaymod/extras/playeroverview/PlayerRenderHook.java create mode 100644 src/main/java/com/replaymod/extras/playeroverview/mixin/MixinRender.java create mode 100644 src/main/resources/mixins.extras.playeroverview.replaymod.json diff --git a/src/main/java/com/replaymod/core/LoadingPlugin.java b/src/main/java/com/replaymod/core/LoadingPlugin.java index 2f0cc730..9a251ed9 100755 --- a/src/main/java/com/replaymod/core/LoadingPlugin.java +++ b/src/main/java/com/replaymod/core/LoadingPlugin.java @@ -20,6 +20,7 @@ public class LoadingPlugin implements IFMLLoadingPlugin { Mixins.addConfiguration("mixins.render.replaymod.json"); Mixins.addConfiguration("mixins.replay.replaymod.json"); Mixins.addConfiguration("mixins.compat.shaders.replaymod.json"); + Mixins.addConfiguration("mixins.extras.playeroverview.replaymod.json"); CodeSource codeSource = getClass().getProtectionDomain().getCodeSource(); if (codeSource != null) { diff --git a/src/main/java/com/replaymod/extras/ReplayModExtras.java b/src/main/java/com/replaymod/extras/ReplayModExtras.java index 5e414197..93ed038a 100644 --- a/src/main/java/com/replaymod/extras/ReplayModExtras.java +++ b/src/main/java/com/replaymod/extras/ReplayModExtras.java @@ -10,7 +10,10 @@ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import org.apache.logging.log4j.Logger; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; @Mod(modid = ReplayModExtras.MOD_ID, version = "@MOD_VERSION@", @@ -33,6 +36,8 @@ public class ReplayModExtras { OpenEyeExtra.class ); + private final Map, Extra> instances = new HashMap<>(); + public static Logger LOGGER; @Mod.EventHandler @@ -46,9 +51,14 @@ public class ReplayModExtras { try { Extra extra = cls.newInstance(); extra.register(ReplayMod.instance); + instances.put(cls, extra); } catch (Throwable t) { LOGGER.warn("Failed to load extra " + cls.getName() + ": ", t); } } } + + public Optional get(Class cls) { + return Optional.ofNullable(instances.get(cls)).map(cls::cast); + } } diff --git a/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java b/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java index 774cce95..67619f84 100644 --- a/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java +++ b/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java @@ -9,8 +9,6 @@ import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.camera.CameraEntity; import com.replaymod.replay.events.ReplayCloseEvent; import com.replaymod.replay.events.ReplayOpenEvent; -import net.minecraft.client.renderer.entity.RenderManager; -import net.minecraft.client.renderer.entity.RenderPlayer; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraftforge.client.event.RenderHandEvent; @@ -60,12 +58,6 @@ public class PlayerOverview implements Extra { FMLCommonHandler.instance().bus().register(this); MinecraftForge.EVENT_BUS.register(this); - - RenderManager renderManager = mod.getMinecraft().getRenderManager(); - @SuppressWarnings("unchecked") - Map skinMap = renderManager.skinMap; - skinMap.put("default", new PlayerRenderHook(this, renderManager, false)); - skinMap.put("slim", new PlayerRenderHook(this, renderManager, true)); } public boolean isHidden(UUID uuid) { diff --git a/src/main/java/com/replaymod/extras/playeroverview/PlayerRenderHook.java b/src/main/java/com/replaymod/extras/playeroverview/PlayerRenderHook.java deleted file mode 100644 index 66c7eb42..00000000 --- a/src/main/java/com/replaymod/extras/playeroverview/PlayerRenderHook.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.replaymod.extras.playeroverview; - -import net.minecraft.client.renderer.culling.ICamera; -import net.minecraft.client.renderer.entity.RenderManager; -import net.minecraft.client.renderer.entity.RenderPlayer; -import net.minecraft.entity.Entity; - -public class PlayerRenderHook extends RenderPlayer { - private final PlayerOverview extra; - - public PlayerRenderHook(PlayerOverview extra, RenderManager renderManager, boolean useSmallArms) { - super(renderManager, useSmallArms); - this.extra = extra; - } - - @Override - public boolean shouldRender(Entity entity, ICamera camera, double camX, double camY, double camZ) { - return !extra.isHidden(entity.getUniqueID()) && super.shouldRender(entity, camera, camX, camY, camZ); - } -} diff --git a/src/main/java/com/replaymod/extras/playeroverview/mixin/MixinRender.java b/src/main/java/com/replaymod/extras/playeroverview/mixin/MixinRender.java new file mode 100644 index 00000000..fd750943 --- /dev/null +++ b/src/main/java/com/replaymod/extras/playeroverview/mixin/MixinRender.java @@ -0,0 +1,40 @@ +package com.replaymod.extras.playeroverview.mixin; + +import com.replaymod.extras.ReplayModExtras; +import com.replaymod.extras.playeroverview.PlayerOverview; +import net.minecraft.client.renderer.culling.ICamera; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * This mixin prevents players that are hidden in the PlayerOverview from being rendered. + * + * Cancelling the RenderPlayerEvent.Pre is insufficient because it affects neither the shadows nor the fire texture. + * See: https://github.com/MinecraftForge/MinecraftForge/issues/2987 + * + * The previous solution was to overwrite the RenderPlayer instances which has been dropped in favor of this one + * because it is less compatible with other mods whereas this one should be fine as long as no other mod completely + * overwrites the shouldRender method. + * One example of the previous solution breaking is when used with VanillaEnhancements because it replaces the + * RenderManager with a new custom one which in turn will reset our registered RenderPlayer instances because + * it does so after we have already registered with the old RenderManager. + */ +@Mixin(value = Render.class, priority = 1200) +public abstract class MixinRender { + @Inject(method = "shouldRender", at=@At("HEAD"), cancellable = true) + public void replayModExtras_isPlayerHidden(Entity entity, ICamera camera, double camX, double camY, double camZ, CallbackInfoReturnable ci) { + ReplayModExtras.instance.get(PlayerOverview.class).ifPresent(playerOverview -> { + if (entity instanceof EntityPlayer) { + EntityPlayer player = (EntityPlayer) entity; + if (playerOverview.isHidden(player.getUniqueID())) { + ci.setReturnValue(false); + } + } + }); + } +} diff --git a/src/main/resources/mixins.extras.playeroverview.replaymod.json b/src/main/resources/mixins.extras.playeroverview.replaymod.json new file mode 100644 index 00000000..7c9a0a1f --- /dev/null +++ b/src/main/resources/mixins.extras.playeroverview.replaymod.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "com.replaymod.extras.playeroverview.mixin", + "mixins": [], + "server": [], + "client": [ + "MixinRender" + ], + "compatibilityLevel": "JAVA_8", + "refmap": "mixins.replaymod.refmap.json" +} \ No newline at end of file From 5c73117e9fc38902cf61992c66f88224121c46fa Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 16 Aug 2017 12:19:48 +0200 Subject: [PATCH 16/27] [Compat] Fix invisible entities with Orange's 1.7 Animations (fixes #78) --- .../com/replaymod/compat/ReplayModCompat.java | 2 ++ .../HideInvisibleEntities.java | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/main/java/com/replaymod/compat/oranges17animations/HideInvisibleEntities.java diff --git a/src/main/java/com/replaymod/compat/ReplayModCompat.java b/src/main/java/com/replaymod/compat/ReplayModCompat.java index 59f96999..02c65914 100644 --- a/src/main/java/com/replaymod/compat/ReplayModCompat.java +++ b/src/main/java/com/replaymod/compat/ReplayModCompat.java @@ -1,6 +1,7 @@ package com.replaymod.compat; import com.replaymod.compat.optifine.DisableFastRender; +import com.replaymod.compat.oranges17animations.HideInvisibleEntities; import com.replaymod.compat.shaders.ShaderBeginRender; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.Mod; @@ -20,6 +21,7 @@ public class ReplayModCompat { EventBus bus = FMLCommonHandler.instance().bus(); bus.register(new ShaderBeginRender()); bus.register(new DisableFastRender()); + bus.register(new HideInvisibleEntities()); } } diff --git a/src/main/java/com/replaymod/compat/oranges17animations/HideInvisibleEntities.java b/src/main/java/com/replaymod/compat/oranges17animations/HideInvisibleEntities.java new file mode 100644 index 00000000..1600905d --- /dev/null +++ b/src/main/java/com/replaymod/compat/oranges17animations/HideInvisibleEntities.java @@ -0,0 +1,28 @@ +package com.replaymod.compat.oranges17animations; + +import com.replaymod.replay.camera.CameraEntity; +import net.minecraft.client.Minecraft; +import net.minecraftforge.client.event.RenderLivingEvent; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +/** + * Orange seems to have copied vast parts of the RendererLivingEntity into their ArmorAnimation class which cancels the RenderLivingEvent.Pre and calls its own code instead. + * This breaks our mixin which assures that, even though the camera is in spectator mode, it cannot see invisible entities. + * + * To fix this issue, we simply cancel the RenderLivingEvent.Pre before it gets to ArmorAnimation if the entity is invisible. + */ +public class HideInvisibleEntities { + private final Minecraft mc = Minecraft.getMinecraft(); + private final boolean modLoaded = Loader.isModLoaded("animations"); + + @SubscribeEvent(priority = EventPriority.HIGH) + public void preRenderLiving(RenderLivingEvent.Pre event) { + if (modLoaded) { + if (mc.thePlayer instanceof CameraEntity && event.entity.isInvisible()) { + event.setCanceled(true); + } + } + } +} From 83b1090e4254386a78e3ef06bcc09deaad4e38fe Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 16 Aug 2017 13:00:49 +0200 Subject: [PATCH 17/27] Fix hotbar being visible while spectating player (fixes #83) --- .../replaymod/replay/camera/CameraEntity.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java index 3d5e74a9..0b5852f9 100755 --- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java +++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java @@ -417,5 +417,42 @@ public class CameraEntity extends EntityPlayerSP { event.roll = roll; } } + + private boolean heldItemTooltipsWasTrue; + + @SubscribeEvent + public void preRenderGameOverlay(RenderGameOverlayEvent.Pre event) { + switch (event.type) { + case ALL: + heldItemTooltipsWasTrue = mc.gameSettings.heldItemTooltips; + mc.gameSettings.heldItemTooltips = false; + break; + case ARMOR: + case HEALTH: + case FOOD: + case AIR: + case HOTBAR: + case EXPERIENCE: + case HEALTHMOUNT: + case JUMPBAR: + event.setCanceled(true); + break; + case HELMET: + case PORTAL: + case CROSSHAIRS: + case BOSSHEALTH: + case TEXT: + case CHAT: + case PLAYER_LIST: + case DEBUG: + break; + } + } + + @SubscribeEvent + public void postRenderGameOverlay(RenderGameOverlayEvent.Post event) { + if (event.type != RenderGameOverlayEvent.ElementType.ALL) return; + mc.gameSettings.heldItemTooltips = heldItemTooltipsWasTrue; + } } } From 933ee5f8665d5a7b70c3d3b4406a2e20f02a4a05 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 16 Aug 2017 14:49:21 +0200 Subject: [PATCH 18/27] [Compat] Fix camera entity with BetterSprinting prior to 2.0.0 (fixes #78) --- .../com/replaymod/compat/ReplayModCompat.java | 2 + .../DisableBetterSprinting.java | 97 +++++++++++++++++++ .../replaymod/replay/camera/CameraEntity.java | 8 ++ .../replay/events/ReplayChatMessageEvent.java | 14 +++ 4 files changed, 121 insertions(+) create mode 100644 src/main/java/com/replaymod/compat/bettersprinting/DisableBetterSprinting.java create mode 100644 src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java diff --git a/src/main/java/com/replaymod/compat/ReplayModCompat.java b/src/main/java/com/replaymod/compat/ReplayModCompat.java index 02c65914..9347cd2f 100644 --- a/src/main/java/com/replaymod/compat/ReplayModCompat.java +++ b/src/main/java/com/replaymod/compat/ReplayModCompat.java @@ -1,5 +1,6 @@ package com.replaymod.compat; +import com.replaymod.compat.bettersprinting.DisableBetterSprinting; import com.replaymod.compat.optifine.DisableFastRender; import com.replaymod.compat.oranges17animations.HideInvisibleEntities; import com.replaymod.compat.shaders.ShaderBeginRender; @@ -22,6 +23,7 @@ public class ReplayModCompat { bus.register(new ShaderBeginRender()); bus.register(new DisableFastRender()); bus.register(new HideInvisibleEntities()); + DisableBetterSprinting.register(); } } diff --git a/src/main/java/com/replaymod/compat/bettersprinting/DisableBetterSprinting.java b/src/main/java/com/replaymod/compat/bettersprinting/DisableBetterSprinting.java new file mode 100644 index 00000000..57ab557a --- /dev/null +++ b/src/main/java/com/replaymod/compat/bettersprinting/DisableBetterSprinting.java @@ -0,0 +1,97 @@ +package com.replaymod.compat.bettersprinting; + +import com.replaymod.replay.ReplayModReplay; +import com.replaymod.replay.events.ReplayChatMessageEvent; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.PlayerControllerMP; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.BlockPos; +import net.minecraft.world.IWorldAccess; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; +import net.minecraftforge.fml.common.versioning.Restriction; +import net.minecraftforge.fml.common.versioning.VersionRange; + +import java.util.Collections; + +/** + * Old Better Sprinting versions replace the vanilla player with their own, overridden instance (replacing the camera entity). + * + * See: https://github.com/chylex/Better-Sprinting/blob/1.8/src/main/java/chylex/bettersprinting/client/player/impl/LogicImplOverride.java + */ +public class DisableBetterSprinting { + private static final VersionRange OLD_VERSION = VersionRange.newRange(null, + Collections.singletonList(new Restriction(null, false, new DefaultArtifactVersion("2.0.0"), false))); + private static final String LOGIC_CLASS_NAME = "chylex.bettersprinting.client.player.impl.LogicImplOverride"; + private static final String CONTROLLER_OVERRIDE_CLASS_NAME = LOGIC_CLASS_NAME + ".PlayerControllerMPOverride"; + + public static void register() { + Loader.instance().getModList().stream() + .filter(mod -> mod.getModId().equalsIgnoreCase("bettersprinting")) + .findFirst() + .map(ModContainer::getProcessedVersion).filter(OLD_VERSION::containsVersion) + .ifPresent($_ -> MinecraftForge.EVENT_BUS.register(new DisableBetterSprinting())); + } + + private DisableBetterSprinting() {} + + private final Minecraft mc = Minecraft.getMinecraft(); + private PlayerControllerMP originalController; + private BetterSprintingWorldAccess worldAccessHook = new BetterSprintingWorldAccess(); + + @SubscribeEvent(priority = EventPriority.HIGH) + public void beforeGuiOpenEvent(GuiOpenEvent event) { + if (ReplayModReplay.instance.getReplayHandler() != null && mc.theWorld != null) { + // During replay, get ready to revert BetterSprinting's overwritten playerController + originalController = mc.playerController; + mc.theWorld.addWorldAccess(worldAccessHook); + } + } + + @SubscribeEvent(priority = EventPriority.LOW) + public void afterGuiOpenEvent(GuiOpenEvent event) { + if (ReplayModReplay.instance.getReplayHandler() != null && mc.theWorld != null) { + mc.theWorld.removeWorldAccess(worldAccessHook); + } + } + + @SubscribeEvent + public void onReplayChatMessage(ReplayChatMessageEvent event) { + // Suppress this message if it's the Better Sprinting warning message + for (StackTraceElement elem : Thread.currentThread().getStackTrace()) { + if (LOGIC_CLASS_NAME.equals(elem.getClassName())) { + event.setCanceled(true); + return; + } + } + } + + private class BetterSprintingWorldAccess implements IWorldAccess { + @Override + public void onEntityRemoved(Entity entityIn) { + if (mc.playerController != null && mc.playerController.getClass().getName().equals(CONTROLLER_OVERRIDE_CLASS_NAME)) { + // Someone has secretly swapped out the player controller and is about to substitute their own player entity. + // This is the right time to destroy their plan. + mc.playerController = originalController; + } + } + + @Override public void markBlockForUpdate(BlockPos pos) {} + @Override public void notifyLightSet(BlockPos pos) {} + @Override public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2) {} + @Override public void playSound(String soundName, double x, double y, double z, float volume, float pitch) {} + @Override public void playSoundToNearExcept(EntityPlayer except, String soundName, double x, double y, double z, float volume, float pitch) {} + @Override public void spawnParticle(int p_180442_1_, boolean p_180442_2_, double p_180442_3_, double p_180442_5_, double p_180442_7_, double p_180442_9_, double p_180442_11_, double p_180442_13_, int... p_180442_15_) {} + @Override public void onEntityAdded(Entity entityIn) {} + @Override public void playRecord(String recordName, BlockPos blockPosIn) {} + @Override public void broadcastSound(int p_180440_1_, BlockPos p_180440_2_, int p_180440_3_) {} + @Override public void playAusSFX(EntityPlayer p_180439_1_, int p_180439_2_, BlockPos blockPosIn, int p_180439_4_) {} + @Override public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress) {} + } +} diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java index 0b5852f9..5db8ecb2 100755 --- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java +++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java @@ -5,6 +5,7 @@ import com.replaymod.core.events.SettingsChangedEvent; import com.replaymod.core.utils.Utils; import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.Setting; +import com.replaymod.replay.events.ReplayChatMessageEvent; import com.replaymod.replaystudio.util.Location; import lombok.Getter; import lombok.Setter; @@ -20,6 +21,7 @@ import net.minecraft.entity.item.EntityItemFrame; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.stats.StatFileWriter; import net.minecraft.util.AxisAlignedBB; +import net.minecraft.util.IChatComponent; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; @@ -348,6 +350,12 @@ public class CameraEntity extends EntityPlayerSP { && (e instanceof EntityPlayer || e instanceof EntityLiving || e instanceof EntityItemFrame); } + @Override + public void addChatMessage(IChatComponent message) { + if (MinecraftForge.EVENT_BUS.post(new ReplayChatMessageEvent(this))) return; + super.addChatMessage(message); + } + private class EventHandler { @SubscribeEvent public void onPreClientTick(TickEvent.ClientTickEvent event) { diff --git a/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java b/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java new file mode 100644 index 00000000..c29c594e --- /dev/null +++ b/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java @@ -0,0 +1,14 @@ +package com.replaymod.replay.events; + +import com.replaymod.replay.camera.CameraEntity; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.minecraftforge.fml.common.eventhandler.Cancelable; +import net.minecraftforge.fml.common.eventhandler.Event; + +@Cancelable +@RequiredArgsConstructor +public class ReplayChatMessageEvent extends Event { + @Getter + private final CameraEntity cameraEntity; +} From 0b9c56cdfd53461dafd03733ac33feabfb7664d6 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 19 Aug 2017 15:20:40 +0200 Subject: [PATCH 19/27] Fix resource packs not working after first time jumping back in time --- src/main/java/com/replaymod/replay/ReplaySender.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java index 4eb82d2f..fc41cf77 100755 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -242,6 +242,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { try { channelInactive(ctx); ctx.channel().pipeline().close(); + FileUtils.deleteDirectory(tempResourcePackFolder); } catch(Exception e) { e.printStackTrace(); } @@ -490,12 +491,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { super.channelActive(ctx); } - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - FileUtils.deleteDirectory(tempResourcePackFolder); - super.channelInactive(ctx); - } - /** * Whether the replay is currently paused. * @return {@code true} if it is paused, {@code false} otherwise From 7a4440c4a8c9908c27917d1baa8f61ac9f95efa8 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 21 Aug 2017 10:33:26 +0200 Subject: [PATCH 20/27] Update ReplayStudio 44109f1 Recover from corrupted entity positions cache (fixes #73) --- ReplayStudio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReplayStudio b/ReplayStudio index ff12b1db..44109f11 160000 --- a/ReplayStudio +++ b/ReplayStudio @@ -1 +1 @@ -Subproject commit ff12b1db0b7e96a73e1a15a6b344d65b5b836050 +Subproject commit 44109f11b157e29615489e83ee5df742b4fb4305 From 703805f269a58c3c00d5601be63cf6a8ae51b6ee Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 23 Aug 2017 22:01:37 +0200 Subject: [PATCH 21/27] Fix infinite loop in mc.scheduledTasks (fixes #86) --- .../java/com/replaymod/core/ReplayMod.java | 32 ++++++++++++++++++- .../com/replaymod/replay/ReplaySender.java | 16 ++++------ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 4a4c71ce..86cf4877 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -26,6 +26,9 @@ import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.EventBus; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; import org.apache.commons.io.FileUtils; import java.io.File; @@ -306,11 +309,38 @@ public class ReplayMod { }); } + /** + * Set when the currently running code has been scheduled by runLater. + * If this is the case, subsequent calls to runLater have to be delayed until all scheduled tasks have been + * processed, otherwise a livelock may occur. + */ + private boolean inRunLater = false; + public void runLater(Runnable runnable) { + if (mc.isCallingFromMinecraftThread() && inRunLater) { + EventBus bus = FMLCommonHandler.instance().bus(); + bus.register(new Object() { + @SubscribeEvent + public void onRenderTick(TickEvent.RenderTickEvent event) { + if (event.phase == TickEvent.Phase.START) { + runLater(runnable); + bus.unregister(this); + } + } + }); + return; + } @SuppressWarnings("unchecked") Queue tasks = mc.scheduledTasks; synchronized (mc.scheduledTasks) { - tasks.add(ListenableFutureTask.create(runnable, null)); + tasks.add(ListenableFutureTask.create(() -> { + inRunLater = true; + try { + runnable.run(); + } finally { + inRunLater = false; + } + }, null)); } } diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java index fc41cf77..09cfa9d3 100755 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -2,7 +2,7 @@ package com.replaymod.replay; import com.google.common.base.Preconditions; import com.google.common.io.Files; -import com.google.common.util.concurrent.ListenableFutureTask; +import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.Restrictions; import com.replaymod.replay.camera.CameraEntity; import com.replaymod.replaystudio.replay.ReplayFile; @@ -30,7 +30,6 @@ import java.io.*; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; /** * Sends replay packets to netty channels. @@ -440,22 +439,19 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { } } - new Callable() { + new Runnable() { @Override @SuppressWarnings("unchecked") - public Void call() { + public void run() { if (mc.theWorld == null || !mc.isCallingFromMinecraftThread()) { - synchronized(mc.scheduledTasks) { - mc.scheduledTasks.add(ListenableFutureTask.create(this)); - } - return null; + ReplayMod.instance.runLater(this); + return; } CameraEntity cent = replayHandler.getCameraEntity(); cent.setCameraPosition(ppl.func_148932_c(), ppl.func_148928_d(), ppl.func_148933_e()); - return null; } - }.call(); + }.run(); } if(p instanceof S2BPacketChangeGameState) { From 6aff99d3acdf2aa9b34b626048ec1b60a97109e3 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 23 Aug 2017 22:03:43 +0200 Subject: [PATCH 22/27] Fix livelock during netty write to embedded channel (fixes #85) --- .../com/replaymod/replay/ReplaySender.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java index 09cfa9d3..1bdf09df 100755 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -8,9 +8,10 @@ import com.replaymod.replay.camera.CameraEntity; import com.replaymod.replaystudio.replay.ReplayFile; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelPromise; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiDownloadTerrain; import net.minecraft.client.gui.GuiErrorScreen; @@ -37,7 +38,7 @@ import java.util.Map; * the replay restart from the beginning. */ @Sharable -public class ReplaySender extends ChannelInboundHandlerAdapter { +public class ReplaySender extends ChannelDuplexHandler { /** * Previously packets for the client player were inserted using one fixed entity id (this one). @@ -487,6 +488,25 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { super.channelActive(ctx); } + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + // The embedded channel's event loop will consider every thread to be in it and as such provides no + // guarantees that only one thread is using the pipeline at any one time. + // For reading the replay sender (either sync or async) is the only thread ever writing. + // For writing it may very well happen that multiple threads want to use the pipline at the same time. + // It's unclear whether the EmbeddedChannel is supposed to be thread-safe (the behavior of the event loop + // does suggest that). However it seems like it either isn't (likely) or there is a race condition. + // See: https://www.replaymod.com/forum/thread/1752#post8045 (https://paste.replaymod.com/lotacatuwo) + // To work around this issue, we just outright drop all write/flush requests (they aren't needed anyway). + // This still leaves channel handlers upstream with the threading issue but they all seem to cope well with it. + promise.setSuccess(); + } + + @Override + public void flush(ChannelHandlerContext ctx) throws Exception { + // See write method above + } + /** * Whether the replay is currently paused. * @return {@code true} if it is paused, {@code false} otherwise From 5b04edbbd30faed9de89e92997274a56402a3254 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 24 Aug 2017 20:56:03 +0200 Subject: [PATCH 23/27] Hide ReplayMod app entry from menus on Linux --- src/main/java/com/replaymod/extras/urischeme/LinuxUriScheme.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/replaymod/extras/urischeme/LinuxUriScheme.java b/src/main/java/com/replaymod/extras/urischeme/LinuxUriScheme.java index cdad32d9..2a4db226 100644 --- a/src/main/java/com/replaymod/extras/urischeme/LinuxUriScheme.java +++ b/src/main/java/com/replaymod/extras/urischeme/LinuxUriScheme.java @@ -25,6 +25,7 @@ public class LinuxUriScheme extends UriScheme { "Icon=" + iconFile.getAbsolutePath().replace("\\", "\\\\").replace("\"", "\\\"") + "\n" + "Type=Application\n" + "Terminal=false\n" + + "NoDisplay=true\n" + "MimeType=x-scheme-handler/replaymod;"; FileOutputStream out = new FileOutputStream(file); From 767ea292a793c591072a47f98b96dd1389c2fe81 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 25 Aug 2017 15:01:06 +0200 Subject: [PATCH 24/27] Fix book gui not being suppressed during replay (fixes #90) --- src/main/java/com/replaymod/replay/ReplaySender.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java index 1bdf09df..8a42426b 100755 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -365,6 +365,13 @@ public class ReplaySender extends ChannelDuplexHandler { if(BAD_PACKETS.contains(p.getClass())) return null; + if (p instanceof S3FPacketCustomPayload) { + S3FPacketCustomPayload packet = (S3FPacketCustomPayload) p; + if ("MC|BOpen".equals(packet.getChannelName())) { + return null; + } + } + convertLegacyEntityIds(p); if(p instanceof S48PacketResourcePackSend) { From 00e999ffd29f01bc7ad81c9c3ed158b07e086697 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 25 Aug 2017 15:13:27 +0200 Subject: [PATCH 25/27] Fix replay not being closed when opened via URI scheme handler (fixes #92) --- src/main/java/com/replaymod/replay/ReplayHandler.java | 2 ++ src/main/java/com/replaymod/replay/ReplayModReplay.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 8a9f4c82..caca9969 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -124,6 +124,8 @@ public class ReplayHandler { ReplayModReplay.instance.replayHandler = null; + mc.displayGuiScreen(null); + FMLCommonHandler.instance().bus().post(new ReplayCloseEvent.Post(this)); } diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 8c72db89..d6ef677d 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -181,6 +181,9 @@ public class ReplayModReplay { } public void startReplay(ReplayFile replayFile, boolean checkModCompat) throws IOException { + if (replayHandler != null) { + replayHandler.endReplay(); + } if (checkModCompat) { ModCompat.ModInfoDifference modDifference = new ModCompat.ModInfoDifference(replayFile.getModInfo()); if (!modDifference.getMissing().isEmpty() || !modDifference.getDiffering().isEmpty()) { From 41d2547f3c6e3d861362e46f77007aee0888d0be Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 26 Aug 2017 13:33:30 +0200 Subject: [PATCH 26/27] Update translations --- src/main/resources/assets/replaymod/lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index c50c65af..8d77fe06 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit c50c65af7e987dbff8e7e24c62f50a6bf9ff2360 +Subproject commit 8d77fe0697691f0b682f422e268b40383468f31d From 08170b38efaf8675d32c89402a498f77cfaf3af0 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 26 Aug 2017 12:55:36 +0200 Subject: [PATCH 27/27] Downgrade FG to 2.1 because FG 2.2 is 1.9+ only --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2d9b7850..b6ce7889 100755 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { } } dependencies { - classpath 'net.minecraftforge.gradle:ForgeGradle:2.2-SNAPSHOT' + classpath 'net.minecraftforge.gradle:ForgeGradle:2.1-SNAPSHOT' classpath 'org.spongepowered:mixingradle:0.3-SNAPSHOT' } }