Clean up parts of the recording module and add support for replay recovery after a crash
This commit is contained in:
Submodule ReplayStudio updated: a91987f72e...3895d9535f
2
jGui
2
jGui
Submodule jGui updated: 96bdfcb7d9...611fa2a4ef
@@ -1,8 +1,11 @@
|
|||||||
package com.replaymod.core;
|
package com.replaymod.core;
|
||||||
|
|
||||||
|
import com.google.common.io.Files;
|
||||||
import com.google.common.util.concurrent.ListenableFutureTask;
|
import com.google.common.util.concurrent.ListenableFutureTask;
|
||||||
|
import com.replaymod.core.gui.RestoreReplayGui;
|
||||||
import com.replaymod.replay.ReplaySender;
|
import com.replaymod.replay.ReplaySender;
|
||||||
import com.replaymod.replaystudio.util.I18n;
|
import com.replaymod.replaystudio.util.I18n;
|
||||||
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
import eu.crushedpixel.replaymod.chat.ChatMessageHandler;
|
import eu.crushedpixel.replaymod.chat.ChatMessageHandler;
|
||||||
import eu.crushedpixel.replaymod.events.handlers.CrosshairRenderHandler;
|
import eu.crushedpixel.replaymod.events.handlers.CrosshairRenderHandler;
|
||||||
import eu.crushedpixel.replaymod.events.handlers.GuiEventHandler;
|
import eu.crushedpixel.replaymod.events.handlers.GuiEventHandler;
|
||||||
@@ -326,6 +329,23 @@ public class ReplayMod {
|
|||||||
|
|
||||||
testIfMoeshAndExitMinecraft();
|
testIfMoeshAndExitMinecraft();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runLater(() -> {
|
||||||
|
// Restore corrupted replays
|
||||||
|
try {
|
||||||
|
File[] files = getReplayFolder().listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isDirectory() && file.getName().endsWith(".mcpr.tmp")) {
|
||||||
|
File origFile = new File(file.getParentFile(), Files.getNameWithoutExtension(file.getName()));
|
||||||
|
new RestoreReplayGui(GuiScreen.wrap(mc.currentScreen), origFile).display();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runLater(Runnable runnable) {
|
public void runLater(Runnable runnable) {
|
||||||
|
|||||||
86
src/main/java/com/replaymod/core/gui/RestoreReplayGui.java
Normal file
86
src/main/java/com/replaymod/core/gui/RestoreReplayGui.java
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package com.replaymod.core.gui;
|
||||||
|
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.replaymod.replaystudio.PacketData;
|
||||||
|
import com.replaymod.replaystudio.io.ReplayInputStream;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
|
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
||||||
|
import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||||
|
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
|
||||||
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
|
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 java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
|
||||||
|
|
||||||
|
public final GuiScreen parent;
|
||||||
|
public final File file;
|
||||||
|
public final GuiPanel textPanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(3));
|
||||||
|
public final GuiPanel buttonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5));
|
||||||
|
public final GuiPanel contentPanel = new GuiPanel(this).addElements(new VerticalLayout.Data(0.5),
|
||||||
|
textPanel, buttonPanel).setLayout(new VerticalLayout().setSpacing(20));
|
||||||
|
public final GuiButton yesButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.yes");
|
||||||
|
public final GuiButton noButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.no");
|
||||||
|
|
||||||
|
public RestoreReplayGui(GuiScreen parent, File file) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.file = file;
|
||||||
|
|
||||||
|
textPanel.addElements(new VerticalLayout.Data(0.5),
|
||||||
|
new GuiLabel().setI18nText("replaymod.gui.restorereplay1"),
|
||||||
|
new GuiLabel().setI18nText("replaymod.gui.restorereplay2", Files.getNameWithoutExtension(file.getName())),
|
||||||
|
new GuiLabel().setI18nText("replaymod.gui.restorereplay3"));
|
||||||
|
yesButton.onClick(() -> {
|
||||||
|
try {
|
||||||
|
ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), null, file);
|
||||||
|
ReplayMetaData metaData = replayFile.getMetaData();
|
||||||
|
if (metaData != null && metaData.getDuration() == 0) {
|
||||||
|
// Try to restore replay duration
|
||||||
|
try (ReplayInputStream in = replayFile.getPacketData()) {
|
||||||
|
PacketData last = null;
|
||||||
|
while ((last = in.readPacket()) != null) {
|
||||||
|
metaData.setDuration((int) last.getTime());
|
||||||
|
}
|
||||||
|
replayFile.writeMetaData(metaData);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replayFile.save();
|
||||||
|
replayFile.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
parent.display();
|
||||||
|
});
|
||||||
|
noButton.onClick(() -> {
|
||||||
|
try {
|
||||||
|
Files.move(new File(file.getParentFile(), file.getName() + ".tmp"),
|
||||||
|
new File(file.getParentFile(), file.getName() + ".del"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
parent.display();
|
||||||
|
});
|
||||||
|
|
||||||
|
setLayout(new CustomLayout<RestoreReplayGui>() {
|
||||||
|
@Override
|
||||||
|
protected void layout(RestoreReplayGui container, int width, int height) {
|
||||||
|
pos(contentPanel, width / 2 - width(contentPanel) / 2, height / 2 - height(contentPanel) / 2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RestoreReplayGui getThis() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.replaymod.recording.Setting;
|
|||||||
import com.replaymod.recording.gui.GuiRecordingOverlay;
|
import com.replaymod.recording.gui.GuiRecordingOverlay;
|
||||||
import com.replaymod.recording.packet.PacketListener;
|
import com.replaymod.recording.packet.PacketListener;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
@@ -79,7 +80,13 @@ public class ConnectionEventHandler {
|
|||||||
File currentFile = new File(folder, name + ".mcpr");
|
File currentFile = new File(folder, name + ".mcpr");
|
||||||
ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), currentFile);
|
ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), currentFile);
|
||||||
|
|
||||||
packetListener = new PacketListener(replayFile, name, worldName, System.currentTimeMillis(), event.isLocal);
|
ReplayMetaData metaData = new ReplayMetaData();
|
||||||
|
metaData.setSingleplayer(event.isLocal);
|
||||||
|
metaData.setServerName(worldName);
|
||||||
|
metaData.setGenerator("ReplayMod v" + ReplayMod.getContainer().getVersion());
|
||||||
|
metaData.setDate(System.currentTimeMillis());
|
||||||
|
metaData.setMcVersion(ReplayMod.getMinecraftVersion());
|
||||||
|
packetListener = new PacketListener(replayFile, metaData);
|
||||||
pipeline.addBefore(packetHandlerKey, "replay_recorder", packetListener);
|
pipeline.addBefore(packetHandlerKey, "replay_recorder", packetListener);
|
||||||
|
|
||||||
recordingEventHandler = new RecordingEventHandler(packetListener);
|
recordingEventHandler = new RecordingEventHandler(packetListener);
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ public class RecordingEventHandler {
|
|||||||
if (mc.isIntegratedServerRunning()) {
|
if (mc.isIntegratedServerRunning()) {
|
||||||
IntegratedServer server = mc.getIntegratedServer();
|
IntegratedServer server = mc.getIntegratedServer();
|
||||||
if (server != null && server.isGamePaused) {
|
if (server != null && server.isGamePaused) {
|
||||||
packetListener.serverWasPaused = true;
|
packetListener.setServerWasPaused();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
package com.replaymod.recording.packet;
|
|
||||||
|
|
||||||
import com.google.common.hash.Hashing;
|
|
||||||
import com.google.common.io.Files;
|
|
||||||
import com.replaymod.core.ReplayMod;
|
|
||||||
import com.replaymod.replaystudio.data.Marker;
|
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
|
||||||
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
|
||||||
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public abstract class DataListener extends ChannelInboundHandlerAdapter {
|
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger();
|
|
||||||
|
|
||||||
private final ReplayFile replayFile;
|
|
||||||
protected final DataWriter dataWriter;
|
|
||||||
|
|
||||||
protected Long startTime = null;
|
|
||||||
protected String name;
|
|
||||||
protected String worldName;
|
|
||||||
public boolean serverWasPaused;
|
|
||||||
protected long lastSentPacket = 0;
|
|
||||||
protected boolean alive = true;
|
|
||||||
protected Set<String> players = new HashSet<>();
|
|
||||||
private boolean singleplayer;
|
|
||||||
|
|
||||||
private final Set<Marker> markers = new HashSet<>();
|
|
||||||
private final Map<Integer, String> requestToHash = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public DataListener(ReplayFile replayFile, String name, String worldName, long startTime, boolean singleplayer) throws IOException {
|
|
||||||
this.replayFile = replayFile;
|
|
||||||
this.startTime = startTime;
|
|
||||||
this.name = name;
|
|
||||||
this.worldName = worldName;
|
|
||||||
this.singleplayer = singleplayer;
|
|
||||||
|
|
||||||
dataWriter = new DataWriter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) {
|
|
||||||
dataWriter.requestFinish();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void recordResourcePack(File file, int requestId) {
|
|
||||||
try {
|
|
||||||
String hash = Hashing.sha1().hashBytes(Files.toByteArray(file)).toString();
|
|
||||||
synchronized (dataWriter) {
|
|
||||||
if (!requestToHash.containsValue(hash)) {
|
|
||||||
try (OutputStream out = replayFile.writeResourcePack(hash)) {
|
|
||||||
FileUtils.copyFile(file, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestToHash.put(requestId, hash);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("Failed to save resource pack.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addMarker() {
|
|
||||||
AdvancedPosition pos = new AdvancedPosition(Minecraft.getMinecraft().getRenderViewEntity());
|
|
||||||
int timestamp = (int) (System.currentTimeMillis() - startTime);
|
|
||||||
|
|
||||||
Marker marker = new Marker();
|
|
||||||
marker.setTime(timestamp);
|
|
||||||
marker.setX(pos.getX());
|
|
||||||
marker.setY(pos.getY());
|
|
||||||
marker.setZ(pos.getZ());
|
|
||||||
marker.setYaw((float) pos.getYaw());
|
|
||||||
marker.setPitch((float) pos.getPitch());
|
|
||||||
marker.setRoll((float) pos.getRoll());
|
|
||||||
markers.add(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DataWriter {
|
|
||||||
private long paused;
|
|
||||||
|
|
||||||
private DataOutputStream stream;
|
|
||||||
private ExecutorService saveService = Executors.newSingleThreadExecutor();
|
|
||||||
|
|
||||||
private final Thread shutdownHook = new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (!saveService.isShutdown()) {
|
|
||||||
requestFinish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, "shutdown-hook-data-listener");
|
|
||||||
|
|
||||||
public DataWriter() throws IOException {
|
|
||||||
stream = new DataOutputStream(replayFile.writePacketData());
|
|
||||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void writePacket(final byte[] bytes) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if(startTime == null) {
|
|
||||||
startTime = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverWasPaused) {
|
|
||||||
paused = now - startTime - lastSentPacket;
|
|
||||||
serverWasPaused = false;
|
|
||||||
}
|
|
||||||
final int timestamp = (int) (now - startTime - paused);
|
|
||||||
lastSentPacket = timestamp;
|
|
||||||
saveService.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
stream.writeInt(timestamp);
|
|
||||||
stream.writeInt(bytes.length);
|
|
||||||
stream.write(bytes);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void requestFinish() {
|
|
||||||
if (saveService.isShutdown()) return;
|
|
||||||
// TODO: Add some GUI showing saving status
|
|
||||||
try {
|
|
||||||
Runtime.getRuntime().removeShutdownHook(shutdownHook);
|
|
||||||
saveService.shutdown();
|
|
||||||
saveService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
|
|
||||||
stream.close();
|
|
||||||
|
|
||||||
String mcversion = ReplayMod.getMinecraftVersion();
|
|
||||||
|
|
||||||
String[] pl = players.toArray(new String[players.size()]);
|
|
||||||
|
|
||||||
String generator = "ReplayMod v" + ReplayMod.getContainer().getVersion();
|
|
||||||
|
|
||||||
ReplayMetaData metaData = new ReplayMetaData();
|
|
||||||
metaData.setSingleplayer(singleplayer);
|
|
||||||
metaData.setServerName(worldName);
|
|
||||||
metaData.setGenerator(generator);
|
|
||||||
metaData.setDuration((int) lastSentPacket);
|
|
||||||
metaData.setDate(startTime);
|
|
||||||
metaData.setPlayers(pl);
|
|
||||||
metaData.setMcVersion(mcversion);
|
|
||||||
|
|
||||||
replayFile.writeMetaData(metaData);
|
|
||||||
replayFile.writeMarkers(markers);
|
|
||||||
replayFile.writeResourcePackIndex(requestToHash);
|
|
||||||
replayFile.save();
|
|
||||||
} catch(Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
// TODO: Always close GUI
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,64 +1,137 @@
|
|||||||
package com.replaymod.recording.packet;
|
package com.replaymod.recording.packet;
|
||||||
|
|
||||||
import com.google.common.hash.Hashing;
|
|
||||||
import com.google.common.io.Files;
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
|
||||||
import com.replaymod.core.utils.Restrictions;
|
import com.replaymod.core.utils.Restrictions;
|
||||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
import com.replaymod.replaystudio.data.Marker;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
|
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.GuiScreenWorking;
|
|
||||||
import net.minecraft.client.gui.GuiYesNo;
|
|
||||||
import net.minecraft.client.gui.GuiYesNoCallback;
|
|
||||||
import net.minecraft.client.multiplayer.ServerData;
|
|
||||||
import net.minecraft.client.multiplayer.ServerList;
|
|
||||||
import net.minecraft.client.network.NetHandlerPlayClient;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
import net.minecraft.client.resources.ResourcePackRepository;
|
|
||||||
import net.minecraft.entity.DataWatcher;
|
import net.minecraft.entity.DataWatcher;
|
||||||
import net.minecraft.network.NetworkManager;
|
import net.minecraft.network.EnumConnectionState;
|
||||||
|
import net.minecraft.network.EnumPacketDirection;
|
||||||
import net.minecraft.network.Packet;
|
import net.minecraft.network.Packet;
|
||||||
import net.minecraft.network.play.client.C19PacketResourcePackStatus;
|
import net.minecraft.network.PacketBuffer;
|
||||||
import net.minecraft.network.play.server.*;
|
import net.minecraft.network.play.server.*;
|
||||||
import net.minecraft.util.ChatComponentText;
|
import net.minecraft.util.ChatComponentText;
|
||||||
import net.minecraft.util.HttpUtil;
|
import net.minecraftforge.fml.common.network.internal.FMLProxyPacket;
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.UUID;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class PacketListener extends DataListener {
|
public class PacketListener extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
private static final Minecraft mc = Minecraft.getMinecraft();
|
private static final Minecraft mc = Minecraft.getMinecraft();
|
||||||
private static final Logger logger = LogManager.getLogger();
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
|
||||||
|
private final ReplayFile replayFile;
|
||||||
|
|
||||||
|
private final ResourcePackRecorder resourcePackRecorder;
|
||||||
|
|
||||||
|
private final ExecutorService saveService = Executors.newSingleThreadExecutor();
|
||||||
|
private final DataOutputStream packetOutputStream;
|
||||||
|
|
||||||
|
private ReplayMetaData metaData;
|
||||||
|
|
||||||
private ChannelHandlerContext context = null;
|
private ChannelHandlerContext context = null;
|
||||||
|
|
||||||
private int nextRequestId;
|
private final long startTime;
|
||||||
|
private long lastSentPacket;
|
||||||
|
private long timePassedWhilePaused;
|
||||||
|
private volatile boolean serverWasPaused;
|
||||||
|
|
||||||
public PacketListener(ReplayFile file, String name, String worldName, long startTime, boolean singleplayer) throws IOException {
|
/**
|
||||||
super(file, name, worldName, startTime, singleplayer);
|
* Used to keep track of the last metadata save job submitted to the save service and
|
||||||
|
* as such prevents unnecessary writes.
|
||||||
|
*/
|
||||||
|
private final AtomicInteger lastSaveMetaDataId = new AtomicInteger();
|
||||||
|
|
||||||
|
public PacketListener(ReplayFile replayFile, ReplayMetaData metaData) throws IOException {
|
||||||
|
this.replayFile = replayFile;
|
||||||
|
this.metaData = metaData;
|
||||||
|
this.resourcePackRecorder = new ResourcePackRecorder(replayFile);
|
||||||
|
this.packetOutputStream = new DataOutputStream(replayFile.writePacketData());
|
||||||
|
this.startTime = metaData.getDate();
|
||||||
|
|
||||||
|
saveMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveMetaData() {
|
||||||
|
int id = lastSaveMetaDataId.incrementAndGet();
|
||||||
|
saveService.submit(() -> {
|
||||||
|
if (lastSaveMetaDataId.get() != id) {
|
||||||
|
return; // Another job has been scheduled, it will do the hard work.
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
synchronized (replayFile) {
|
||||||
|
replayFile.writeMetaData(metaData);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Writing metadata:", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(Packet packet) {
|
public void save(Packet packet) {
|
||||||
try {
|
try {
|
||||||
if(packet instanceof S0CPacketSpawnPlayer) {
|
if(packet instanceof S0CPacketSpawnPlayer) {
|
||||||
UUID uuid = ((S0CPacketSpawnPlayer) packet).func_179819_c();
|
UUID uuid = ((S0CPacketSpawnPlayer) packet).func_179819_c();
|
||||||
players.add(uuid.toString());
|
Set<String> uuids = new HashSet<>(Arrays.asList(metaData.getPlayers()));
|
||||||
|
uuids.add(uuid.toString());
|
||||||
|
metaData.setPlayers(uuids.toArray(new String[uuids.size()]));
|
||||||
|
saveMetaData();
|
||||||
}
|
}
|
||||||
|
|
||||||
dataWriter.writePacket(getPacketData(packet));
|
byte[] bytes = getPacketData(packet);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
saveService.submit(() -> {
|
||||||
|
if (serverWasPaused) {
|
||||||
|
timePassedWhilePaused = now - startTime - lastSentPacket;
|
||||||
|
serverWasPaused = false;
|
||||||
|
}
|
||||||
|
int timestamp = (int) (now - startTime - timePassedWhilePaused);
|
||||||
|
lastSentPacket = timestamp;
|
||||||
|
try {
|
||||||
|
packetOutputStream.writeInt(timestamp);
|
||||||
|
packetOutputStream.writeInt(bytes.length);
|
||||||
|
packetOutputStream.write(bytes);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
e.printStackTrace();
|
logger.error("Writing packet:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) {
|
||||||
|
metaData.setDuration((int) lastSentPacket);
|
||||||
|
saveMetaData();
|
||||||
|
|
||||||
|
saveService.shutdown();
|
||||||
|
try {
|
||||||
|
saveService.awaitTermination(10, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.error("Waiting for save service termination:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (replayFile) {
|
||||||
|
try {
|
||||||
|
replayFile.save();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Saving replay file:", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,11 +145,8 @@ public class PacketListener extends DataListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.context = ctx;
|
this.context = ctx;
|
||||||
if(!alive) {
|
|
||||||
super.channelRead(ctx, msg);
|
if (msg instanceof Packet) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(msg instanceof Packet) {
|
|
||||||
try {
|
try {
|
||||||
Packet packet = (Packet) msg;
|
Packet packet = (Packet) msg;
|
||||||
|
|
||||||
@@ -88,29 +158,28 @@ public class PacketListener extends DataListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(packet instanceof S0CPacketSpawnPlayer) {
|
|
||||||
UUID uuid = ((S0CPacketSpawnPlayer) packet).func_179819_c();
|
|
||||||
players.add(uuid.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet instanceof S48PacketResourcePackSend) {
|
if (packet instanceof S48PacketResourcePackSend) {
|
||||||
S48PacketResourcePackSend p = (S48PacketResourcePackSend) packet;
|
save(resourcePackRecorder.handleResourcePack((S48PacketResourcePackSend) packet));
|
||||||
int requestId = handleResourcePack(p);
|
|
||||||
save(new S48PacketResourcePackSend("replay://" + requestId, ""));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dataWriter.writePacket(getPacketData(packet));
|
if (packet instanceof FMLProxyPacket) {
|
||||||
|
// This packet requires special handling
|
||||||
|
super.channelRead(ctx, msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
save(packet);
|
||||||
|
|
||||||
if (packet instanceof S3FPacketCustomPayload) {
|
if (packet instanceof S3FPacketCustomPayload) {
|
||||||
S3FPacketCustomPayload p = (S3FPacketCustomPayload) packet;
|
S3FPacketCustomPayload p = (S3FPacketCustomPayload) packet;
|
||||||
if (Restrictions.PLUGIN_CHANNEL.equals(p.getChannelName())) {
|
if (Restrictions.PLUGIN_CHANNEL.equals(p.getChannelName())) {
|
||||||
packet = new S40PacketDisconnect(new ChatComponentText("Please update to view this replay."));
|
packet = new S40PacketDisconnect(new ChatComponentText("Please update to view this replay."));
|
||||||
dataWriter.writePacket(getPacketData(packet));
|
save(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
e.printStackTrace();
|
logger.error("Handling packet for recording:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -119,7 +188,7 @@ public class PacketListener extends DataListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private byte[] getPacketData(Packet packet) {
|
private byte[] getPacketData(Packet packet) throws IOException {
|
||||||
if(packet instanceof S0FPacketSpawnMob) {
|
if(packet instanceof S0FPacketSpawnMob) {
|
||||||
S0FPacketSpawnMob p = (S0FPacketSpawnMob) packet;
|
S0FPacketSpawnMob p = (S0FPacketSpawnMob) packet;
|
||||||
if (p.field_149043_l == null) {
|
if (p.field_149043_l == null) {
|
||||||
@@ -144,155 +213,49 @@ public class PacketListener extends DataListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ReplayFileIO.serializePacket(packet);
|
Integer packetId = EnumConnectionState.PLAY.getPacketId(EnumPacketDirection.CLIENTBOUND, packet);
|
||||||
}
|
if (packetId == null) {
|
||||||
|
throw new IOException("Unknown packet type:" + packet.getClass());
|
||||||
public synchronized int handleResourcePack(S48PacketResourcePackSend packet) {
|
|
||||||
final int requestId = nextRequestId++;
|
|
||||||
final NetHandlerPlayClient netHandler = mc.getNetHandler();
|
|
||||||
final NetworkManager netManager = netHandler.getNetworkManager();
|
|
||||||
final String url = packet.func_179783_a();
|
|
||||||
final String hash = packet.func_179784_b();
|
|
||||||
|
|
||||||
if (url.startsWith("level://")) {
|
|
||||||
String levelName = url.substring("level://".length());
|
|
||||||
File savesDir = new File(mc.mcDataDir, "saves");
|
|
||||||
final File levelDir = new File(savesDir, levelName);
|
|
||||||
|
|
||||||
if (levelDir.isFile()) {
|
|
||||||
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
|
|
||||||
Futures.addCallback(mc.getResourcePackRepository().func_177319_a(levelDir), new FutureCallback() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Object result) {
|
|
||||||
recordResourcePack(levelDir, requestId);
|
|
||||||
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@Nonnull Throwable throwable) {
|
|
||||||
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final ServerData serverData = mc.getCurrentServerData();
|
|
||||||
if (serverData != null && serverData.getResourceMode() == ServerData.ServerResourceMode.ENABLED) {
|
|
||||||
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
|
|
||||||
downloadResourcePackFuture(requestId, url, hash);
|
|
||||||
} else if (serverData != null && serverData.getResourceMode() != ServerData.ServerResourceMode.PROMPT) {
|
|
||||||
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED));
|
|
||||||
} else {
|
|
||||||
mc.addScheduledTask(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mc.displayGuiScreen(new GuiYesNo(new GuiYesNoCallback() {
|
|
||||||
@Override
|
|
||||||
public void confirmClicked(boolean result, int id) {
|
|
||||||
if (serverData != null) {
|
|
||||||
serverData.setResourceMode(result ? ServerData.ServerResourceMode.ENABLED : ServerData.ServerResourceMode.DISABLED);
|
|
||||||
}
|
|
||||||
if (result) {
|
|
||||||
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
|
|
||||||
downloadResourcePackFuture(requestId, url, hash);
|
|
||||||
} else {
|
|
||||||
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED));
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerList.func_147414_b(serverData);
|
|
||||||
mc.displayGuiScreen(null);
|
|
||||||
}
|
|
||||||
}, I18n.format("multiplayer.texturePrompt.line1"), I18n.format("multiplayer.texturePrompt.line2"), 0));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return requestId;
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
PacketBuffer packetBuffer = new PacketBuffer(byteBuf);
|
||||||
|
packetBuffer.writeVarIntToBuffer(packetId);
|
||||||
|
packet.writePacketData(packetBuffer);
|
||||||
|
|
||||||
|
byteBuf.readerIndex(0);
|
||||||
|
byte[] array = new byte[byteBuf.readableBytes()];
|
||||||
|
byteBuf.readBytes(array);
|
||||||
|
|
||||||
|
byteBuf.release();
|
||||||
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadResourcePackFuture(int requestId, String url, final String hash) {
|
public void addMarker() {
|
||||||
Futures.addCallback(downloadResourcePack(requestId, url, hash), new FutureCallback() {
|
AdvancedPosition pos = new AdvancedPosition(Minecraft.getMinecraft().getRenderViewEntity());
|
||||||
@Override
|
int timestamp = (int) (System.currentTimeMillis() - startTime);
|
||||||
public void onSuccess(Object result) {
|
|
||||||
mc.getNetHandler().addToSendQueue(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
Marker marker = new Marker();
|
||||||
public void onFailure(@Nonnull Throwable throwable) {
|
marker.setTime(timestamp);
|
||||||
mc.getNetHandler().addToSendQueue(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
|
marker.setX(pos.getX());
|
||||||
|
marker.setY(pos.getY());
|
||||||
|
marker.setZ(pos.getZ());
|
||||||
|
marker.setYaw((float) pos.getYaw());
|
||||||
|
marker.setPitch((float) pos.getPitch());
|
||||||
|
marker.setRoll((float) pos.getRoll());
|
||||||
|
saveService.submit(() -> {
|
||||||
|
synchronized (replayFile) {
|
||||||
|
try {
|
||||||
|
Set<Marker> markers = replayFile.getMarkers().or(HashSet::new);
|
||||||
|
markers.add(marker);
|
||||||
|
replayFile.writeMarkers(markers);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Writing markers:", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFuture downloadResourcePack(final int requestId, String url, String hash) {
|
public void setServerWasPaused() {
|
||||||
final ResourcePackRepository repo = mc.mcResourcePackRepository;
|
this.serverWasPaused = true;
|
||||||
String fileName;
|
|
||||||
if (hash.matches("^[a-f0-9]{40}$")) {
|
|
||||||
fileName = hash;
|
|
||||||
} else {
|
|
||||||
fileName = url.substring(url.lastIndexOf("/") + 1);
|
|
||||||
|
|
||||||
if (fileName.contains("?")) {
|
|
||||||
fileName = fileName.substring(0, fileName.indexOf("?"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fileName.endsWith(".zip")) {
|
|
||||||
return Futures.immediateFailedFuture(new IllegalArgumentException("Invalid filename; must end in .zip"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName = "legacy_" + fileName.replaceAll("\\W", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
final File file = new File(repo.dirServerResourcepacks, fileName);
|
|
||||||
repo.field_177321_h.lock();
|
|
||||||
try {
|
|
||||||
repo.func_148529_f();
|
|
||||||
|
|
||||||
if (file.exists() && hash.length() == 40) {
|
|
||||||
try {
|
|
||||||
String fileHash = Hashing.sha1().hashBytes(Files.toByteArray(file)).toString();
|
|
||||||
if (fileHash.equals(hash)) {
|
|
||||||
recordResourcePack(file, requestId);
|
|
||||||
return repo.func_177319_a(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.warn("File " + file + " had wrong hash (expected " + hash + ", found " + fileHash + "). Deleting it.");
|
|
||||||
FileUtils.deleteQuietly(file);
|
|
||||||
} catch (IOException ioexception) {
|
|
||||||
logger.warn("File " + file + " couldn\'t be hashed. Deleting it.", ioexception);
|
|
||||||
FileUtils.deleteQuietly(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final GuiScreenWorking guiScreen = new GuiScreenWorking();
|
|
||||||
final Minecraft mc = Minecraft.getMinecraft();
|
|
||||||
|
|
||||||
Futures.getUnchecked(mc.addScheduledTask(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mc.displayGuiScreen(guiScreen);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
Map sessionInfo = Minecraft.getSessionInfo();
|
|
||||||
repo.field_177322_i = HttpUtil.func_180192_a(file, url, sessionInfo, 50 * 1024 * 1024, guiScreen, mc.getProxy());
|
|
||||||
Futures.addCallback(repo.field_177322_i, new FutureCallback() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Object value) {
|
|
||||||
recordResourcePack(file, requestId);
|
|
||||||
repo.func_177319_a(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@Nonnull Throwable throwable) {
|
|
||||||
throwable.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return repo.field_177322_i;
|
|
||||||
} finally {
|
|
||||||
repo.field_177321_h.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.replaymod.recording.packet;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import net.minecraft.network.*;
|
|
||||||
import net.minecraft.util.MessageSerializer;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class PacketSerializer extends MessageSerializer {
|
|
||||||
|
|
||||||
public PacketSerializer(EnumPacketDirection direction) {
|
|
||||||
super(direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf byteBuf) throws IOException {
|
|
||||||
EnumConnectionState state = ((EnumConnectionState) ctx.channel().attr(NetworkManager.attrKeyConnectionState).get());
|
|
||||||
encode(state, packet, byteBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void encode(EnumConnectionState state, Packet packet, ByteBuf byteBuf) {
|
|
||||||
Integer integer = state.getPacketId(EnumPacketDirection.CLIENTBOUND, packet);
|
|
||||||
|
|
||||||
if (integer != null) {
|
|
||||||
PacketBuffer packetbuffer = new PacketBuffer(byteBuf);
|
|
||||||
packetbuffer.writeVarIntToBuffer(integer);
|
|
||||||
|
|
||||||
try {
|
|
||||||
packet.writePacketData(packetbuffer);
|
|
||||||
} catch(Throwable throwable) {
|
|
||||||
throwable.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
package com.replaymod.recording.packet;
|
||||||
|
|
||||||
|
import com.google.common.hash.Hashing;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.GuiScreenWorking;
|
||||||
|
import net.minecraft.client.gui.GuiYesNo;
|
||||||
|
import net.minecraft.client.multiplayer.ServerData;
|
||||||
|
import net.minecraft.client.multiplayer.ServerList;
|
||||||
|
import net.minecraft.client.network.NetHandlerPlayClient;
|
||||||
|
import net.minecraft.client.resources.I18n;
|
||||||
|
import net.minecraft.client.resources.ResourcePackRepository;
|
||||||
|
import net.minecraft.network.NetworkManager;
|
||||||
|
import net.minecraft.network.play.client.C19PacketResourcePackStatus;
|
||||||
|
import net.minecraft.network.play.server.S48PacketResourcePackSend;
|
||||||
|
import net.minecraft.util.HttpUtil;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records resource packs and handles incoming resource pack packets during recording.
|
||||||
|
*/
|
||||||
|
public class ResourcePackRecorder {
|
||||||
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
private static final Minecraft mc = Minecraft.getMinecraft();
|
||||||
|
|
||||||
|
private final ReplayFile replayFile;
|
||||||
|
|
||||||
|
private int nextRequestId;
|
||||||
|
|
||||||
|
public ResourcePackRecorder(ReplayFile replayFile) {
|
||||||
|
this.replayFile = replayFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordResourcePack(File file, int requestId) {
|
||||||
|
try {
|
||||||
|
// Read in resource pack file
|
||||||
|
byte[] bytes = Files.toByteArray(file);
|
||||||
|
// Check whether it is already known
|
||||||
|
String hash = Hashing.sha1().hashBytes(bytes).toString();
|
||||||
|
boolean doWrite = false; // Whether we are the first and have to write it
|
||||||
|
synchronized (replayFile) { // Need to read, modify and write the resource pack index atomically
|
||||||
|
Map<Integer, String> index = replayFile.getResourcePackIndex();
|
||||||
|
if (!index.containsValue(hash)) {
|
||||||
|
// Hash is unknown, we have to write the resource pack ourselves
|
||||||
|
doWrite = true;
|
||||||
|
}
|
||||||
|
// Save this request
|
||||||
|
index.put(requestId, hash);
|
||||||
|
replayFile.writeResourcePackIndex(index);
|
||||||
|
}
|
||||||
|
if (doWrite) {
|
||||||
|
try (OutputStream out = replayFile.writeResourcePack(hash)) {
|
||||||
|
out.write(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to save resource pack.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized S48PacketResourcePackSend handleResourcePack(S48PacketResourcePackSend packet) {
|
||||||
|
final int requestId = nextRequestId++;
|
||||||
|
final NetHandlerPlayClient netHandler = mc.getNetHandler();
|
||||||
|
final NetworkManager netManager = netHandler.getNetworkManager();
|
||||||
|
final String url = packet.func_179783_a();
|
||||||
|
final String hash = packet.func_179784_b();
|
||||||
|
|
||||||
|
if (url.startsWith("level://")) {
|
||||||
|
String levelName = url.substring("level://".length());
|
||||||
|
File savesDir = new File(mc.mcDataDir, "saves");
|
||||||
|
final File levelDir = new File(savesDir, levelName);
|
||||||
|
|
||||||
|
if (levelDir.isFile()) {
|
||||||
|
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
|
||||||
|
Futures.addCallback(mc.getResourcePackRepository().func_177319_a(levelDir), new FutureCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object result) {
|
||||||
|
recordResourcePack(levelDir, requestId);
|
||||||
|
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@Nonnull Throwable throwable) {
|
||||||
|
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final ServerData serverData = mc.getCurrentServerData();
|
||||||
|
if (serverData != null && serverData.getResourceMode() == ServerData.ServerResourceMode.ENABLED) {
|
||||||
|
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
|
||||||
|
downloadResourcePackFuture(requestId, url, hash);
|
||||||
|
} else if (serverData != null && serverData.getResourceMode() != ServerData.ServerResourceMode.PROMPT) {
|
||||||
|
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED));
|
||||||
|
} else {
|
||||||
|
mc.addScheduledTask(() -> mc.displayGuiScreen(new GuiYesNo((result, id) -> {
|
||||||
|
if (serverData != null) {
|
||||||
|
serverData.setResourceMode(result ? ServerData.ServerResourceMode.ENABLED : ServerData.ServerResourceMode.DISABLED);
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
|
||||||
|
downloadResourcePackFuture(requestId, url, hash);
|
||||||
|
} else {
|
||||||
|
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED));
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerList.func_147414_b(serverData);
|
||||||
|
mc.displayGuiScreen(null);
|
||||||
|
}, I18n.format("multiplayer.texturePrompt.line1"), I18n.format("multiplayer.texturePrompt.line2"), 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new S48PacketResourcePackSend("replay://" + requestId, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadResourcePackFuture(int requestId, String url, final String hash) {
|
||||||
|
Futures.addCallback(downloadResourcePack(requestId, url, hash), new FutureCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object result) {
|
||||||
|
mc.getNetHandler().addToSendQueue(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@Nonnull Throwable throwable) {
|
||||||
|
mc.getNetHandler().addToSendQueue(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture downloadResourcePack(final int requestId, String url, String hash) {
|
||||||
|
final ResourcePackRepository repo = mc.mcResourcePackRepository;
|
||||||
|
String fileName;
|
||||||
|
if (hash.matches("^[a-f0-9]{40}$")) {
|
||||||
|
fileName = hash;
|
||||||
|
} else {
|
||||||
|
fileName = url.substring(url.lastIndexOf("/") + 1);
|
||||||
|
|
||||||
|
if (fileName.contains("?")) {
|
||||||
|
fileName = fileName.substring(0, fileName.indexOf("?"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileName.endsWith(".zip")) {
|
||||||
|
return Futures.immediateFailedFuture(new IllegalArgumentException("Invalid filename; must end in .zip"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName = "legacy_" + fileName.replaceAll("\\W", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
final File file = new File(repo.dirServerResourcepacks, fileName);
|
||||||
|
repo.field_177321_h.lock();
|
||||||
|
try {
|
||||||
|
repo.func_148529_f();
|
||||||
|
|
||||||
|
if (file.exists() && hash.length() == 40) {
|
||||||
|
try {
|
||||||
|
String fileHash = Hashing.sha1().hashBytes(Files.toByteArray(file)).toString();
|
||||||
|
if (fileHash.equals(hash)) {
|
||||||
|
recordResourcePack(file, requestId);
|
||||||
|
return repo.func_177319_a(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn("File " + file + " had wrong hash (expected " + hash + ", found " + fileHash + "). Deleting it.");
|
||||||
|
FileUtils.deleteQuietly(file);
|
||||||
|
} catch (IOException ioexception) {
|
||||||
|
logger.warn("File " + file + " couldn\'t be hashed. Deleting it.", ioexception);
|
||||||
|
FileUtils.deleteQuietly(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final GuiScreenWorking guiScreen = new GuiScreenWorking();
|
||||||
|
final Minecraft mc = Minecraft.getMinecraft();
|
||||||
|
|
||||||
|
Futures.getUnchecked(mc.addScheduledTask(() -> mc.displayGuiScreen(guiScreen)));
|
||||||
|
|
||||||
|
Map sessionInfo = Minecraft.getSessionInfo();
|
||||||
|
repo.field_177322_i = HttpUtil.func_180192_a(file, url, sessionInfo, 50 * 1024 * 1024, guiScreen, mc.getProxy());
|
||||||
|
Futures.addCallback(repo.field_177322_i, new FutureCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object value) {
|
||||||
|
recordResourcePack(file, requestId);
|
||||||
|
repo.func_177319_a(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@Nonnull Throwable throwable) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return repo.field_177322_i;
|
||||||
|
} finally {
|
||||||
|
repo.field_177321_h.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package eu.crushedpixel.replaymod.utils;
|
package eu.crushedpixel.replaymod.utils;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.replaymod.recording.packet.PacketSerializer;
|
|
||||||
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
import eu.crushedpixel.replaymod.assets.CustomObjectRepository;
|
import eu.crushedpixel.replaymod.assets.CustomObjectRepository;
|
||||||
import eu.crushedpixel.replaymod.holders.PacketData;
|
import eu.crushedpixel.replaymod.holders.PacketData;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
@@ -27,7 +26,6 @@ import java.util.zip.ZipOutputStream;
|
|||||||
@SuppressWarnings("resource") //Gets handled by finalizer
|
@SuppressWarnings("resource") //Gets handled by finalizer
|
||||||
public class ReplayFileIO {
|
public class ReplayFileIO {
|
||||||
|
|
||||||
private static final PacketSerializer packetSerializer = new PacketSerializer(EnumPacketDirection.CLIENTBOUND);
|
|
||||||
private static final byte[] uniqueBytes = new byte[]{0, 1, 1, 2, 3, 5, 8};
|
private static final byte[] uniqueBytes = new byte[]{0, 1, 1, 2, 3, 5, 8};
|
||||||
|
|
||||||
public static File getRenderFolder() throws IOException {
|
public static File getRenderFolder() throws IOException {
|
||||||
@@ -98,18 +96,6 @@ public class ReplayFileIO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] serializePacket(Packet packet) {
|
|
||||||
ByteBuf bb = Unpooled.buffer();
|
|
||||||
packetSerializer.encode(EnumConnectionState.PLAY, packet, bb);
|
|
||||||
bb.readerIndex(0);
|
|
||||||
byte[] array = new byte[bb.readableBytes()];
|
|
||||||
bb.readBytes(array);
|
|
||||||
|
|
||||||
bb.readerIndex(0);
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Gson gson = new Gson();
|
private static final Gson gson = new Gson();
|
||||||
|
|
||||||
private static void write(Object obj, File file) throws IOException {
|
private static void write(Object obj, File file) throws IOException {
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ replaymod.gui.settings=Settings
|
|||||||
|
|
||||||
replaymod.gui.keyframerepo.delete=Are you sure you want to delete these keyframes?
|
replaymod.gui.keyframerepo.delete=Are you sure you want to delete these keyframes?
|
||||||
|
|
||||||
|
replaymod.gui.restorereplay1=It seems like Minecraft has not quit normally.
|
||||||
|
replaymod.gui.restorereplay2=The Replay "%1$s" was not saved correctly.
|
||||||
|
replaymod.gui.restorereplay3=Do you wish to recover it?
|
||||||
|
|
||||||
#Only change these if it's neccessary
|
#Only change these if it's neccessary
|
||||||
replaymod.gui.replayviewer=Replay Viewer
|
replaymod.gui.replayviewer=Replay Viewer
|
||||||
replaymod.gui.replaycenter=Replay Center
|
replaymod.gui.replaycenter=Replay Center
|
||||||
|
|||||||
Reference in New Issue
Block a user