From 6879729633192eda246630e5770d7f5e49f7c869 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 15 Nov 2020 13:17:00 +0100 Subject: [PATCH 01/12] Fix OpenEXR export option throwing UnsatisfiedLinkError on Windows --- ...in_WindowsWorkaroundForTinyEXRNatives.java | 81 +++++++++++++++++++ .../resources/mixins.render.replaymod.json | 1 + 2 files changed, 82 insertions(+) create mode 100644 src/main/java/com/replaymod/render/mixin/Mixin_WindowsWorkaroundForTinyEXRNatives.java diff --git a/src/main/java/com/replaymod/render/mixin/Mixin_WindowsWorkaroundForTinyEXRNatives.java b/src/main/java/com/replaymod/render/mixin/Mixin_WindowsWorkaroundForTinyEXRNatives.java new file mode 100644 index 00000000..3df81c08 --- /dev/null +++ b/src/main/java/com/replaymod/render/mixin/Mixin_WindowsWorkaroundForTinyEXRNatives.java @@ -0,0 +1,81 @@ +//#if MC>=11400 +package com.replaymod.render.mixin; + +import org.lwjgl.system.Library; +import org.lwjgl.system.Platform; +import org.lwjgl.util.tinyexr.TinyEXR; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +/** + * It appears like natives on Windows cannot be loaded if one of their dependencies has already been loaded by a + * different class loader. In our case we cannot load tinyexr (on the knot class loader) because lwjgl has already + * been loaded on the system class loader. + * + * If we force the tinyexr native to load on the system class loader (by calling `Library.loadSystem(absPath)`), + * it'll load but we'll get an error when we call any of the native methods. + * + * We can't really load TinyEXR itself via the system class loader because Java does not provide any methods for + * modifying the system class path at runtime and we'd have to use JVM-specific hacks. + * + * Strangely, if we use System.loadLibrary instead of System.load, then it all just works. This mixin implements + * that workaround by finding MC's natives folder, extracting the dll from our jar into that folder and then replacing + * the context class passed to Library.loadSystem (which it uses to find dlls in jars) with Library (which is on the + * system class loader) so it cannot find the dll in our jar and falls back to using System.loadLibrary. + */ +@Mixin(value = TinyEXR.class, remap = false) +public class Mixin_WindowsWorkaroundForTinyEXRNatives { + private static final String LOAD_SYSTEM_CONSUMERS = "Lorg/lwjgl/system/Library;loadSystem(Ljava/util/function/Consumer;Ljava/util/function/Consumer;Ljava/lang/Class;Ljava/lang/String;)V"; + + @ModifyArg(method = "", at = @At(value = "INVOKE", target = LOAD_SYSTEM_CONSUMERS)) + private static Class uglyWindowsHacks(Consumer load, Consumer loadLibrary, Class context, String name) throws IOException { + if (Platform.get() != Platform.WINDOWS) { + return context; // works out of the box on linux + } + + name = System.mapLibraryName(name); + + URL libURL = context.getClassLoader().getResource(name); + if (libURL == null) { + throw new UnsatisfiedLinkError("Failed to locate library: " + name); + } + + String lwjglLibName = Library.JNI_LIBRARY_NAME; + if (!lwjglLibName.endsWith(".dll")) { + lwjglLibName = System.mapLibraryName(lwjglLibName); + } + + String paths = System.getProperty("java.library.path"); + Path nativesDir = null; + for (String dir : Pattern.compile(File.pathSeparator).split(paths)) { + Path path = Paths.get(dir); + if (Files.isReadable(path.resolve(lwjglLibName))) { + nativesDir = path; + break; + } + } + if (nativesDir == null) { + throw new UnsatisfiedLinkError("Failed to locate natives folder in " + paths); + } + + Path libPath = nativesDir.resolve(name); + try (InputStream source = libURL.openStream()) { + Files.copy(source, libPath, StandardCopyOption.REPLACE_EXISTING); + } + + return Library.class; + } +} +//#endif diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json index 9a7ca987..976fbf9b 100644 --- a/src/main/resources/mixins.render.replaymod.json +++ b/src/main/resources/mixins.render.replaymod.json @@ -19,6 +19,7 @@ //#endif //#if MC>=11400 "Mixin_PreserveDepthDuringHandRendering", + "Mixin_WindowsWorkaroundForTinyEXRNatives", "MainWindowAccessor", //#endif "WorldRendererAccessor", From 36276d32b6cfbc368995f0a0826e96ec831752f5 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 09:14:51 +0100 Subject: [PATCH 02/12] Fix camera path export roll being inverted (fixes #427) --- src/main/java/com/replaymod/render/CameraPathExporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/render/CameraPathExporter.java b/src/main/java/com/replaymod/render/CameraPathExporter.java index abb9e994..1d26db6a 100644 --- a/src/main/java/com/replaymod/render/CameraPathExporter.java +++ b/src/main/java/com/replaymod/render/CameraPathExporter.java @@ -91,7 +91,7 @@ public class CameraPathExporter { quatYaw.setFromAxisAngle(new Vector4f(0, -1, 0, (float) Math.toRadians(yaw))); quatPitch.setFromAxisAngle(new Vector4f(-1, 0, 0, (float) Math.toRadians(pitch))); - quatRoll.setFromAxisAngle(new Vector4f(0, 0, 1, (float) Math.toRadians(roll))); + quatRoll.setFromAxisAngle(new Vector4f(0, 0, -1, (float) Math.toRadians(roll))); Quaternion quaternion = new Quaternion(0, 0, 0, 1); Quaternion.mul(quaternion, quatYaw, quaternion); From ee0294439189d713e7ad55f1f9d66e0f08570b0a Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 13:00:37 +0100 Subject: [PATCH 03/12] Fix crash when rendering ODS without depth export (fixes #418) --- .../java/com/replaymod/render/capturer/ODSFrameCapturer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java index 473bbf1d..5f8d1713 100644 --- a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java @@ -141,7 +141,9 @@ public class ODSFrameCapturer implements FrameCapturer { for (Channel channel : Channel.values()) { CubicOpenGlFrame leftFrame = leftChannels.get(channel); CubicOpenGlFrame rightFrame = rightChannels.get(channel); - result.put(channel, new ODSOpenGlFrame(leftFrame, rightFrame)); + if (leftFrame != null && rightFrame != null) { + result.put(channel, new ODSOpenGlFrame(leftFrame, rightFrame)); + } } return result; } From d4b7fdf949c56f9afde7ea57844ace8c3eb61af8 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 14:31:03 +0100 Subject: [PATCH 04/12] Update remap (fixes #414) Support remapping of mixin targets declared in static fields (specifically `core.MixinKeyboardListener:ON_KEY_PRESSED`). String literals in annotations can be specified in a static final field instead of inline (so you can e.g. use the same literal in multiple places). This commit adds support for remapping those external literals. --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index e91e2915..a47318d4 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ import java.io.ByteArrayOutputStream plugins { id("fabric-loom") version "0.4-SNAPSHOT" apply false - id("com.replaymod.preprocess") version "3c46acb" + id("com.replaymod.preprocess") version "24ac087" id("com.github.hierynomus.license") version "0.15.0" } From 4739d2278b1d50b9e4a1f28f761885844df74d2f Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 14:51:48 +0100 Subject: [PATCH 05/12] Fix render settings not saving when rendering (fixes #425) Clicking Cancel or Add to Queue does save them but clicking Render does not. This is caused by the fact that the GuiRenderSettings are now a popup and no longer their own screen, so to close them, we need to call its close method, not close the entire screen. --- .../extras/advancedscreenshots/GuiCreateScreenshot.java | 2 +- src/main/java/com/replaymod/render/gui/GuiRenderSettings.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java index 5e831ebb..62d316cf 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java @@ -50,7 +50,7 @@ public class GuiCreateScreenshot extends GuiRenderSettings implements Loadable { buttonPanel.removeElement(queueButton); renderButton.setI18nLabel("replaymod.gui.advancedscreenshots.create").onClick(() -> { // Closing this GUI ensures that settings are saved - getMinecraft().openScreen(null); + close(); mod.runLater(() -> { try { diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 5a632c57..cf0f58f5 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -252,7 +252,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { @Override public void run() { // Closing this GUI ensures that settings are saved - getMinecraft().openScreen(null); + close(); try { VideoRenderer videoRenderer = new VideoRenderer(save(false), replayHandler, timeline); videoRenderer.renderVideo(); From f895a13b61eb59780dce9ce59d1799a038134578 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 14:55:07 +0100 Subject: [PATCH 06/12] Apply Saving Replay dialog when pressing Enter (closes #424) --- .../com/replaymod/recording/gui/GuiSavingReplay.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java b/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java index e42b6e91..ff8d8b07 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java +++ b/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java @@ -83,10 +83,7 @@ public class GuiSavingReplay { GuiButton applyButton = new GuiButton() .setSize(150, 20) .setI18nLabel("replaymod.gui.done") - .onClick(() -> { - apply.forEach(Runnable::run); - close(); - }); + .onClick(this::apply); panel.addElements(new VerticalLayout.Data(0.5), applyButton); } @@ -98,6 +95,7 @@ public class GuiSavingReplay { .setText(originalName) .setI18nHint("replaymod.gui.delete") .setTextColorDisabled(Colors.RED) + .onEnter(this::apply) .setTooltip(createTooltip(path, metaData)); GuiButton clearButton = new GuiButton() .setSize(20, 20) @@ -133,6 +131,11 @@ public class GuiSavingReplay { }).addElements(null, tooltip, entry); } + private void apply() { + apply.forEach(Runnable::run); + close(); + } + private void applyOutput(Path path, String newName) { if (newName.isEmpty()) { try { From f7f20ee6cbea105a4f8e74d43806d0659a092a84 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 15:03:24 +0100 Subject: [PATCH 07/12] Add setting to disable Saving Replay rename dialog (closes #423) --- src/main/java/com/replaymod/recording/Setting.java | 1 + .../java/com/replaymod/recording/gui/GuiSavingReplay.java | 5 +++++ src/main/resources/assets/replaymod/lang | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/recording/Setting.java b/src/main/java/com/replaymod/recording/Setting.java index 27d2d69c..0bb27719 100644 --- a/src/main/java/com/replaymod/recording/Setting.java +++ b/src/main/java/com/replaymod/recording/Setting.java @@ -8,6 +8,7 @@ public final class Setting extends SettingsRegistry.SettingKeys { public static final Setting INDICATOR = make("indicator", "indicator", true); public static final Setting AUTO_START_RECORDING = make("autoStartRecording", "autostartrecording", true); public static final Setting AUTO_POST_PROCESS = make("autoPostProcess", null, true); + public static final Setting RENAME_DIALOG = make("renameDialog", "rename_recording_dialog", true); private static Setting make(String key, String displayName, T defaultValue) { return new Setting<>(key, displayName, defaultValue); diff --git a/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java b/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java index ff8d8b07..7f1a1238 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java +++ b/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java @@ -2,6 +2,7 @@ package com.replaymod.recording.gui; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.Utils; +import com.replaymod.recording.Setting; import com.replaymod.replay.gui.screen.GuiReplayViewer; import com.replaymod.replaystudio.replay.ReplayMetaData; import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair; @@ -86,6 +87,10 @@ public class GuiSavingReplay { .onClick(this::apply); panel.addElements(new VerticalLayout.Data(0.5), applyButton); + + if (!core.getSettingsRegistry().get(Setting.RENAME_DIALOG)) { + apply(); + } } private GuiTextField addOutput(Path path, ReplayMetaData metaData) { diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index b45b1a1f..63b7a8be 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit b45b1a1f9ae205a79c03726fce885536dd0dabd0 +Subproject commit 63b7a8bea1ab9a9f005c9ae0e1c54b4432cbc9e6 From 68ca92e8a2f65cc9b3411751bd9fa9c8e47ee311 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 15:06:28 +0100 Subject: [PATCH 08/12] Update changelog URL --- build.gradle.kts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a47318d4..184e5ed3 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -115,10 +115,7 @@ fun generateVersionsJson(): Map { versions.forEach { val (thisMcVersion, modVersion, preVersion) = it.split("-") + listOf(null, null) if (thisMcVersion == mcVersion) { - mcVersionRoot[it] = if (tagVersions.contains(it)) - "See https://github.com/ReplayMod/ReplayMod/releases/$it" - else - "See https://www.replaymod.com/forum/thread/100" + mcVersionRoot[it] = "See https://www.replaymod.com/changelog.txt" if (latest == null) latest = it if (preVersion == null) { if (recommended == null) recommended = it From d06ef47f1c27ce5bc18408b7a0e5e416bea8cd5b Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 15:15:41 +0100 Subject: [PATCH 09/12] Update ReplayStudio (fixes #426) --- versions/common.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions/common.gradle b/versions/common.gradle index 11bba57d..be04eb4f 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -309,7 +309,7 @@ dependencies { shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8' - shadow "com.github.ReplayMod:ReplayStudio:830678c", shadeExclusions + shadow "com.github.ReplayMod:ReplayStudio:dac74cc", shadeExclusions implementation(jGui){ transitive = false // FG 1.2 puts all MC deps into the compile configuration and we don't want to shade those From c3c59ec1c0e759cbb7f330b86288ef66c3ab1875 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 15:18:41 +0100 Subject: [PATCH 10/12] Trim video file extension from glb file name (fixes #422) --- src/main/java/com/replaymod/render/CameraPathExporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/render/CameraPathExporter.java b/src/main/java/com/replaymod/render/CameraPathExporter.java index 1d26db6a..7bf0c23a 100644 --- a/src/main/java/com/replaymod/render/CameraPathExporter.java +++ b/src/main/java/com/replaymod/render/CameraPathExporter.java @@ -213,7 +213,7 @@ public class CameraPathExporter { java.nio.file.Path videoPath = settings.getOutputFile().toPath(); java.nio.file.Path glbBasePath = Files.isDirectory(videoPath) ? videoPath.resolve("camera.glb") - : videoPath.resolveSibling(videoPath.getFileName() + ".glb"); + : videoPath.resolveSibling(FilenameUtils.getBaseName(videoPath.getFileName().toString()) + ".glb"); java.nio.file.Path glbPath = glbBasePath; for (int i = 0; Files.exists(glbPath); i++) { String baseName = FilenameUtils.getBaseName(glbBasePath.getFileName().toString()); From 673964ea6afae36affcf563e33e7f239ba16cada Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 16:04:14 +0100 Subject: [PATCH 11/12] Hide Saving Replay popup when no output is produced (fixes #420) I.e. if the had Auto-Recording disabled and never pressed start, there won't be any replays and it would just be the Done button, which is pointless. --- .../replaymod/editor/gui/MarkerProcessor.java | 4 ++++ .../recording/packet/PacketListener.java | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java index eca8f74f..80e6494d 100644 --- a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java +++ b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java @@ -53,6 +53,10 @@ public class MarkerProcessor { } } + public static boolean producesAnyOutput(ReplayFile replayFile) throws IOException { + return !getOutputSuffixes(replayFile).isEmpty(); + } + private enum OutputState { /** A new output file has begun but not data has been written yet. */ NotYetWriting, diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index 53b7de29..40d4605c 100644 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -39,6 +39,7 @@ import net.minecraft.network.Packet; import net.minecraft.network.PacketByteBuf; import net.minecraft.text.LiteralText; import net.minecraft.util.crash.CrashReport; +import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -65,6 +66,7 @@ import net.minecraft.network.NetworkSide; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; @@ -266,6 +268,23 @@ public class PacketListener extends ChannelInboundHandlerAdapter { List> outputPaths; synchronized (replayFile) { try { + if (!MarkerProcessor.producesAnyOutput(replayFile)) { + // Immediately close the saving popup, the user doesn't care about it + core.runLater(guiSavingReplay::close); + + // We still have the replay, so we just save it (at least for a few weeks) in case they change their mind + String replayName = FilenameUtils.getBaseName(outputPath.getFileName().toString()); + Path rawFolder = ReplayMod.instance.getRawReplayFolder(); + Path rawPath = rawFolder.resolve(outputPath.getFileName()); + for (int i = 1; Files.exists(rawPath); i++) { + rawPath = rawPath.resolveSibling(replayName + "." + i + ".mcpr"); + } + Files.createDirectories(rawPath.getParent()); + replayFile.saveTo(rawPath.toFile()); + replayFile.close(); + return; + } + replayFile.save(); replayFile.close(); From 99b1528dccef116f5d3ef44ba68da9a5102f4680 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 22 Nov 2020 16:18:22 +0100 Subject: [PATCH 12/12] Update doRelease task for current branching model --- build.gradle.kts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 184e5ed3..0193959a 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -144,13 +144,19 @@ val doRelease by tasks.registering { listOf(version, null) } - // Create new commit + // Merge release branch into stable but do not yet commit (we need to update the version.txt first) + command("git", "checkout", "stable") + command("git", "merge", "--no-ff", "--no-commit", "release-$version") + + // Update version.txt + file("version.txt").writeText("$version\n") + command("git", "add", "version.txt") + + // Finallize the merge. The message is what is later used to identify releses for building the version.json tree val commitMessage = if (preVersion != null) "Pre-release $preVersion of $modVersion" else "Release $modVersion" - file("version.txt").writeText("$version\n") - command("git", "add", "version.txt") command("git", "commit", "-m", commitMessage) // Generate versions.json content