Permalink
Browse files

No longer requires aapt or jarsigner to be in the PATH variables

Closes #5.
Jarsigning now occurs in code, no external binary required
  • Loading branch information...
1 parent dde6646 commit b818a2e2001fb590d5610b5cdb8d928626413631 @yifanlu committed Aug 11, 2011
View
@@ -3,6 +3,7 @@
.idea/uiDesigner.xml
data/
resources/libjava-activity-wrapper.so
+resources/aapt*
lib/
out/
output/
@@ -13,6 +13,7 @@
<element id="dir-copy" path="$PROJECT_DIR$/resources" />
</element>
<element id="extracted-dir" path="$PROJECT_DIR$/lib/swing-layout-1.0.4.jar" path-in-jar="/" />
+ <element id="extracted-dir" path="$PROJECT_DIR$/lib/sdklib.jar" path-in-jar="/" />
</root>
</artifact>
</component>
View
@@ -1,19 +1,23 @@
PSXperia Emulator Converter Tool
-Beta Release
+Beta 2 Release
By Yifan Lu (http://yifan.lu/)
========================================
This tool will take a PSX image that you legally own and convert it to be playable on the Xperia Play with the emulator extracted from the packaged game "Crash Bandicoot."
-Requirements
+To build, you need to copy the following to the "lib" directory
+* apktool.jar from http://code.google.com/p/android-apktool/
+* commons-io-2.0.1.jar from http://commons.apache.org/io/download_io.cgi
+* sdklib.jar from Android SDK (under tools/lib)
+* swing-layout-1.0.4.jar from Netbeans (under platform/modules/ext)
-* Android SDK with "aapk" in your system's PATH variable
-* Java SDK with "jarsigner" in your system's PATH variable
-* Apache Commons IO (2.0.1)
-* Swing Layout Extensions (1.0.4)
-* Apktool (http://code.google.com/p/android-apktool/)
+You also need a copy of "aapt" from Android SDK (under platform-tools)
+* OSX version named aapt-osx
+* Windows version named aapt-windows.exe
+* Linux version named aapt-linux
+Put these in the "resources" directory
-To build, just attach these libraries and compile.
+Finally, you need my PSXperia wrapper library (compiled) in the "resources" directory
To run the GUI, use "java -jar PSXperiaTool.jar"
To run the command line tool, use "java -cp PSXperiaTool.jar com.yifanlu.PSXperiaTool.Interface.CommandLine" to see usage directions, which is also listed below for your convenience.
File renamed without changes.
@@ -0,0 +1,138 @@
+/*
+ * PSXperia Converter Tool - Logging
+ * 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;
+
+import com.android.sdklib.internal.build.SignedJarBuilder;
+
+import java.io.*;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+public class ApkBuilder {
+ private static final String ALIAS = "signPSXperia";
+ private static final char[] KEYSTORE_PASSWORD = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
+ private static final char[] ALIAS_PASSWORD = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
+
+ private File mInputDir;
+ private File mOutputApk;
+
+ public ApkBuilder(File inputDir, File outputApk){
+ this.mInputDir = inputDir;
+ this.mOutputApk = outputApk;
+ }
+
+ public void buildApk() throws IOException, InterruptedException, GeneralSecurityException, SignedJarBuilder.IZipEntryFilter.ZipAbortException {
+ String os = System.getProperty("os.name");
+ Logger.verbose("Your OS: %s", os);
+ File aaptTool;
+ if(os.equals("Mac OS X"))
+ aaptTool = new File("./aapt-osx");
+ else if(os.startsWith("Windows"))
+ aaptTool = new File("./aapt-windows.exe");
+ else if(os.equals("Linux"))
+ aaptTool = new File("./aapt-linux");
+ else {
+ Logger.warning("Does not understand OS name '%s', assuming to be Linux", os);
+ aaptTool = new File("./aapt-linux");
+ }
+ InputStream in = PSXperiaTool.class.getResourceAsStream("/resources/" + aaptTool.getName());
+ Logger.verbose("Extracting %s", aaptTool.getPath());
+ writeStreamToFile(in, aaptTool);
+ in.close();
+ aaptTool.setExecutable(true);
+
+ File androidFrameworkJar = new File("./android-framework.jar");
+ Logger.verbose("Extracting %s", androidFrameworkJar.getPath());
+ in = PSXperiaTool.class.getResourceAsStream("/resources/android-framework.jar");
+ writeStreamToFile(in, androidFrameworkJar);
+ in.close();
+
+ File tempApk = new File(mOutputApk.getPath() + ".unsigned");
+
+ String[] cmd = new String[12];
+ cmd[0] = (aaptTool.getPath());
+ cmd[1] = ("package");
+ cmd[2] = ("-f");
+ cmd[3] = ("-F");
+ cmd[4] = (tempApk.getPath());
+ cmd[5] = ("-S");
+ cmd[6] = ((new File(mInputDir, "/res")).getPath());
+ cmd[7] = ("-M");
+ cmd[8] = ((new File(mInputDir, "/assets/AndroidManifest.xml")).getPath());
+ cmd[9] = ("-I");
+ cmd[10] = (androidFrameworkJar.getPath());
+ cmd[11] = (mInputDir.getPath());
+ Logger.debug("Running command: " + Arrays.toString(cmd).replaceAll("\\,", ""));
+ runCmdWithOutput(cmd);
+
+ Logger.info("Signing apk %s to %s", tempApk.getPath(), mOutputApk.getPath());
+ signApk(tempApk);
+
+ Logger.verbose("Cleaning up signing stuff.");
+ tempApk.delete();
+ androidFrameworkJar.delete();
+ aaptTool.delete();
+ }
+
+ 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[1024];
+ int n;
+ while((n = in.read(buffer)) != -1){
+ out.write(buffer, 0, n);
+ }
+ out.close();
+ }
+
+ public static void runCmdWithOutput(String[] cmd) throws IOException, InterruptedException {
+ Process ps = Runtime.getRuntime().exec(cmd);
+ BufferedReader in = new BufferedReader(new InputStreamReader(ps.getErrorStream()));
+ String line;
+ while ((line = in.readLine()) != null) {
+ Logger.debug(line);
+ }
+ in.close();
+ if (ps.waitFor() != 0) {
+ throw new IOException("Executable did not return without error.");
+ }
+ }
+
+ private void signApk(File unsignedApk) throws IOException, GeneralSecurityException, SignedJarBuilder.IZipEntryFilter.ZipAbortException {
+ FileInputStream in = new FileInputStream(unsignedApk);
+ FileOutputStream out = new FileOutputStream(mOutputApk);
+ KeyStore ks = getKeyStore();
+ PrivateKey key = (PrivateKey)ks.getKey(ALIAS, ALIAS_PASSWORD);
+ X509Certificate cert = (X509Certificate)ks.getCertificate(ALIAS);
+ SignedJarBuilder builder = new SignedJarBuilder(out, key, cert);
+ builder.writeZip(in, null);
+ builder.close();
+ out.close();
+ in.close();
+ }
+
+ private KeyStore getKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ InputStream is = PSXperiaTool.class.getResourceAsStream("/resources/signApk.keystore");
+ ks.load(is, KEYSTORE_PASSWORD);
+ return ks;
+ }
+}
@@ -18,6 +18,7 @@
package com.yifanlu.PSXperiaTool.Interface;
+import com.android.sdklib.internal.build.SignedJarBuilder;
import com.yifanlu.PSXperiaTool.Extractor.CrashBandicootExtractor;
import com.yifanlu.PSXperiaTool.Logger;
import com.yifanlu.PSXperiaTool.PSImageExtract;
@@ -27,6 +28,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Properties;
import java.util.Stack;
@@ -69,6 +71,12 @@ public static void main(String[] args) {
} catch (DataFormatException ex) {
Logger.error("Data format error, Java says: %s", ex.toString());
ex.printStackTrace();
+ } catch (GeneralSecurityException ex) {
+ Logger.error("Error signing JAR, Java says: %s", ex.toString());
+ ex.printStackTrace();
+ } catch (SignedJarBuilder.IZipEntryFilter.ZipAbortException ex) {
+ Logger.error("Error signing JAR, Java says: %s", ex.toString());
+ ex.printStackTrace();
}
}
@@ -96,7 +104,7 @@ public static void doExtractData(String[] args) throws InvalidArgumentException,
System.exit(0);
}
- public static void doConvertImage(String[] args) throws InvalidArgumentException, IOException, InterruptedException {
+ public static void doConvertImage(String[] args) throws InvalidArgumentException, IOException, InterruptedException, GeneralSecurityException, SignedJarBuilder.IZipEntryFilter.ZipAbortException {
if (args.length < 3)
throw new InvalidArgumentException("Not enough input.");
@@ -18,6 +18,7 @@
package com.yifanlu.PSXperiaTool.Interface;
+import com.android.sdklib.internal.build.SignedJarBuilder;
import com.yifanlu.PSXperiaTool.Extractor.CrashBandicootExtractor;
import com.yifanlu.PSXperiaTool.Logger;
import com.yifanlu.PSXperiaTool.PSXperiaTool;
@@ -29,6 +30,7 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.util.Properties;
public class GUI extends javax.swing.JFrame {
@@ -700,6 +702,12 @@ public void stepsTook(int current, int max) {
} catch (IOException ex) {
Logger.error("Cannot build APK!");
ex.printStackTrace();
+ } catch (GeneralSecurityException ex) {
+ Logger.error("Error signing JAR, Java says: %s", ex.toString());
+ ex.printStackTrace();
+ } catch (SignedJarBuilder.IZipEntryFilter.ZipAbortException ex) {
+ Logger.error("Error signing JAR, Java says: %s", ex.toString());
+ ex.printStackTrace();
} finally {
convertProgress.setValue(0);
convertButton.setEnabled(true);
@@ -18,9 +18,11 @@
package com.yifanlu.PSXperiaTool;
+import com.android.sdklib.internal.build.SignedJarBuilder;
import org.apache.commons.io.FileUtils;
import java.io.*;
+import java.security.GeneralSecurityException;
import java.util.*;
public class PSXperiaTool extends ProgressMonitor {
@@ -48,7 +50,7 @@ public PSXperiaTool(Properties properties, File inputFile, File dataDir, File ou
setTotalSteps(TOTAL_STEPS);
}
- public void startBuild() throws IOException, InterruptedException {
+ public void startBuild() throws IOException, InterruptedException, GeneralSecurityException, SignedJarBuilder.IZipEntryFilter.ZipAbortException {
Logger.info("Starting build.");
checkData(mDataDir);
mTempDir = createTempDir(mDataDir);
@@ -186,23 +188,7 @@ private void generateDefaultZpak() throws IOException {
Logger.debug("Done generating default ZPAK at %s", zpakFile.getPath());
}
- // Unused, resources will be compiled at the end
- /*
- private void buildResources(BuildResources br) throws IOException, AndrolibException {
- br.compileResources();
- // Delete old files
- FileUtils.deleteDirectory(new File(mTempDir, "/res"));
- FileUtils.deleteQuietly(new File(mTempDir, "/AndroidManifest.xml"));
- // Add new files
- FileUtils.moveDirectoryToDirectory(new File(mTempDir, "/build/apk/res"), mTempDir, false);
- FileUtils.moveFileToDirectory(new File(mTempDir, "/build/apk/AndroidManifest.xml"), mTempDir, false);
- FileUtils.moveFileToDirectory(new File(mTempDir, "/build/apk/resources.arsc"), mTempDir, false);
- // Delete temp dir
- FileUtils.deleteDirectory(new File(mTempDir, "/build"));
- }
- */
-
- private void generateOutput() throws IOException, InterruptedException {
+ private void generateOutput() throws IOException, InterruptedException, GeneralSecurityException, SignedJarBuilder.IZipEntryFilter.ZipAbortException {
nextStep("Done processing, generating output.");
String titleId = mProperties.getProperty("KEY_TITLE_ID");
if (!mOutputDir.exists())
@@ -214,83 +200,16 @@ private void generateOutput() throws IOException, InterruptedException {
FileUtils.cleanDirectory(outDataDir);
FileUtils.moveFileToDirectory(new File(mTempDir, "/" + titleId + ".zpak"), outDataDir, false);
FileUtils.moveFileToDirectory(new File(mTempDir, "/image_ps_toc.bin"), outDataDir, false);
- //AndrolibResources ares = new AndrolibResources();
File outApk = new File(mOutputDir, "/com.sony.playstation." + titleId + ".apk");
- //ares.aaptPackage(outApk, new File(mTempDir, "/AndroidManifest.xml"), new File(mTempDir, "/res"), mTempDir, null, null, false, false);
-
- // TEMPORARY! Will use cleaner method one day
-
- File currentDir = new File(".");
- File androidFrameworkJar = new File(currentDir, "android-framework.jar");
- File keystoreFile = new File(currentDir, "signApk");
- InputStream in1 = PSXperiaTool.class.getResourceAsStream("/resources/android-framework.jar");
- InputStream in2 = PSXperiaTool.class.getResourceAsStream("/resources/signApk");
- writeStreamToFile(in1, androidFrameworkJar);
- writeStreamToFile(in2, keystoreFile);
- String[] cmd = new String[12];
- cmd[0] = ("aapt");
- cmd[1] = ("package");
- cmd[2] = ("-f");
- cmd[3] = ("-F");
- cmd[4] = (outApk.getPath());
- cmd[5] = ("-S");
- cmd[6] = ((new File(mTempDir, "/res")).getPath());
- cmd[7] = ("-M");
- cmd[8] = ((new File(mTempDir, "/assets/AndroidManifest.xml")).getPath());
- cmd[9] = ("-I");
- cmd[10] = (androidFrameworkJar.getPath());
- cmd[11] = (mTempDir.getPath());
- Logger.debug("Running command: " + Arrays.toString(cmd).replaceAll("\\,", ""));
- runCmdWithOutput(cmd);
- cmd = new String[11];
- cmd[0] = ("jarsigner");
- cmd[1] = ("-keystore");
- cmd[2] = (keystoreFile.getPath());
- cmd[3] = ("-storepass");
- cmd[4] = ("password");
- cmd[5] = ("-keypass");
- cmd[6] = ("password");
- cmd[7] = ("-signedjar");
- cmd[8] = (outApk.getPath() + ".signed");
- cmd[9] = (outApk.getPath());
- cmd[10] = ("signPSXperia");
- Logger.debug("Running command: " + Arrays.toString(cmd).replaceAll("\\,", ""));
- runCmdWithOutput(cmd);
-
- String apkName = outApk.getPath();
- outApk.delete();
- androidFrameworkJar.delete();
- keystoreFile.delete();
- FileUtils.moveFile(new File(apkName + ".signed"), new File(apkName));
+ ApkBuilder build = new ApkBuilder(mTempDir, outApk);
+ build.buildApk();
Logger.info("Done.");
Logger.info("APK file: %s", outApk.getPath());
Logger.info("Data dir: %s", outDataDir.getPath());
}
- public static void runCmdWithOutput(String[] cmd) throws IOException, InterruptedException {
- Process ps = Runtime.getRuntime().exec(cmd);
- BufferedReader in = new BufferedReader(new InputStreamReader(ps.getErrorStream()));
- String line;
- while ((line = in.readLine()) != null) {
- Logger.debug(line);
- }
- in.close();
- if (ps.waitFor() != 0) {
- throw new IOException("Executable did not return without error.");
- }
- }
- 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[1024];
- int n;
- while((n = in.read(buffer)) != -1){
- out.write(buffer, 0, n);
- }
- out.close();
- }
}

0 comments on commit b818a2e

Please sign in to comment.