From ae652f51c23a51ef0d58bf84b035971d4f885b3e Mon Sep 17 00:00:00 2001 From: Chicken-Bomes Date: Mon, 3 Feb 2014 11:02:42 +1000 Subject: [PATCH] Added command line tool frontend. --- MANIFEST.MF | 2 + codechicken/lib/render/QBImporter.java | 37 +++--- codechicken/lib/tool/LibDownloader.java | 110 ++++++++++++++++++ codechicken/lib/tool/MCStripTransformer.java | 44 +++++++ codechicken/lib/tool/Main.java | 14 +++ codechicken/lib/tool/StripClassLoader.java | 50 ++++++++ codechicken/lib/tool/ToolMain.java | 44 +++++++ codechicken/lib/tool/module/JOptModule.java | 48 ++++++++ .../lib/tool/module/ModuleQBConverter.java | 70 +++++++++++ 9 files changed, 396 insertions(+), 23 deletions(-) create mode 100644 MANIFEST.MF create mode 100644 codechicken/lib/tool/LibDownloader.java create mode 100644 codechicken/lib/tool/MCStripTransformer.java create mode 100644 codechicken/lib/tool/Main.java create mode 100644 codechicken/lib/tool/StripClassLoader.java create mode 100644 codechicken/lib/tool/ToolMain.java create mode 100644 codechicken/lib/tool/module/JOptModule.java create mode 100644 codechicken/lib/tool/module/ModuleQBConverter.java diff --git a/MANIFEST.MF b/MANIFEST.MF new file mode 100644 index 0000000..9915f07 --- /dev/null +++ b/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Main-Class: codechicken.lib.tool.Main diff --git a/codechicken/lib/render/QBImporter.java b/codechicken/lib/render/QBImporter.java index 8af4aa3..05be9bb 100644 --- a/codechicken/lib/render/QBImporter.java +++ b/codechicken/lib/render/QBImporter.java @@ -239,7 +239,6 @@ public class QBImporter } } - private static final int[][] vertOrder = new int[][] {//clockwise because MC is left handed {3, 0}, {1, 0}, @@ -577,8 +576,6 @@ public class QBImporter public QBMatrix[] matrices; public boolean rightHanded; - - public RasterisedModel toRasterisedModel(int flags) { List qbImages = new ArrayList(); List> modelQuads = new ArrayList>(); @@ -727,29 +724,23 @@ public class QBImporter } public static QBModel loadQB(ResourceLocation res) { - try - { + try { return loadQB(Minecraft.getMinecraft().getResourceManager().getResource(res).getInputStream()); - } - catch(Exception e) - { + } catch(Exception e) { throw new RuntimeException("failed to load model: "+res, e); } } - /** - * try { - File dump = new File("packed.png"); - if(!dump.exists()) dump.createNewFile(); - ImageIO.write(img, "PNG", dump); - - File dump2 = new File("packed.obj"); - if(!dump2.exists()) dump2.createNewFile(); - PrintWriter p = new PrintWriter(dump2); - CCModel.exportObj(Collections.singletonMap(name, m), p); - p.close(); - } catch(IOException e) { - e.printStackTrace(); - } - */ + public static QBModel loadQB(File file) { + try { + FileInputStream fin = new FileInputStream(file); + try { + return loadQB(fin); + } finally { + fin.close(); + } + } catch(Exception e) { + throw new RuntimeException("failed to load model: "+file.getPath(), e); + } + } } diff --git a/codechicken/lib/tool/LibDownloader.java b/codechicken/lib/tool/LibDownloader.java new file mode 100644 index 0000000..0785b43 --- /dev/null +++ b/codechicken/lib/tool/LibDownloader.java @@ -0,0 +1,110 @@ +package codechicken.lib.tool; + +import java.io.*; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; + +public class LibDownloader +{ + private static String[] libs = new String[] { + "org/ow2/asm/asm-debug-all/4.1/asm-debug-all-4.1.jar", + "com/google/guava/guava/14.0/guava-14.0.jar", + "net/sf/jopt-simple/jopt-simple/4.5/jopt-simple-4.5.jar"}; + private static File libDir = new File("lib"); + + private static ByteBuffer downloadBuffer = ByteBuffer.allocateDirect(1 << 23); + + public static void load() { + if(!libDir.exists()) + libDir.mkdir(); + if(!libDir.isDirectory()) + throw new RuntimeException("/lib is not a directory"); + + List missing = checkExists(); + for(String lib : missing) + download(lib); + addPaths(libs); + } + + private static void addPaths(String[] libs) { + try { + URLClassLoader cl = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Method m_addURL = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class}); + m_addURL.setAccessible(true); + for(String lib : libs) + m_addURL.invoke(cl, new File(libDir, fileName(lib)).toURI().toURL()); + } + catch (Exception e) { + throw new RuntimeException("Failed to add libraries to classpath", e); + } + } + + private static void download(String lib) { + File libFile = new File(libDir, fileName(lib)); + try + { + URL libDownload = new URL("http://repo1.maven.org/maven2/"+lib); + URLConnection connection = libDownload.openConnection(); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.setRequestProperty("User-Agent", "CodeChickenLib Downloader"); + int sizeGuess = connection.getContentLength(); + download(connection.getInputStream(), sizeGuess, libFile); + } + catch (Exception e) + { + libFile.delete(); + throw new RuntimeException("A download error occured", e); + } + } + + private static void download(InputStream is, int sizeGuess, File target) throws Exception + { + String name = target.getName(); + if (sizeGuess > downloadBuffer.capacity()) + throw new Exception(String.format("The file %s is too large to be downloaded", name)); + + downloadBuffer.clear(); + + int bytesRead, fullLength = 0; + + System.out.format("Downloading lib %s", name); + byte[] smallBuffer = new byte[1024]; + while ((bytesRead = is.read(smallBuffer)) >= 0) { + downloadBuffer.put(smallBuffer, 0, bytesRead); + fullLength += bytesRead; + System.out.format("\rDownloading lib %s %d%%", name, (int) (fullLength * 100 / sizeGuess)); + } + System.out.format("\rDownloaded lib %s \n", name); + is.close(); + downloadBuffer.limit(fullLength); + + if(!target.exists()) + target.createNewFile(); + + downloadBuffer.position(0); + FileOutputStream fos = new FileOutputStream(target); + fos.getChannel().write(downloadBuffer); + fos.close(); + } + + private static String fileName(String lib) { + return lib.replaceAll(".+/", ""); + } + + private static List checkExists() { + LinkedList list = new LinkedList(); + for(String lib : libs) { + File file = new File(libDir, fileName(lib)); + if(!file.exists()) + list.add(lib); + } + return list; + } + +} diff --git a/codechicken/lib/tool/MCStripTransformer.java b/codechicken/lib/tool/MCStripTransformer.java new file mode 100644 index 0000000..76a4a70 --- /dev/null +++ b/codechicken/lib/tool/MCStripTransformer.java @@ -0,0 +1,44 @@ +package codechicken.lib.tool; + +import codechicken.lib.asm.ASMHelper; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.commons.RemappingMethodAdapter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.Iterator; + +public class MCStripTransformer { + public static class ReferenceDetector extends Remapper { + boolean found = false; + + @Override + public String map(String typeName) { + if(typeName.startsWith("net/minecraft")) + found = true; + return typeName; + } + } + + public static byte[] transform(byte[] bytes) { + ClassNode cnode = ASMHelper.createClassNode(bytes, ClassReader.EXPAND_FRAMES); + + boolean changed = false; + Iterator it = cnode.methods.iterator(); + while(it.hasNext()) { + MethodNode mnode = it.next(); + ReferenceDetector r = new ReferenceDetector(); + mnode.accept(new RemappingMethodAdapter(mnode.access, mnode.desc, new MethodVisitor(Opcodes.ASM4) {}, r)); + if(r.found) { + it.remove(); + changed = true; + } + } + if(changed) + bytes = ASMHelper.createBytes(cnode, 0); + return bytes; + } +} diff --git a/codechicken/lib/tool/Main.java b/codechicken/lib/tool/Main.java new file mode 100644 index 0000000..083db03 --- /dev/null +++ b/codechicken/lib/tool/Main.java @@ -0,0 +1,14 @@ +package codechicken.lib.tool; + +public class Main +{ + public static void main(String[] args) { + LibDownloader.load(); + try { + Class c_toolMain = new StripClassLoader().loadClass("codechicken.lib.tool.ToolMain"); + c_toolMain.getDeclaredMethod("main", String[].class).invoke(null, new Object[]{args}); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/codechicken/lib/tool/StripClassLoader.java b/codechicken/lib/tool/StripClassLoader.java new file mode 100644 index 0000000..51bf15a --- /dev/null +++ b/codechicken/lib/tool/StripClassLoader.java @@ -0,0 +1,50 @@ +package codechicken.lib.tool; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; + +public class StripClassLoader extends URLClassLoader +{ + public StripClassLoader() { + super(new URL[0], StripClassLoader.class.getClassLoader()); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if(!name.startsWith("codechicken.lib")) + return super.loadClass(name); + + try { + String resName = name.replace('.', '/')+".class"; + InputStream res = getResourceAsStream(resName); + if(res == null) + throw new ClassNotFoundException("Could not find resource: "+resName); + byte[] bytes = readFully(res); + bytes = transform(bytes); + return defineClass(name, bytes, 0, bytes.length); + + } catch(IOException e) { + throw new ClassNotFoundException(name, e); + } + } + + public static byte[] readFully(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int read; + byte[] data = new byte[16384]; + while ((read = is.read(data, 0, data.length)) > 0) + buffer.write(data, 0, read); + + return buffer.toByteArray(); + } + + private byte[] transform(byte[] bytes) { + + bytes = MCStripTransformer.transform(bytes); + return bytes; + } +} diff --git a/codechicken/lib/tool/ToolMain.java b/codechicken/lib/tool/ToolMain.java new file mode 100644 index 0000000..24dee52 --- /dev/null +++ b/codechicken/lib/tool/ToolMain.java @@ -0,0 +1,44 @@ +package codechicken.lib.tool; + +import codechicken.lib.tool.module.ModuleQBConverter; + +public class ToolMain +{ + public static interface Module { + public void main(String[] args); + public String name(); + public void printHelp(); + } + + public static Module[] modules = new Module[]{ + new ModuleQBConverter() + }; + + private static void printHelp() { + System.out.println("Usage: [module] [args]"); + System.out.println(" Modules: "); + for(Module m : modules) + System.out.println(" - "+m.name()); + System.out.println("-h [module] for module help"); + } + + public static void main(String[] args) { + if(args.length > 0) { + for(Module m : modules) + if(args[0].equals(m.name())) { + String[] args2 = new String[args.length-1]; + System.arraycopy(args, 1, args2, 0, args2.length); + m.main(args2); + return; + } + if(args[0].equals("-h") && args.length >= 2) { + for(Module m : modules) + if(args[1].equals(m.name())) { + m.printHelp(); + return; + } + } + } + printHelp(); + } +} diff --git a/codechicken/lib/tool/module/JOptModule.java b/codechicken/lib/tool/module/JOptModule.java new file mode 100644 index 0000000..f67e920 --- /dev/null +++ b/codechicken/lib/tool/module/JOptModule.java @@ -0,0 +1,48 @@ +package codechicken.lib.tool.module; + +import codechicken.lib.tool.ToolMain; +import joptsimple.OptionException; +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +import java.io.IOException; + +public abstract class JOptModule implements ToolMain.Module +{ + OptionParser parser = new OptionParser(); + + @Override + public void main(String[] args) { + OptionSet options; + try + { + options = parser.parse(args); + } + catch(OptionException ex) + { + System.err.println(ex.getLocalizedMessage()); + System.exit(-1); + return; + } + + try + { + main(parser, options); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + protected abstract void main(OptionParser parser, OptionSet options); + + @Override + public void printHelp() { + try { + parser.printHelpOn(System.out); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/codechicken/lib/tool/module/ModuleQBConverter.java b/codechicken/lib/tool/module/ModuleQBConverter.java new file mode 100644 index 0000000..c4ae0c0 --- /dev/null +++ b/codechicken/lib/tool/module/ModuleQBConverter.java @@ -0,0 +1,70 @@ +package codechicken.lib.tool.module; + +import codechicken.lib.render.QBImporter; +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +import java.io.File; + +import static java.util.Arrays.asList; + +public class ModuleQBConverter extends JOptModule +{ + public ModuleQBConverter() { + parser.acceptsAll(asList("?", "h", "help"), "Show the help"); + parser.acceptsAll(asList("i", "input"), "comma separated list of paths to models (.qb or directories)") + .withRequiredArg().ofType(File.class).withValuesSeparatedBy(',').required(); + parser.acceptsAll(asList("o", "out"), "Output Directory") + .withRequiredArg().ofType(File.class); + parser.acceptsAll(asList("o2", "textureplanes"), "2nd level optimisation. Merges coplanar polygons. Increases texture size"); + parser.acceptsAll(asList("s", "squaretextures"), "Produce square textures"); + parser.acceptsAll(asList("t", "mergetextures"), "Use the same texture for all models"); + parser.acceptsAll(asList("r", "scalemc"), "Resize model to mc standard (shrink by factor of 16)"); + } + + protected void main(OptionParser parser, OptionSet options) { + int flags = 0; + if(options.has("o2")) flags |= QBImporter.TEXTUREPLANES; + if(options.has("s")) flags |= QBImporter.SQUARETEXTURE; + if(options.has("t")) flags |= QBImporter.MERGETEXTURES; + if(options.has("r")) flags |= QBImporter.SCALEMC; + + File[] input = options.valuesOf("input").toArray(new File[0]); + File[] outDir = new File[input.length]; + if(options.has("out")) { + File output = (File)options.valueOf("out"); + if(output.isFile()) + throw new RuntimeException("Output Path is not a directory"); + if(!output.exists()) + output.mkdirs(); + + for(int i = 0; i < input.length; i++) + outDir[i] = output; + } else { + for(int i = 0; i < input.length; i++) + outDir[i] = input[i].isDirectory() ? input[i] : input[i].getParentFile(); + } + + for(int i = 0; i < input.length; i++) { + File file = input[i]; + if(file.isDirectory()) { + for(File file2 : file.listFiles()) + if(file2.getName().endsWith(".qb")) + convert(file2, outDir[i], flags); + } + else + convert(file, outDir[i], flags); + } + } + + private void convert(File in, File outDir, int flags) { + System.out.println("Converting: "+in.getName()); + QBImporter.RasterisedModel m = QBImporter.loadQB(in).toRasterisedModel(flags); + m.export(new File(outDir, in.getName().replace(".qb", ".obj")), outDir); + } + + @Override + public String name() { + return "QBConverter"; + } +}