Save resource packs to replay file and use those during replay
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.recording;
|
||||
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.Gson;
|
||||
import eu.crushedpixel.replaymod.gui.GuiReplaySaving;
|
||||
import eu.crushedpixel.replaymod.holders.PacketData;
|
||||
@@ -8,16 +10,20 @@ import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
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.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public abstract class DataListener extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
|
||||
protected File file;
|
||||
protected Long startTime = null;
|
||||
protected String name;
|
||||
@@ -29,6 +35,10 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter {
|
||||
private boolean singleplayer;
|
||||
private Gson gson = new Gson();
|
||||
|
||||
private final File tempResourcePacksFolder = Files.createTempDir();
|
||||
private final Map<Integer, String> requestToHash = new ConcurrentHashMap<Integer, String>();
|
||||
private final Map<String, File> resourcePacks = new HashMap<String, File>();
|
||||
|
||||
public DataListener(File file, String name, String worldName, long startTime, boolean singleplayer) throws FileNotFoundException {
|
||||
this.file = file;
|
||||
this.startTime = startTime;
|
||||
@@ -54,6 +64,22 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter {
|
||||
dataWriter.requestFinish(players);
|
||||
}
|
||||
|
||||
protected void recordResourcePack(File file, int requestId) {
|
||||
try {
|
||||
String hash = Hashing.sha1().hashBytes(Files.toByteArray(file)).toString();
|
||||
requestToHash.put(requestId, hash);
|
||||
synchronized (resourcePacks) {
|
||||
if (!resourcePacks.containsKey(hash)) {
|
||||
File tempFile = new File(tempResourcePacksFolder, hash);
|
||||
FileUtils.copyFile(file, tempFile);
|
||||
resourcePacks.put(hash, tempFile);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to save resource pack.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public class DataWriter {
|
||||
|
||||
private boolean active = true;
|
||||
@@ -138,9 +164,10 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter {
|
||||
File archive = new File(folder, name + ReplayFile.ZIP_FILE_EXTENSION);
|
||||
archive.createNewFile();
|
||||
|
||||
ReplayFileIO.writeReplayFile(archive, file, metaData);
|
||||
ReplayFileIO.writeReplayFile(archive, file, metaData, resourcePacks, requestToHash);
|
||||
|
||||
file.delete();
|
||||
FileUtils.deleteDirectory(tempResourcePacksFolder);
|
||||
|
||||
GuiReplaySaving.replaySaving = false;
|
||||
} catch(Exception e) {
|
||||
|
||||
@@ -1,27 +1,50 @@
|
||||
package eu.crushedpixel.replaymod.recording;
|
||||
|
||||
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 eu.crushedpixel.replaymod.holders.PacketData;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
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.network.NetworkManager;
|
||||
import net.minecraft.network.Packet;
|
||||
import net.minecraft.network.play.client.C19PacketResourcePackStatus;
|
||||
import net.minecraft.network.play.server.S0CPacketSpawnPlayer;
|
||||
import net.minecraft.network.play.server.S0DPacketCollectItem;
|
||||
import net.minecraft.network.play.server.S0FPacketSpawnMob;
|
||||
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 java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PacketListener extends DataListener {
|
||||
|
||||
private static final Minecraft mc = Minecraft.getMinecraft();
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
|
||||
private ChannelHandlerContext context = null;
|
||||
|
||||
private int nextRequestId;
|
||||
|
||||
public PacketListener(File file, String name, String worldName, long startTime, boolean singleplayer) throws FileNotFoundException {
|
||||
super(file, name, worldName, startTime, singleplayer);
|
||||
@@ -34,7 +57,7 @@ public class PacketListener extends DataListener {
|
||||
players.add(uuid.toString());
|
||||
}
|
||||
|
||||
PacketData pd = getPacketData(context, packet);
|
||||
PacketData pd = getPacketData(packet);
|
||||
writeData(pd);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -72,7 +95,14 @@ public class PacketListener extends DataListener {
|
||||
players.add(uuid.toString());
|
||||
}
|
||||
|
||||
PacketData pd = getPacketData(ctx, packet);
|
||||
if (packet instanceof S48PacketResourcePackSend) {
|
||||
S48PacketResourcePackSend p = (S48PacketResourcePackSend) packet;
|
||||
int requestId = handleResourcePack(p);
|
||||
saveOnly(new S48PacketResourcePackSend("replay://" + requestId, ""));
|
||||
return;
|
||||
}
|
||||
|
||||
PacketData pd = getPacketData(packet);
|
||||
writeData(pd);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -89,7 +119,7 @@ public class PacketListener extends DataListener {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private PacketData getPacketData(ChannelHandlerContext ctx, Packet packet) throws IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
|
||||
private PacketData getPacketData(Packet packet) throws IOException {
|
||||
|
||||
if(startTime == null) startTime = System.currentTimeMillis();
|
||||
|
||||
@@ -120,10 +150,148 @@ public class PacketListener extends DataListener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
byte[] array = ReplayFileIO.serializePacket(packet);
|
||||
|
||||
return new PacketData(array, timestamp);
|
||||
}
|
||||
|
||||
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() {
|
||||
public void onSuccess(Object result) {
|
||||
recordResourcePack(levelDir, requestId);
|
||||
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
|
||||
}
|
||||
|
||||
public void onFailure(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() {
|
||||
public void run() {
|
||||
mc.displayGuiScreen(new GuiYesNo(new GuiYesNoCallback() {
|
||||
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;
|
||||
}
|
||||
|
||||
private void downloadResourcePackFuture(int requestId, String url, final String hash) {
|
||||
Futures.addCallback(downloadResourcePack(requestId, url, hash), new FutureCallback() {
|
||||
public void onSuccess(Object result) {
|
||||
mc.getNetHandler().addToSendQueue(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
|
||||
}
|
||||
|
||||
public void onFailure(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(new Runnable() {
|
||||
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() {
|
||||
public void onSuccess(Object value) {
|
||||
recordResourcePack(file, requestId);
|
||||
repo.func_177319_a(file);
|
||||
}
|
||||
|
||||
public void onFailure(Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
});
|
||||
return repo.field_177322_i;
|
||||
} finally {
|
||||
repo.field_177321_h.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.replay;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.util.concurrent.ListenableFutureTask;
|
||||
import eu.crushedpixel.replaymod.ReplayMod;
|
||||
import eu.crushedpixel.replaymod.entities.CameraEntity;
|
||||
@@ -15,7 +16,6 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiDownloadTerrain;
|
||||
import net.minecraft.client.resources.ResourcePackRepository;
|
||||
import net.minecraft.network.EnumConnectionState;
|
||||
import net.minecraft.network.NetworkManager;
|
||||
import net.minecraft.network.Packet;
|
||||
@@ -24,14 +24,12 @@ import net.minecraft.world.EnumDifficulty;
|
||||
import net.minecraft.world.WorldSettings.GameType;
|
||||
import net.minecraft.world.WorldType;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
@@ -137,6 +135,11 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
*/
|
||||
protected boolean allowMovement;
|
||||
|
||||
/**
|
||||
* Directory to which resource packs are extracted.
|
||||
*/
|
||||
private final File tempResourcePackFolder = Files.createTempDir();
|
||||
|
||||
/**
|
||||
* Create a new replay sender.
|
||||
* @param file The replay file
|
||||
@@ -249,7 +252,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
* @param p The packet to process
|
||||
* @return The processed packet or {@code null} if no packet shall be sent
|
||||
*/
|
||||
protected Packet processPacket(Packet p) {
|
||||
protected Packet processPacket(Packet p) throws Exception {
|
||||
if(BAD_PACKETS.contains(p.getClass())) return null;
|
||||
|
||||
if(p instanceof S29PacketSoundEffect && ReplayProcess.isVideoRecording()) {
|
||||
@@ -262,7 +265,21 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
|
||||
if(p instanceof S48PacketResourcePackSend) {
|
||||
S48PacketResourcePackSend packet = (S48PacketResourcePackSend) p;
|
||||
new ResourcePackCheck(packet.func_179783_a(), packet.func_179784_b()).start();
|
||||
String url = packet.func_179783_a();
|
||||
if (url.startsWith("replay://")) {
|
||||
int id = Integer.parseInt(url.substring("replay://".length()));
|
||||
Map<Integer, String> index = replayFile.resourcePackIndex().get();
|
||||
if (index != null) {
|
||||
String hash = index.get(id);
|
||||
if (hash != null) {
|
||||
File file = new File(tempResourcePackFolder, hash + ".zip");
|
||||
if (!file.exists()) {
|
||||
IOUtils.copy(replayFile.resourcePack(hash).get(), new FileOutputStream(file));
|
||||
}
|
||||
mc.getResourcePackRepository().func_177319_a(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -355,6 +372,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
replayFile.close();
|
||||
FileUtils.deleteDirectory(tempResourcePackFolder);
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@@ -386,102 +404,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
MCTimerHandler.setTimerSpeed((float) d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for stored resource packs and loads them or downloads a new one.
|
||||
*/
|
||||
private static class ResourcePackCheck extends Thread {
|
||||
|
||||
private static Minecraft mc = Minecraft.getMinecraft();
|
||||
private static ResourcePackRepository repo = mc.getResourcePackRepository();
|
||||
|
||||
private String url, hash;
|
||||
|
||||
public ResourcePackCheck(String url, String hash) {
|
||||
this.url = url;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location of the stored resource pack.
|
||||
* @param url The original url of the resource pack
|
||||
* @param hash The hash code of the resource pack
|
||||
* @return File location of the resource pack
|
||||
*/
|
||||
private File getServerResourcePackLocation(String url, String hash) {
|
||||
|
||||
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 null;
|
||||
}
|
||||
|
||||
filename = "legacy_" + filename.replaceAll("\\W", "");
|
||||
}
|
||||
|
||||
return new File(repo.dirServerResourcepacks, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a resource pack from a specified URL.
|
||||
* @param url The URL to download from
|
||||
* @param file The target file location
|
||||
* @return {@code true} if the download was successful, {@code false} if an I/O-error occured
|
||||
*/
|
||||
private boolean downloadServerResourcePack(String url, File file) {
|
||||
try {
|
||||
FileUtils.copyURLToFile(new URL(url), file);
|
||||
return true;
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the resource pack to the loaded resource packs if resource packs are enabled in the config.
|
||||
* If there is no local copy of the resource pack, this loads the resource pack from the specified url.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
boolean use = ReplayMod.replaySettings.getUseResourcePacks();
|
||||
if(!use) return;
|
||||
|
||||
System.out.println("Looking for downloaded Resource Pack...");
|
||||
File rp = getServerResourcePackLocation(url, hash);
|
||||
if(rp == null) {
|
||||
System.out.println("Invalid Resource Pack provided");
|
||||
return;
|
||||
}
|
||||
if(rp.exists()) {
|
||||
System.out.println("Resource Pack found!");
|
||||
repo.func_177319_a(rp);
|
||||
|
||||
} else {
|
||||
System.out.println("No Resource Pack found.");
|
||||
System.out.println("Attempting to download Resource Pack...");
|
||||
boolean success = downloadServerResourcePack(url, rp);
|
||||
System.out.println(success ? "Resource pack was successfully downloaded!" : "Resource Pack download failed.");
|
||||
if(success) {
|
||||
repo.func_177319_a(rp);
|
||||
}
|
||||
}
|
||||
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Asynchronous packet processing //
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.studio;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.JsonObject;
|
||||
import de.johni0702.replaystudio.PacketData;
|
||||
import de.johni0702.replaystudio.filter.ChangeTimestampFilter;
|
||||
@@ -9,13 +10,17 @@ import de.johni0702.replaystudio.io.ReplayOutputStream;
|
||||
import de.johni0702.replaystudio.stream.PacketStream;
|
||||
import de.johni0702.replaystudio.studio.ReplayStudio;
|
||||
import eu.crushedpixel.replaymod.recording.ReplayMetaData;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFile;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class StudioImplementation {
|
||||
|
||||
@@ -49,13 +54,25 @@ public class StudioImplementation {
|
||||
|
||||
out.close();
|
||||
|
||||
ReplayMetaData metaData = ReplayFileIO.getMetaData(replayFile);
|
||||
ReplayFile replay = new ReplayFile(replayFile);
|
||||
ReplayMetaData metaData = replay.metadata().get();
|
||||
ending = Math.min(metaData.getDuration(), ending);
|
||||
metaData.setDuration(ending - beginning);
|
||||
|
||||
Map<Integer, String> resourcePackIndex = replay.resourcePackIndex().get();
|
||||
Map<String, File> resourcePacks = new HashMap<String, File>();
|
||||
File tempFolder = Files.createTempDir();
|
||||
for (String hash : resourcePackIndex.values()) {
|
||||
if (!resourcePacks.containsKey(hash)) {
|
||||
File resourcePack = new File(tempFolder, hash);
|
||||
IOUtils.copy(replay.resourcePack(hash).get(), new FileOutputStream(resourcePack));
|
||||
resourcePacks.put(hash, resourcePack);
|
||||
}
|
||||
}
|
||||
|
||||
outputFile.createNewFile();
|
||||
|
||||
ReplayFileIO.writeReplayFile(outputFile, temp, metaData);
|
||||
ReplayFileIO.writeReplayFile(outputFile, temp, metaData, resourcePacks, resourcePackIndex);
|
||||
}
|
||||
|
||||
//TODO Work with Johni to connect multiple Replay Files
|
||||
|
||||
@@ -2,6 +2,8 @@ package eu.crushedpixel.replaymod.utils;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import eu.crushedpixel.replaymod.holders.KeyframeSet;
|
||||
import eu.crushedpixel.replaymod.holders.PlayerVisibility;
|
||||
import eu.crushedpixel.replaymod.recording.ReplayMetaData;
|
||||
@@ -11,6 +13,8 @@ import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReplayFile extends ZipFile {
|
||||
|
||||
@@ -21,6 +25,8 @@ public class ReplayFile extends ZipFile {
|
||||
public static final String ENTRY_METADATA = "metaData" + JSON_FILE_EXTENSION;
|
||||
public static final String ENTRY_PATHS = "paths";
|
||||
public static final String ENTRY_THUMB = "thumb";
|
||||
public static final String ENTRY_RESOURCE_PACK = "resourcepack/%s.zip";
|
||||
public static final String ENTRY_RESOURCE_PACK_INDEX = "resourcepack/index.json";
|
||||
public static final String ENTRY_VISIBILITY = "visibility";
|
||||
|
||||
private final File file;
|
||||
@@ -138,4 +144,52 @@ public class ReplayFile extends ZipFile {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ZipArchiveEntry resourcePackIndexEntry() {
|
||||
return getEntry(ENTRY_RESOURCE_PACK_INDEX);
|
||||
}
|
||||
|
||||
public Supplier<Map<Integer, String>> resourcePackIndex() {
|
||||
return new Supplier<Map<Integer, String>>() {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<Integer, String> get() {
|
||||
try {
|
||||
ZipArchiveEntry entry = resourcePackIndexEntry();
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
Map<Integer, String> index = new HashMap<Integer, String>();
|
||||
JsonObject json = new Gson().fromJson(new InputStreamReader(getInputStream(entry)), JsonObject.class);
|
||||
for (Map.Entry<String, JsonElement> e : json.entrySet()) {
|
||||
index.put(Integer.parseInt(e.getKey()), e.getValue().getAsString());
|
||||
}
|
||||
return index;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ZipArchiveEntry resourcePackEntry(String hash) {
|
||||
return getEntry(String.format(ENTRY_RESOURCE_PACK, hash));
|
||||
}
|
||||
|
||||
public Supplier<InputStream> resourcePack(final String hash) {
|
||||
return new Supplier<InputStream>() {
|
||||
@Override
|
||||
public InputStream get() {
|
||||
try {
|
||||
ZipArchiveEntry entry = resourcePackEntry(hash);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
return getInputStream(entry);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.network.play.server.S01PacketJoinGame;
|
||||
import net.minecraft.network.play.server.S08PacketPlayerPosLook;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
@@ -58,7 +60,8 @@ public class ReplayFileIO {
|
||||
return files;
|
||||
}
|
||||
|
||||
public static void writeReplayFile(File replayFile, File tempFile, ReplayMetaData metaData) throws IOException {
|
||||
public static void writeReplayFile(File replayFile, File tempFile, ReplayMetaData metaData,
|
||||
Map<String, File> resourcePacks, Map<Integer, String> resourcePackRequests) throws IOException {
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
if(!replayFile.exists()) {
|
||||
@@ -86,6 +89,20 @@ public class ReplayFileIO {
|
||||
fis.close();
|
||||
zos.closeEntry();
|
||||
|
||||
if (!resourcePackRequests.isEmpty()) {
|
||||
zos.putNextEntry(new ZipEntry(ReplayFile.ENTRY_RESOURCE_PACK_INDEX));
|
||||
pw = new PrintWriter(zos);
|
||||
pw.write(new Gson().toJson(resourcePackRequests));
|
||||
pw.flush();
|
||||
zos.closeEntry();
|
||||
|
||||
for (Map.Entry<String, File> entry : resourcePacks.entrySet()) {
|
||||
zos.putNextEntry(new ZipEntry(String.format(ReplayFile.ENTRY_RESOURCE_PACK, entry.getKey())));
|
||||
IOUtils.copy(new FileInputStream(entry.getValue()), zos);
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
zos.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ public net.minecraft.client.Minecraft field_71429_W # leftClickCounter
|
||||
public net.minecraft.client.Minecraft field_71445_n # isGamePaused
|
||||
public net.minecraft.client.Minecraft field_152351_aB # scheduledTasks
|
||||
public net.minecraft.client.Minecraft field_110449_ao # defaultResourcePacks
|
||||
public net.minecraft.client.Minecraft field_110448_aq # mcResourcePackRepository
|
||||
|
||||
public net.minecraft.client.Minecraft func_71386_F()L # getSystemTime
|
||||
public net.minecraft.client.Minecraft func_71383_b(I)V # updateDebugProfilerName
|
||||
@@ -36,6 +37,8 @@ public net.minecraft.entity.Entity field_70180_af # dataWatcher
|
||||
|
||||
# ResourcePackRepository
|
||||
public net.minecraft.client.resources.ResourcePackRepository field_148534_e # dirServerResourcepacks
|
||||
public net.minecraft.client.resources.ResourcePackRepository field_177321_h # lock
|
||||
public net.minecraft.client.resources.ResourcePackRepository field_177322_i # httpRequest
|
||||
|
||||
# EntityRenderer
|
||||
public net.minecraft.client.renderer.EntityRenderer field_147711_ac # resourceManager
|
||||
|
||||
Reference in New Issue
Block a user