diff --git a/libs/replaystudio-0.0.1-SNAPSHOT-all.jar b/libs/replaystudio-0.0.1-SNAPSHOT-all.jar index b5b7ec72..6c15f5c5 100644 Binary files a/libs/replaystudio-0.0.1-SNAPSHOT-all.jar and b/libs/replaystudio-0.0.1-SNAPSHOT-all.jar differ diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/replayeditor/GuiConnectPart.java b/src/main/java/eu/crushedpixel/replaymod/gui/replayeditor/GuiConnectPart.java index 34ea1b9c..b1048c4d 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/replayeditor/GuiConnectPart.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/replayeditor/GuiConnectPart.java @@ -5,6 +5,7 @@ import eu.crushedpixel.replaymod.gui.elements.GuiArrowButton; import eu.crushedpixel.replaymod.gui.elements.GuiDropdown; import eu.crushedpixel.replaymod.gui.elements.GuiEntryList; import eu.crushedpixel.replaymod.gui.elements.listeners.SelectionListener; +import eu.crushedpixel.replaymod.studio.StudioImplementation; import eu.crushedpixel.replaymod.utils.ReplayFileIO; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; @@ -42,7 +43,22 @@ public class GuiConnectPart extends GuiStudioPart { @Override public void applyFilters(File replayFile, File outputFile) { - //TODO + try { + List inputFiles = new ArrayList(); + OUTER: + for (String fileName : filesToConcat) { + for (File file : replayFiles) { + if (fileName.equals(FilenameUtils.getBaseName(file.getAbsolutePath()))) { + inputFiles.add(file); + continue OUTER; + } + } + throw new RuntimeException(fileName); + } + StudioImplementation.connectReplayFiles(inputFiles, outputFile); + } catch (Exception e) { + e.printStackTrace(); + } } @Override diff --git a/src/main/java/eu/crushedpixel/replaymod/studio/ConnectMetadataFilter.java b/src/main/java/eu/crushedpixel/replaymod/studio/ConnectMetadataFilter.java new file mode 100644 index 00000000..507ff802 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/studio/ConnectMetadataFilter.java @@ -0,0 +1,155 @@ +package eu.crushedpixel.replaymod.studio; + +import com.google.common.base.Optional; +import com.google.gson.JsonObject; +import de.johni0702.replaystudio.PacketData; +import de.johni0702.replaystudio.Studio; +import de.johni0702.replaystudio.filter.StreamFilter; +import de.johni0702.replaystudio.replay.ReplayFile; +import de.johni0702.replaystudio.replay.ReplayMetaData; +import de.johni0702.replaystudio.stream.PacketStream; +import net.minecraft.client.Minecraft; +import org.apache.commons.io.IOUtils; +import org.spacehq.mc.protocol.packet.ingame.server.ServerResourcePackSendPacket; +import org.spacehq.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket; +import org.spacehq.packetlib.packet.Packet; + +import java.io.*; +import java.nio.file.Files; +import java.util.*; + +// TODO Reset resourcepack when changing from a resourcepack replay to a normal replay +public class ConnectMetadataFilter implements StreamFilter { + private ReplayFile replayFile; + private Set players = new HashSet(); + private Set invisiblePlayers = new HashSet(); + private long duration; + private Map resourcePacks = new HashMap(); + private Map resourcePackIndex = new HashMap(); + + @Override + public String getName() { + return "connect_metadata"; + } + + public void setInputReplay(ReplayFile replayFile) { + this.replayFile = replayFile; + } + + @Override + public void init(Studio studio, JsonObject jsonObject) { + + } + + @Override + public void onStart(PacketStream packetStream) { + try { + Optional> invisible = replayFile.getInvisiblePlayers(); + if (invisible.isPresent()) { + invisiblePlayers.addAll(invisible.get()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean onPacket(PacketStream packetStream, PacketData packetData) { + Packet packet = packetData.getPacket(); + if (packet instanceof ServerSpawnPlayerPacket) { + players.add(((ServerSpawnPlayerPacket) packet).getUUID().toString()); + } + if (packet instanceof ServerResourcePackSendPacket) { + String url = ((ServerResourcePackSendPacket) packet).getUrl(); + if (url.startsWith("replay://")) { + int id = Integer.parseInt(url.substring("replay://".length())); + try { + Map oldIndex = replayFile.getResourcePackIndex(); + if (oldIndex != null) { + String hash = oldIndex.get(id); + if (hash != null) { + // Make sure we have the resource pack + File file = resourcePacks.get(hash); + if (file == null) { + Optional in = replayFile.getResourcePack(hash); + if (in.isPresent()) { + try { + file = Files.createTempFile("replaymod", "resourcepack").toFile(); + OutputStream out = new FileOutputStream(file); + try { + IOUtils.copy(in.get(), out); + } finally { + IOUtils.closeQuietly(out); + } + } finally { + IOUtils.closeQuietly(in.get()); + } + resourcePacks.put(hash, file); + } + } + + // Re-Index the current packet + id = resourcePackIndex.size(); + resourcePackIndex.put(id, hash); + url = "replay://" + id; + packetStream.insert(packetData.getTime(), new ServerResourcePackSendPacket(url, "")); + return false; + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + return true; + } + + @Override + public void onEnd(PacketStream packetStream, long l) { + if (l > duration) { + duration = l; + } + } + + public void writeTo(ReplayFile output) throws IOException { + String mcVersion = Minecraft.getMinecraft().getVersion(); + String[] split = mcVersion.split("-"); + if(split.length > 0) { + mcVersion = split[0]; + } + + ReplayMetaData metaData = new ReplayMetaData(); + metaData.setDuration((int) duration); + metaData.setSingleplayer(false); + metaData.setServerName("Multiple worlds"); + metaData.setMcVersion(mcVersion); + metaData.setDate(System.currentTimeMillis()); + metaData.setPlayers(players.toArray(new String[players.size()])); + output.writeMetaData(metaData); + + if (!invisiblePlayers.isEmpty()) { + output.writeInvisiblePlayers(invisiblePlayers); + } + + if (!resourcePackIndex.isEmpty()) { + output.writeResourcePackIndex(resourcePackIndex); + + // Only store resource packs we really need + resourcePacks.keySet().retainAll(resourcePackIndex.values()); + + for (Map.Entry e : resourcePacks.entrySet()) { + OutputStream out = output.writeResourcePack(e.getKey()); + try { + InputStream in = new FileInputStream(e.getValue()); + try { + IOUtils.copy(in, out); + } finally { + IOUtils.closeQuietly(in); + } + } finally { + IOUtils.closeQuietly(out); + } + } + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/studio/StudioImplementation.java b/src/main/java/eu/crushedpixel/replaymod/studio/StudioImplementation.java index e16cf85a..ff1b8706 100755 --- a/src/main/java/eu/crushedpixel/replaymod/studio/StudioImplementation.java +++ b/src/main/java/eu/crushedpixel/replaymod/studio/StudioImplementation.java @@ -3,6 +3,7 @@ package eu.crushedpixel.replaymod.studio; import com.google.gson.JsonObject; import de.johni0702.replaystudio.PacketData; import de.johni0702.replaystudio.filter.ChangeTimestampFilter; +import de.johni0702.replaystudio.filter.NeutralizerFilter; import de.johni0702.replaystudio.filter.RemoveFilter; import de.johni0702.replaystudio.filter.SquashFilter; import de.johni0702.replaystudio.io.ReplayOutputStream; @@ -11,6 +12,7 @@ import de.johni0702.replaystudio.replay.ReplayMetaData; import de.johni0702.replaystudio.replay.ZipReplayFile; import de.johni0702.replaystudio.stream.PacketStream; import de.johni0702.replaystudio.studio.ReplayStudio; +import org.apache.commons.lang3.Validate; import java.io.File; import java.io.IOException; @@ -54,8 +56,48 @@ public class StudioImplementation { replayFile.saveTo(outputFile); } - //TODO Work with Johni to connect multiple Replay Files - public static void connectReplayFiles(List filesToConnect, File outputFile) { + public static void connectReplayFiles(List filesToConnect, File outputFile) throws IOException { + Validate.notEmpty(filesToConnect); + ReplayStudio studio = new ReplayStudio(); + studio.setWrappingEnabled(false); + ReplayFile outputReplayFile = new ZipReplayFile(studio, outputFile); + ReplayOutputStream out = outputReplayFile.writePacketData(); + + ConnectMetadataFilter metaDataFilter = new ConnectMetadataFilter(); + int startTime = 0; + for (File file : filesToConnect) { + ReplayFile replayFile = new ZipReplayFile(studio, file); + PacketStream stream = studio.createReplayStream(replayFile.getPacketData(), true); + ReplayMetaData metaData = replayFile.getMetaData(); + + stream.addFilter(new NeutralizerFilter()); + + if (startTime > 0) { + ChangeTimestampFilter ctf = new ChangeTimestampFilter(); + JsonObject cfg = new JsonObject(); + cfg.addProperty("offset", startTime); + ctf.init(studio, cfg); + stream.addFilter(ctf); + } + + metaDataFilter.setInputReplay(replayFile); + stream.addFilter(metaDataFilter); + + PacketData packetData; + while ((packetData = stream.next()) != null) { + out.write(packetData); + } + for (PacketData data : stream.end()) { + out.write(data); + } + + startTime += metaData.getDuration(); + } + + out.close(); + + metaDataFilter.writeTo(outputReplayFile); + outputReplayFile.saveTo(outputFile); } }