diff --git a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java index e749e4df..790ab121 100755 --- a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java +++ b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java @@ -2,12 +2,15 @@ package eu.crushedpixel.replaymod; import com.google.common.util.concurrent.ListenableFutureTask; import eu.crushedpixel.replaymod.api.ApiClient; +import eu.crushedpixel.replaymod.api.replay.holders.FileInfo; import eu.crushedpixel.replaymod.chat.ChatMessageHandler; import eu.crushedpixel.replaymod.events.handlers.*; +import eu.crushedpixel.replaymod.gui.online.GuiReplayDownloading; import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay; import eu.crushedpixel.replaymod.holders.KeyframeSet; import eu.crushedpixel.replaymod.localization.LocalizedResourcePack; import eu.crushedpixel.replaymod.online.authentication.AuthenticationHandler; +import eu.crushedpixel.replaymod.online.urischeme.UriScheme; import eu.crushedpixel.replaymod.recording.ConnectionEventHandler; import eu.crushedpixel.replaymod.registry.*; import eu.crushedpixel.replaymod.renderer.*; @@ -38,9 +41,13 @@ import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import org.apache.commons.io.IOUtils; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.net.ServerSocket; +import java.net.Socket; import java.util.List; import java.util.Map; import java.util.Queue; @@ -87,6 +94,17 @@ public class ReplayMod { @EventHandler public void preInit(FMLPreInitializationEvent event) { + try { + UriScheme uriScheme = UriScheme.create(); + if (uriScheme == null) { + throw new UnsupportedOperationException("OS not supported."); + } + uriScheme.install(); + } catch (Exception e) { + System.err.println("Failed to install UriScheme handler:"); + e.printStackTrace(); + } + config = new Configuration(event.getSuggestedConfigurationFile()); config.load(); AuthenticationHandler.loadAuthkeyFromConfig(); @@ -320,6 +338,67 @@ public class ReplayMod { testIfMoeshAndExitMinecraft(); } + + new Thread(new Runnable() { + @Override + public void run() { + ServerSocket serverSocket = null; + try { + serverSocket = new ServerSocket(UriScheme.PROCESS_PORT); + while (!Thread.interrupted()) { + Socket clientSocket = serverSocket.accept(); + try { + InputStream inputStream = clientSocket.getInputStream(); + String replayId = IOUtils.toString(inputStream); + final int id = Integer.parseInt(replayId); + mc.addScheduledTask(new Runnable() { + @Override + public void run() { + loadOnlineReplay(id); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(clientSocket); + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(serverSocket); + } + } + }, "UriSchemeHandler").start(); + + String replayId = System.getenv("replaymod.uri.replayid"); + if (replayId != null) { + final int id = Integer.parseInt(replayId); + @SuppressWarnings("unchecked") + Queue tasks = mc.scheduledTasks; + synchronized (mc.scheduledTasks) { + tasks.add(ListenableFutureTask.create(new Runnable() { + @Override + public void run() { + loadOnlineReplay(id); + } + }, null)); + } + } + } + + private void loadOnlineReplay(int id) { + File file = ReplayMod.downloadedFileHandler.getFileForID(id); + if (file == null) { + FileInfo info = new FileInfo(id, null, null, null, 0, 0, 0, String.valueOf(id), false, 0); + mc.displayGuiScreen(new GuiReplayDownloading(info)); + } else { + try { + ReplayHandler.startReplay(file); + } catch (Exception e) { + e.printStackTrace(); + } + } } private void testIfMoeshAndExitMinecraft() { diff --git a/src/main/java/eu/crushedpixel/replaymod/online/urischeme/LinuxUriScheme.java b/src/main/java/eu/crushedpixel/replaymod/online/urischeme/LinuxUriScheme.java new file mode 100644 index 00000000..b9597534 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/online/urischeme/LinuxUriScheme.java @@ -0,0 +1,64 @@ +package eu.crushedpixel.replaymod.online.urischeme; + +import eu.crushedpixel.replaymod.ReplayMod; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.StringBuilderWriter; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; + +public class LinuxUriScheme extends UriScheme { + + @Override + public void install() throws URISyntaxException, IOException { + File file = new File("replaymod.desktop"); + File iconFile = new File("replaymod-icon.jpg"); + String path = ReplayMod.getContainer().getSource().getAbsolutePath().replace("\\", "\\\\").replace("\"", "\\\""); + String content = + "[Desktop Entry]\n" + + "Name=ReplayMod\n" + + "Exec=java -cp \"" + path + "\" " + UriScheme.class.getName() + " %u\n" + + "Icon=" + iconFile.getAbsolutePath().replace("\\", "\\\\").replace("\"", "\\\"") + "\n" + + "Type=Application\n" + + "Terminal=false\n" + + "MimeType=x-scheme-handler/replaymod;"; + + FileOutputStream out = new FileOutputStream(file); + try { + IOUtils.write(content, out); + } finally { + out.close(); + } + + InputStream in = LinuxUriScheme.class.getResourceAsStream("/assets/replaymod/logo.jpg"); + try { + out = new FileOutputStream(iconFile); + try { + IOUtils.copy(in, out); + } finally { + out.close(); + } + } finally { + in.close(); + } + + String[] command = {"xdg-desktop-menu", "install", "--novendor", "replaymod.desktop"}; + Process process = new ProcessBuilder().command(command).start(); + try { + if (process.waitFor() != 0) { + StringBuilderWriter writer = new StringBuilderWriter(); + IOUtils.copy(process.getInputStream(), writer); + IOUtils.copy(process.getErrorStream(), writer); + throw new IOException(writer.toString()); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + FileUtils.deleteQuietly(file); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/online/urischeme/OSXUriScheme.java b/src/main/java/eu/crushedpixel/replaymod/online/urischeme/OSXUriScheme.java new file mode 100644 index 00000000..1ea19c2b --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/online/urischeme/OSXUriScheme.java @@ -0,0 +1,8 @@ +package eu.crushedpixel.replaymod.online.urischeme; + +public class OSXUriScheme extends UriScheme { + @Override + public void install() throws Exception { + throw new UnsupportedOperationException("OSX URI scheme not yet implemented."); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/online/urischeme/UriScheme.java b/src/main/java/eu/crushedpixel/replaymod/online/urischeme/UriScheme.java new file mode 100644 index 00000000..544ac359 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/online/urischeme/UriScheme.java @@ -0,0 +1,85 @@ +package eu.crushedpixel.replaymod.online.urischeme; + +import net.minecraft.util.Util; + +import javax.swing.*; +import java.io.*; +import java.net.InetAddress; +import java.net.Socket; +import java.util.Arrays; + +public abstract class UriScheme { + public static final String PROTOCOL = "replaymod://"; + public static final int PROCESS_PORT = 11754; + + public static void main(String[] args) { + try { + System.out.println("Args: " + Arrays.toString(args)); + + if (args.length == 1 && args[0].startsWith(PROTOCOL)) { + int id = Integer.parseInt(args[0].substring(PROTOCOL.length())); + try { + Socket socket = new Socket(InetAddress.getLocalHost(), PROCESS_PORT); + socket.getOutputStream().write(String.valueOf(id).getBytes()); + socket.close(); + } catch (Exception e) { + e.printStackTrace(); + + launchNewInstance(id); + } + } else { + launchNewInstance(null); + } + } catch (Throwable t) { + t.printStackTrace(); + JOptionPane.showMessageDialog(null, "Failed to start Minecraft launcher. Saving exception to replaymod-crash.txt"); + try { + FileOutputStream out = new FileOutputStream("replaymod-crash.txt"); + t.printStackTrace(new PrintStream(out)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } + + private static void launchNewInstance(Integer replayId) throws IOException { + // Determine launcher.jar location + String osName = System.getProperty("os.name").toLowerCase(); + String userHome = System.getProperty("user.home", "."); + File dir; + if (osName.contains("win")) { + String appData = System.getenv("APPDATA"); + dir = new File(appData != null ? appData : userHome, ".minecraft/"); + } else if (osName.contains("mac")) { + dir = new File(userHome, "Library/Application Support/minecraft"); + } else if (osName.contains("linux") || osName.contains("unix")) { + dir = new File(userHome, ".minecraft/"); + } else { + dir = new File(userHome, "minecraft/"); + } + + // Launch process + ProcessBuilder processBuilder = new ProcessBuilder("java", "-jar", "launcher.jar").directory(dir); + if (replayId != null) { + processBuilder.environment().put("replaymod.uri.replayid", String.valueOf(replayId)); + } + processBuilder.start(); + } + + public static UriScheme create() { + switch (Util.getOSType()) { + case LINUX: + return new LinuxUriScheme(); + case WINDOWS: + return new WindowsUriScheme(); + case OSX: + return new OSXUriScheme(); + case SOLARIS: + case UNKNOWN: + default: + return null; + } + } + + public abstract void install() throws Exception; +} diff --git a/src/main/java/eu/crushedpixel/replaymod/online/urischeme/WindowsUriScheme.java b/src/main/java/eu/crushedpixel/replaymod/online/urischeme/WindowsUriScheme.java new file mode 100644 index 00000000..12fe517a --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/online/urischeme/WindowsUriScheme.java @@ -0,0 +1,31 @@ +package eu.crushedpixel.replaymod.online.urischeme; + +import eu.crushedpixel.replaymod.ReplayMod; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.StringBuilderWriter; + +import java.io.IOException; + +public class WindowsUriScheme extends UriScheme { + @Override + public void install() throws IOException, InterruptedException { + String path = ReplayMod.getContainer().getSource().getAbsolutePath().replace("\\", "\\\\").replace("\"", "\\\""); + regAdd("\\replaymod /f /ve /d \"URL:replaymod Protocol\""); + regAdd("\\replaymod /f /v \"URL Protocol\" /d \"\""); + regAdd("\\replaymod\\shell\\open\\command /f /ve /d \"java -cp \\\"" + path + "\\\" " + UriScheme.class.getName() + " \\\"%1\\\"\""); + } + + private void regAdd(String args) throws IOException, InterruptedException { + Process process = Runtime.getRuntime().exec("REG ADD HKCU\\Software\\Classes" + args); + try { + if (process.waitFor() != 0) { + StringBuilderWriter writer = new StringBuilderWriter(); + IOUtils.copy(process.getInputStream(), writer); + IOUtils.copy(process.getErrorStream(), writer); + throw new IOException(writer.toString()); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +}