package com.jpexs.decompiler.flash.exporters.swf; import com.jpexs.decompiler.flash.ReadOnlyTagList; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.abc.ABC; import com.jpexs.decompiler.flash.abc.ClassPath; import com.jpexs.decompiler.flash.abc.ScriptPack; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.script.Dependency; import com.jpexs.decompiler.flash.exporters.script.DependencyType; import com.jpexs.decompiler.flash.importers.SwfXmlImporter; import com.jpexs.decompiler.flash.tags.ABCContainerTag; import com.jpexs.decompiler.flash.tags.DoABC2Tag; import com.jpexs.decompiler.flash.tags.SymbolClassTag; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.Helper; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class SwfToSwcExporter { private static final String DEPENDENCY_NAMESPACE = "n"; private static final String DEPENDENCY_INHERITANCE = "i"; private static final String DEPENDENCY_EXPRESSION = "e"; private static final String DEPENDENCY_SIGNATURE = "s"; private static String sha256(InputStream is) { String output; int read; byte[] buffer = new byte[8192]; try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); while ((read = is.read(buffer)) > 0) { digest.update(buffer, 0, read); } byte[] hash = digest.digest(); BigInteger bigInt = new BigInteger(1, hash); output = bigInt.toString(16); while (output.length() < 32) { output = "0" + output; } } catch (Exception e) { return null; } return output; } public SwfToSwcExporter() { } private String dottedChainToId(DottedChain dc) { if (dc.getWithoutLast().isEmpty()) { return dc.getLast(); } return dc.getWithoutLast().toRawString() + ":" + dc.getLast(); } private String generateCatalog(SWF swf, byte[] swfBytes, boolean skipDependencies) { StringBuilder sb = new StringBuilder(); final String libraryFileName = "library.swf"; final String SWC_VERSION = "1.2"; final String FLEX_VERSION = "4.6.0"; final String FLEX_BUILD = "23201"; final String MINIMUM_SUPPORTED_VERSION = "3.0.0"; sb.append("<?xml version=\"1.0\" encoding =\"utf-8\"?>\n") .append("<swc xmlns=\"http://www.adobe.com/flash/swccatalog/9\">\n") .append(" <versions>\n") .append(" <swc version=\"").append(SWC_VERSION).append("\" />\n") .append(" <flex version=\"").append(FLEX_VERSION).append("\" build=\"").append(FLEX_BUILD).append("\" minimumSupportedVersion=\"").append(MINIMUM_SUPPORTED_VERSION).append("\" />\n") .append(" </versions>\n") .append(" <features>\n") .append(" <feature-script-deps />\n") .append(" <feature-files />\n") .append(" </features>\n") .append(" <libraries>\n") .append(" <library path=\"").append(libraryFileName).append("\">\n"); final long TIME_NOW = new Date().getTime(); Set<DottedChain> definedObjects = new HashSet<>(); List<ABCContainerTag> abcTagList = swf.getAbcList(); List<ScriptPack> packs = swf.getAS3Packs(); for (ScriptPack pack : packs) { ClassPath cp = pack.getClassPath(); definedObjects.add(cp.packageStr.add(cp.className, "" /*strip*/)); } List<ABC> allAbcList = new ArrayList<>(); for (int i = 0; i < abcTagList.size(); i++) { allAbcList.add(abcTagList.get(i).getABC()); } for (ABCContainerTag abcContainer : abcTagList) { if (!(abcContainer instanceof DoABC2Tag)) { continue; //DoABCTag 1 does not have name } DoABC2Tag abcTag = (DoABC2Tag) abcContainer; String scriptName = abcTag.name; sb.append(" <script name=\"").append(scriptName).append("\" mod=\"").append(TIME_NOW).append("\">\n"); List<ScriptPack> tagPacks = abcTag.getABC().getScriptPacks(null, allAbcList); for (ScriptPack pack : tagPacks) { ClassPath cp = pack.getClassPath(); String defId = dottedChainToId(cp.packageStr.add(cp.className, "" /*strip*/)); sb.append(" <def id=\"").append(defId).append("\" />\n"); Set<DottedChain> allDeps = new HashSet<>(); allDeps.add(new DottedChain(new String[]{"AS3"}, "")); sb.append(" <dep id=\"AS3\" type=\"").append(DEPENDENCY_NAMESPACE).append("\" />\n"); if (!skipDependencies) { List<Dependency> dependencies = new ArrayList<>(); List<String> uses = new ArrayList<>(); pack.abc.script_info.get(pack.scriptIndex).traits.getDependencies(null, pack.abc, dependencies, uses, new DottedChain(new String[]{"NO:PACKAGE"}, ""), new ArrayList<>()); for (Dependency d : dependencies) { if ("*".equals(d.getId().getLast())) { continue; } //some toplevel "imports" can be only method calls if (d.getId().getWithoutLast().isEmpty() && !definedObjects.contains(d.getId())) { continue; } if (allDeps.contains(d.getId())) { continue; } sb.append(" <dep id=\"").append(dottedChainToId(d.getId())).append("\" type=\"").append(getDependencyStr(d.getType())).append("\" />\n"); allDeps.add(d.getId()); } } } sb.append(" </script>\n"); } String sha256Hash = sha256(new ByteArrayInputStream(swfBytes)); sb.append(" <digests>\n") .append(" <digest type=\"SHA-256\" signed=\"false\" value=\"").append(sha256Hash).append("\" />\n") .append(" </digests>\n") .append(" </library>\n") .append(" </libraries>\n") .append(" <files>\n") .append(" </files>\n") .append("</swc>\n"); return sb.toString(); } private static String getDependencyStr(DependencyType depType) { switch (depType) { case EXPRESSION: return DEPENDENCY_EXPRESSION; case INHERITANCE: return DEPENDENCY_INHERITANCE; case SIGNATURE: return DEPENDENCY_SIGNATURE; case NAMESPACE: return DEPENDENCY_NAMESPACE; default: return null; } } private SWF recompileSWF(SWF swf) throws IOException, InterruptedException { ByteArrayOutputStream swfOrigBaos = new ByteArrayOutputStream(); swf.saveTo(swfOrigBaos); return new SWF(new ByteArrayInputStream(swfOrigBaos.toByteArray()), Configuration.parallelSpeedUp.get(), false); } private static void printDelay(String title, long t1, long t2) { //System.out.println("time " + title + ": " + (t2 - t1)); } public void exportSwf(SWF swf, File outSwcFile, boolean skipDependencies) throws IOException { long t4 = 0; //Make local copy of SWF so we do not modify original try { long t1 = System.currentTimeMillis(); swf = recompileSWF(swf); long t2 = System.currentTimeMillis(); printDelay("swc.recompile", t1, t2); final String HASH = "myhash"; String abcTagXml = new String(Helper.readStream(SwfToSwcExporter.class.getResourceAsStream("/com/jpexs/decompiler/flash/exporters/swf/swc_main_abctag.xml")), "UTF-8"); abcTagXml = abcTagXml.replace("%hash%", HASH); SwfXmlImporter xmlImporter = new SwfXmlImporter(); DoABC2Tag mainAbcTag = (DoABC2Tag) xmlImporter.importObject(abcTagXml, DoABC2Tag.class, swf); ReadOnlyTagList list = swf.getTags(); final String documentClassStub = "_%hash%_flash_display_Sprite"; String documentClass = documentClassStub.replace("%hash%", HASH); boolean documentClassSet = false; for (int i = 0; i < list.size(); i++) { if (list.get(i) instanceof ABCContainerTag) { swf.addTag(i, mainAbcTag); break; } } for (int i = 0; i < list.size(); i++) { if (list.get(i) instanceof SymbolClassTag) { SymbolClassTag sct = (SymbolClassTag) list.get(i); for (int j = 0; j < sct.tags.size(); j++) { if (sct.tags.get(j) == 0) { sct.names.set(j, documentClass); documentClassSet = true; break; } } } } long t3 = System.currentTimeMillis(); printDelay("swc.documentTag", t2, t3); if (!documentClassSet) { throw new IOException("Original document class not found!"); } swf = recompileSWF(swf); t4 = System.currentTimeMillis(); printDelay("swc.recompile2", t3, t4); } catch (Exception ex) { ex.printStackTrace(); System.exit(0); throw new RuntimeException(ex); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); swf.saveTo(baos); byte[] swfBytes = baos.toByteArray(); long t5 = System.currentTimeMillis(); printDelay("swc.getBytes", t4, t5); String catalogStr = generateCatalog(swf, swfBytes, skipDependencies); long t6 = System.currentTimeMillis(); printDelay("swc.generateCatalog", t5, t6); File tempFile = new File((outSwcFile.getAbsolutePath()) + ".tmp"); FileOutputStream fos = null; ZipOutputStream zos = null; try { fos = new FileOutputStream(tempFile); zos = new ZipOutputStream(fos); ZipEntry libraryEntry = new ZipEntry("library.swf"); zos.putNextEntry(libraryEntry); zos.write(swfBytes); zos.closeEntry(); ZipEntry catalogEntry = new ZipEntry("catalog.xml"); zos.putNextEntry(catalogEntry); zos.write(catalogStr.getBytes("UTF-8")); zos.closeEntry(); zos.close(); zos = null; fos.close(); fos = null; outSwcFile.delete(); tempFile.renameTo(outSwcFile); } finally { if (zos != null) { try { zos.close(); } catch (IOException ex) { //ignore } } if (fos != null) { try { fos.close(); } catch (IOException ex) { //ignore } } if (tempFile.exists()) { tempFile.delete(); } } long t7 = System.currentTimeMillis(); printDelay("swc.zip", t6, t7); } }