Java tutorial
/* * PSXperia Converter Tool - Extractor * Copyright (C) 2011 Yifan Lu (http://yifan.lu/) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.yifanlu.PSXperiaTool.Extractor; import brut.androlib.AndrolibException; import brut.androlib.res.AndrolibResources; import brut.androlib.res.data.ResTable; import brut.androlib.res.util.ExtFile; import com.yifanlu.PSXperiaTool.*; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.WildcardFileFilter; import ie.wombat.jbdiff.JBPatch; import java.io.*; import java.net.URISyntaxException; import java.net.URL; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class CrashBandicootExtractor extends ProgressMonitor { private static final int TOTAL_STEPS = 8; private File mApkFile; private File mZpakData; private File mOutputDir; private static final int BLOCK_SIZE = 1024; private static final Map<String, String> STRING_REPLACEMENT_MAP = new TreeMap<String, String>(); public CrashBandicootExtractor(File apk, File zPakData, File outputDir) { this.mApkFile = apk; this.mOutputDir = outputDir; this.mZpakData = zPakData; setTotalSteps(TOTAL_STEPS); } public void extractApk() throws IOException, URISyntaxException { Logger.info("Starting extraction with PSXPeria Extractor version %s", PSXperiaTool.VERSION); verifyFiles(); processConfig(); decodeValues(); FileFilter filterCompiledRes = new FileFilter() { public boolean accept(File file) { if (file.getParent() == null || file.getParentFile().getParent() == null) return true; File parent = file.getParentFile(); return (!parent.getName().equals("res")) && (!parent.getParentFile().getName().equals("res")); } }; nextStep("Extracting APK"); extractZip(mApkFile, mOutputDir, filterCompiledRes); extractZpaks(); cleanUp(); moveResourceFiles(); patchStrings(); patchEmulator(); nextStep("Done."); } private void verifyFiles() throws IOException { nextStep("Verifying files"); if (!mApkFile.exists()) throw new FileNotFoundException("Cannot find APK file: " + mApkFile.getPath()); if (!mZpakData.exists()) throw new FileNotFoundException("Cannot find ZPAK file: " + mZpakData.getPath()); if (!mOutputDir.exists()) mOutputDir.mkdirs(); if (mOutputDir.list().length > 0) Logger.warning( "The output directory is not empty! Whatever is in this folder will be included in all generated APKs."); //FileUtils.cleanDirectory(mOutputDir); } private void processConfig() throws IOException, UnsupportedOperationException { long crc32 = ZpakCreate.getCRC32(mApkFile); String crcString = Long.toHexString(crc32).toUpperCase(); InputStream inConfig = null; if ((inConfig = PSXperiaTool.class .getResourceAsStream("/resources/patches/" + crcString + "/config.xml")) == null) { throw new FileNotFoundException("Cannot find config for this APK (CRC32: " + crcString + ")"); } Properties config = new Properties(); config.loadFromXML(inConfig); inConfig.close(); Logger.info("Identified " + config.getProperty("game_name", "Unknown Game") + " " + config.getProperty("game_region") + " Version " + config.getProperty("game_version", "Unknown") + ", CRC32: " + config.getProperty("game_crc32", "Unknown")); if (config.getProperty("valid", "yes").equals("no")) throw new UnsupportedOperationException("This APK is not supported."); Logger.verbose("Copying config files."); FileUtils.copyInputStreamToFile( PSXperiaTool.class.getResourceAsStream("/resources/patches/" + crcString + "/config.xml"), new File(mOutputDir, "/config/config.xml")); FileUtils.copyInputStreamToFile( PSXperiaTool.class.getResourceAsStream("/resources/patches/" + crcString + "/filelist.txt"), new File(mOutputDir, "/config/filelist.txt")); FileUtils.copyInputStreamToFile( PSXperiaTool.class .getResourceAsStream("/resources/patches/" + crcString + "/stringReplacements.txt"), new File(mOutputDir, "/config/stringReplacements.txt")); String emulatorPatch = config.getProperty("emulator_patch", ""); String gamePatch = config.getProperty("iso_patch", ""); if (!gamePatch.equals("")) { FileUtils.copyInputStreamToFile( PSXperiaTool.class.getResourceAsStream("/resources/patches/" + crcString + "/" + gamePatch), new File(mOutputDir, "/config/game-patch.bin")); } if (!emulatorPatch.equals("")) { FileUtils.copyInputStreamToFile( PSXperiaTool.class.getResourceAsStream("/resources/patches/" + crcString + "/" + emulatorPatch), new File(mOutputDir, "/config/" + emulatorPatch)); } } private void extractZip(File zipFile, File output, FileFilter filter) throws IOException { Logger.info("Extracting ZIP file: %s to: %s", zipFile.getPath(), output.getPath()); if (!output.exists()) output.mkdirs(); ZipInputStream zip = new ZipInputStream(new FileInputStream(zipFile)); ZipEntry entry; while ((entry = zip.getNextEntry()) != null) { File file = new File(output, entry.getName()); if (file.isDirectory()) continue; if (filter != null && !filter.accept(file)) continue; Logger.verbose("Unzipping %s", entry.getName()); FileUtils.touch(file); FileOutputStream out = new FileOutputStream(file.getPath()); int n; byte[] buffer = new byte[BLOCK_SIZE]; while ((n = zip.read(buffer)) != -1) { out.write(buffer, 0, n); } out.close(); zip.closeEntry(); Logger.verbose("Done extracting %s", entry.getName()); } zip.close(); Logger.debug("Done extracting ZIP."); } private void extractZpaks() throws IOException { nextStep("Extracting ZPAKS"); WildcardFileFilter ff = new WildcardFileFilter("*.zpak"); File[] candidates = (new File(mOutputDir, "/assets")).listFiles((FileFilter) ff); if (candidates == null || candidates.length < 1) throw new FileNotFoundException("Cannot find the default ZPAK under /assets"); else if (candidates.length > 1) Logger.warning("Found more than one default ZPAK under /assets. Using the first one."); File defaultZpak = candidates[0]; extractZip(defaultZpak, new File(mOutputDir, "/assets/ZPAK"), null); FileFilter filter = new FileFilter() { public boolean accept(File file) { if (file.getName().equals("image.ps")) return false; if (file.getParentFile() == null) return true; if (file.getParentFile().getName().equals("manual")) return false; return true; } }; extractZip(mZpakData, new File(mOutputDir, "/ZPAK"), filter); defaultZpak.delete(); } private void decodeValues() throws IOException { nextStep("Decoding values"); try { AndrolibResources res = new AndrolibResources(); ExtFile extFile = new ExtFile(mApkFile); ResTable resTable = res.getResTable(extFile); res.decode(resTable, extFile, mOutputDir); } catch (AndrolibException ex) { ex.printStackTrace(); throw new IOException(ex); } } private void cleanUp() throws IOException { nextStep("Removing unneeded files."); (new File(mOutputDir, "/AndroidManifest.xml")).delete(); FileUtils.deleteDirectory(new File(mOutputDir, "/META-INF")); Logger.verbose("Done cleaning up."); } private void moveResourceFiles() throws IOException { nextStep("Adding new files."); InputStream defaultIcon = PSXperiaTool.class.getResourceAsStream("/resources/icon.png"); writeStreamToFile(defaultIcon, new File(mOutputDir, "/assets/ZPAK/assets/default/bitmaps/icon.png")); defaultIcon = PSXperiaTool.class.getResourceAsStream("/resources/icon.png"); writeStreamToFile(defaultIcon, new File(mOutputDir, "/res/drawable/icon.png")); defaultIcon.close(); Logger.verbose("Done adding new files."); } private void writeStreamToFile(InputStream in, File outFile) throws IOException { Logger.verbose("Writing to: %s", outFile.getPath()); FileOutputStream out = new FileOutputStream(outFile); byte[] buffer = new byte[BLOCK_SIZE]; int n; while ((n = in.read(buffer)) != -1) { out.write(buffer, 0, n); } out.close(); } private void fillReplacementMap() throws IOException { Logger.verbose("Filling string replacement map with resource data."); File file = new File(mOutputDir, "/config/stringReplacements.txt"); InputStream in = new FileInputStream(file); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line1, line2; while ((line1 = reader.readLine()) != null && (line2 = reader.readLine()) != null) { if (line1.isEmpty()) continue; Logger.verbose("Replacing %s with %s.", line1, line2); STRING_REPLACEMENT_MAP.put(line1, line2); } reader.close(); in.close(); file.delete(); } private void patchStrings() throws IOException { nextStep("Patching XML strings with tags."); if (STRING_REPLACEMENT_MAP.isEmpty()) { fillReplacementMap(); } StringReplacement strReplace = new StringReplacement(STRING_REPLACEMENT_MAP, mOutputDir); strReplace.execute(PSXperiaTool.FILES_TO_MODIFY); Logger.verbose("String replacement done."); } private void patchEmulator() throws IOException { Logger.info("Verifying the emulator binary."); Properties config = new Properties(); config.loadFromXML(new FileInputStream(new File(mOutputDir, "/config/config.xml"))); String emulatorName = config.getProperty("emulator_name", "libjava-activity.so"); File origEmulator = new File(mOutputDir, "/lib/armeabi/" + emulatorName); String emulatorCRC32 = Long.toHexString(FileUtils.checksumCRC32(origEmulator)); if (!emulatorCRC32.equalsIgnoreCase(config.getProperty("emulator_crc32"))) throw new UnsupportedOperationException( "The emulator checksum is invalid. Cannot patch. CRC32: " + emulatorCRC32); File newEmulator = new File(mOutputDir, "/lib/armeabi/libjava-activity-patched.so"); File emulatorPatch = new File(mOutputDir, "/config/" + config.getProperty("emulator_patch", "")); if (emulatorPatch.equals("")) { Logger.info("No patch needed."); FileUtils.moveFile(origEmulator, newEmulator); } else { Logger.info("Patching emulator."); newEmulator.createNewFile(); JBPatch.bspatch(origEmulator, newEmulator, emulatorPatch); emulatorPatch.delete(); } FileUtils.copyInputStreamToFile( PSXperiaTool.class.getResourceAsStream("/resources/libjava-activity-wrapper.so"), origEmulator); } }