Change to an android app
This commit is contained in:
parent
db61025c7b
commit
c3c854a8dc
|
@ -1,2 +1,18 @@
|
|||
psm_backup.tar
|
||||
shared_prefs/*
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/app/build/*
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
libsuperuser/build/*
|
||||
app/build/*
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
|
@ -0,0 +1 @@
|
|||
nopsmdrm
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<value>
|
||||
<entry key="app">
|
||||
<State />
|
||||
</entry>
|
||||
</value>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/libsuperuser" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,9 @@
|
|||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
9
LICENSE
9
LICENSE
|
@ -1,9 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Li
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
12
README.md
12
README.md
|
@ -1,12 +0,0 @@
|
|||
# NoPsmDrm-Android
|
||||
|
||||
NoPsmDrm patches for PSM APP on XPERIA PLAY.
|
||||
|
||||
- requires JAVA
|
||||
- requires you to have the Xperia ADB drivers, (see https://developer.sony.com/open-source/aosp-on-xperia-open-devices/downloads/drivers)
|
||||
- requires root
|
||||
|
||||
!! if you have PSM already installed create a backup of your PSM Application before using this
|
||||
it *shouldnt* break anything, but ya never know ..
|
||||
|
||||
.. run install.bat ?
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,35 @@
|
|||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.psmreborn.nopsmdrm'
|
||||
compileSdk 10
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.psmreborn.nopsmdrm"
|
||||
minSdk 10
|
||||
//noinspection ExpiredTargetSdkVersion dont care about google play
|
||||
targetSdk 10
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':libsuperuser')
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/app_icon"
|
||||
android:label="@string/app_name"
|
||||
tools:targetApi="13">
|
||||
<activity android:name="com.psmreborn.nopsmdrm.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,34 @@
|
|||
package com.psmreborn.nopsmdrm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class Helper {
|
||||
|
||||
private static Context ctx;
|
||||
public static void setContext(Context context){
|
||||
ctx = context;
|
||||
}
|
||||
public static boolean isPsmInstalled(){
|
||||
try {
|
||||
ctx.getPackageManager().getApplicationInfo("com.playstation.psstore", 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static ApplicationInfo getPsmApp() throws PackageManager.NameNotFoundException {
|
||||
ApplicationInfo pkg = ctx.getPackageManager().getApplicationInfo("com.playstation.psstore", 0);
|
||||
return pkg;
|
||||
}
|
||||
public static String getDateTime(){
|
||||
String formattedDate = new SimpleDateFormat("MM_dd_yyyy_HH_mm_ss").format(new Date());
|
||||
return formattedDate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,341 @@
|
|||
package com.psmreborn.nopsmdrm;
|
||||
|
||||
import static com.psmreborn.nopsmdrm.Helper.*;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
|
||||
public class Installer extends AsyncTask<Void, Void, Void> {
|
||||
private boolean wasError = false;
|
||||
private String errorMsg = "";
|
||||
|
||||
private ProgressDialog dialog = null;
|
||||
private Context ctx = null;
|
||||
private StringEncryptor stringEncryptor = null;
|
||||
private String busyboxBinary = "";
|
||||
public Installer(Context context){
|
||||
this.ctx = context;
|
||||
this.stringEncryptor = new StringEncryptor(this.ctx);
|
||||
}
|
||||
|
||||
private void setError(String msg) throws Exception {
|
||||
this.wasError = true;
|
||||
this.errorMsg = msg;
|
||||
Log.e("INSTALLER",errorMsg);
|
||||
throw new Exception(msg);
|
||||
}
|
||||
private static void writeTxtFile(String txtFile, String txt) throws IOException {
|
||||
Log.i("INSTALLER", "Writing: "+txtFile);
|
||||
FileWriter txtStream = new FileWriter(txtFile);
|
||||
txtStream.write(txt);
|
||||
txtStream.close();
|
||||
}
|
||||
private void copyTo(InputStream inpStream, OutputStream outStream) throws IOException {
|
||||
int totalRead = 0;
|
||||
byte[] buffer = new byte[0x1000];
|
||||
|
||||
do {
|
||||
totalRead = inpStream.read(buffer, 0, buffer.length);
|
||||
outStream.write(buffer, 0, totalRead);
|
||||
}
|
||||
while(totalRead >= buffer.length);
|
||||
|
||||
outStream.flush();
|
||||
}
|
||||
|
||||
private void unpackResource(int resourceId, File outputFile) throws IOException {
|
||||
InputStream resourceStream = ctx.getResources().openRawResource(resourceId);
|
||||
|
||||
FileOutputStream fs = new FileOutputStream(outputFile);
|
||||
copyTo(resourceStream, fs);
|
||||
fs.close();
|
||||
|
||||
resourceStream.close();
|
||||
}
|
||||
|
||||
private boolean fileExistRoot(String filename) throws Shell.ShellDiedException {
|
||||
int res = Shell.Pool.SU.run(new String[] { this.busyboxBinary + " stat '" + filename +"'" });
|
||||
return res == 0;
|
||||
}
|
||||
|
||||
private void moveFileRoot(String filename, String destFilename) throws Exception {
|
||||
int res = Shell.Pool.SU.run(new String[] { this.busyboxBinary +" mv '"+ filename + "' '" +destFilename +"'"});
|
||||
if(res != 0){
|
||||
this.setError("Failed to rename "+filename+" to "+ destFilename);
|
||||
}
|
||||
}
|
||||
private void mkdirAndChmodChown(String directory, int chmod, String chown) throws Shell.ShellDiedException {
|
||||
Shell.Pool.SU.run(new String[]{
|
||||
this.busyboxBinary + " mkdir '" + directory +"'",
|
||||
this.busyboxBinary + " chmod " + String.valueOf(chmod) +" '"+directory+"'",
|
||||
this.busyboxBinary + " chown " + chown +":"+chown+" '"+directory+"'"
|
||||
});
|
||||
}
|
||||
private void copyChmodAndChown(String srcFile, String dstFile, int chmod, String chown) throws Exception {
|
||||
int res = Shell.Pool.SU.run(new String[]{
|
||||
this.busyboxBinary + " cp '" + srcFile +"' '"+dstFile+"'",
|
||||
this.busyboxBinary + " chmod " + String.valueOf(chmod) +" '"+dstFile+"'",
|
||||
this.busyboxBinary + " chown " + chown +":"+chown+" '"+dstFile+"'"
|
||||
});
|
||||
|
||||
if(res != 0){
|
||||
this.setError("Failed to copy & change mode.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setupBusyBox() throws Exception {
|
||||
Log.i("INSTALLER","Creating busybox binary");
|
||||
File tmpFile = new File(ctx.getCacheDir(), "busybox");
|
||||
tmpFile.createNewFile();
|
||||
|
||||
if(tmpFile.setExecutable(true,false)) {
|
||||
unpackResource(R.raw.busybox, tmpFile);
|
||||
this.busyboxBinary = tmpFile.getAbsolutePath();
|
||||
}
|
||||
else {
|
||||
this.setError("failed to extract busybox binary.");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void generateDeviceFingerprint(String outputFilename) throws FileNotFoundException, UnsupportedEncodingException {
|
||||
|
||||
|
||||
TelephonyManager tm = ((TelephonyManager) ctx.getSystemService( Context.TELEPHONY_SERVICE));
|
||||
|
||||
String deviceId = "(blank)";
|
||||
if(tm != null)
|
||||
deviceId = tm.getDeviceId();
|
||||
|
||||
if(deviceId == null)
|
||||
deviceId = "(blank)";
|
||||
|
||||
String serial = Build.SERIAL;
|
||||
if(serial == null)
|
||||
serial = "(blank)";
|
||||
|
||||
String brand = Build.BRAND;
|
||||
if(brand == null)
|
||||
brand = "(blank)";
|
||||
|
||||
String manu = Build.MANUFACTURER;
|
||||
if(manu == null)
|
||||
manu = "(blank)";
|
||||
|
||||
String model = Build.MODEL;
|
||||
if(model == null)
|
||||
model = "(blank)";
|
||||
|
||||
String product = Build.PRODUCT;
|
||||
if(product == null)
|
||||
product = "(blank)";
|
||||
|
||||
String device = Build.DEVICE;
|
||||
if(device == null)
|
||||
device = "(blank)";
|
||||
|
||||
String type = Build.TYPE;
|
||||
if(type == null)
|
||||
type = "(blank)";
|
||||
|
||||
PrintWriter writer = new PrintWriter(outputFilename, "UTF-8");
|
||||
writer.println(String.valueOf(stringEncryptor.getPsmUid()));
|
||||
writer.println(stringEncryptor.getAndroidId());
|
||||
writer.println(deviceId);
|
||||
writer.println(serial);
|
||||
writer.println(brand);
|
||||
writer.println(manu);
|
||||
writer.println(model);
|
||||
writer.println(product);
|
||||
writer.println(device);
|
||||
writer.println(type);
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private void backupPsm() throws Exception {
|
||||
String psmSdcardFolder = new File(Environment.getExternalStorageDirectory(), "psm").getAbsolutePath();
|
||||
new File(psmSdcardFolder).mkdirs();
|
||||
String filename = "psm_"+getDateTime();
|
||||
|
||||
int res = Shell.Pool.SU.run(new String[]{this.busyboxBinary + " tar c '" + getPsmApp().dataDir + "' -f '" + new File(psmSdcardFolder, filename+".tar").getAbsolutePath()+"'"});
|
||||
if (res != 0) {
|
||||
this.setError("Failed to backup existing PSM data. (exit code: " + res + ")");
|
||||
return;
|
||||
}
|
||||
generateDeviceFingerprint(new File(psmSdcardFolder, filename + "_DEV.txt").getAbsolutePath());
|
||||
}
|
||||
|
||||
private void makeDirs() throws PackageManager.NameNotFoundException, Shell.ShellDiedException {
|
||||
mkdirAndChmodChown(new File(getPsmApp().dataDir, "shared_prefs").getAbsolutePath(), 771, String.valueOf(stringEncryptor.getPsmUid()));
|
||||
mkdirAndChmodChown(new File(getPsmApp().dataDir, "files").getAbsolutePath(), 771, String.valueOf(stringEncryptor.getPsmUid()));
|
||||
mkdirAndChmodChown(new File(new File(getPsmApp().dataDir, "files"), "kdc").getAbsolutePath(), 771, String.valueOf(stringEncryptor.getPsmUid()));
|
||||
mkdirAndChmodChown(new File(getPsmApp().dataDir, "databases").getAbsolutePath(), 771, String.valueOf(stringEncryptor.getPsmUid()));
|
||||
}
|
||||
|
||||
private void patchSharedPrefs() throws Exception {
|
||||
// get the path to the shared_prefs folder
|
||||
String sharedPrefsPath = new File(getPsmApp().dataDir, "shared_prefs").getAbsolutePath();
|
||||
// check if signininfo.xml file exists or not ...
|
||||
boolean signinInfoExist = fileExistRoot(new File(sharedPrefsPath, "SigninInfo.xml").getAbsolutePath());
|
||||
|
||||
if (!signinInfoExist) { // if file not found ...
|
||||
// then generate our own shared_prefs
|
||||
|
||||
// encrypt the actual strings, username, password, etc
|
||||
String emailAddress = stringEncryptor.encryptString("nopsmdrm@transrights.lgbt");
|
||||
String password = stringEncryptor.encryptString("password");
|
||||
String accountId = stringEncryptor.encryptString(String.valueOf(0x123456789ABCDEFL));
|
||||
|
||||
// get the cache folder for our 'shared_prefs'
|
||||
String tmpPrefsFolder = ctx.getCacheDir().getAbsolutePath();
|
||||
|
||||
// work out paths to each file ...
|
||||
String csigninInfo = new File(tmpPrefsFolder, "SigninInfo.xml").getAbsolutePath();
|
||||
String cpsstorePrefs = new File(tmpPrefsFolder, "com.playstation.psstore_preferences.xml").getAbsolutePath();
|
||||
String crunningContentInfo = new File(tmpPrefsFolder, "RunningContentInfo.xml").getAbsolutePath();
|
||||
String clocalLibrary = new File(tmpPrefsFolder, "LocalLibrary.xml").getAbsolutePath();
|
||||
|
||||
String rsigninInfo = new File(sharedPrefsPath, "SigninInfo.xml").getAbsolutePath();
|
||||
String rpsstorePrefs = new File(sharedPrefsPath, "com.playstation.psstore_preferences.xml").getAbsolutePath();
|
||||
String rrunningContentInfo = new File(sharedPrefsPath, "RunningContentInfo.xml").getAbsolutePath();
|
||||
String rlocalLibrary = new File(sharedPrefsPath, "LocalLibrary.xml").getAbsolutePath();
|
||||
|
||||
// generate shared_prefs
|
||||
writeTxtFile(csigninInfo, "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n<map>\n<string name=\"SignedInUsername\">"+emailAddress+"</string>\n<boolean name=\"PassSave\" value=\"true\" />\n<string name=\"Password\">"+password+"</string>\n<boolean name=\"AutoSignIn\" value=\"true\" />\n</map>\n");
|
||||
writeTxtFile(cpsstorePrefs, "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n<map>\n<boolean name=\"key_upgradeDownloadTableForNeedWifi\" value=\"true\" />\n<string name=\"last_signin_account_id\">"+accountId+"</string>\n<long name=\"last_signin_account_region\" value=\"2\" />\n<int name=\"key_psstore\" value=\"1\" />\n<int name=\"key_downloader\" value=\"1\" />\n<int name=\"psm_license_agree_version_code\" value=\"1170\" />\n<boolean name=\"key_notDisplayAgainEndOfSupportPreNavi\" value=\"true\" />\n<int name=\"key_xmlcache\" value=\"1\" />\n<string name=\"last_signin_account_country\">US</string>\n<boolean name=\"key_notDisplayAgainContentStartNavi\" value=\"true\" />\n<int name=\"key_startcontent\" value=\"1\" />\n<int name=\"key_nsxevent\" value=\"1\" />\n<boolean name=\"key_upgradeLibraryTableForLocationUseConfirmationDate\" value=\"true\" />\n<int name=\"key_install\" value=\"1\" />\n<string name=\"update_md5\">387ce7e424258aef426aaa5be8a1638a</string>\n<boolean name=\"psm_license_agree\" value=\"true\" />\n<int name=\"key_guestinfo\" value=\"1\" />\n<string name=\"last_signin_account_language\">en</string>\n<int name=\"key_cache\" value=\"2\" />\n<int name=\"key_signinfo\" value=\"2\" />\n</map>\n");
|
||||
writeTxtFile(crunningContentInfo, "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n<map>\n<null name=\"title_id\" />\n<null name=\"next_title_id\" />\n</map>\n");
|
||||
writeTxtFile(clocalLibrary, "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n<map>\n<boolean name=\"notDisplayAgain\" value=\"true\" />\n<int name=\"sortType\" value=\"0\" />\n<boolean name=\"isList\" value=\"false\" />\n</map>\n");
|
||||
|
||||
// copy to the correct place and set permissions properly.
|
||||
copyChmodAndChown(csigninInfo, rsigninInfo, 660, String.valueOf(stringEncryptor.getPsmUid()));
|
||||
copyChmodAndChown(cpsstorePrefs, rpsstorePrefs, 660, String.valueOf(stringEncryptor.getPsmUid()));
|
||||
copyChmodAndChown(crunningContentInfo, rrunningContentInfo, 660, String.valueOf(stringEncryptor.getPsmUid()));
|
||||
copyChmodAndChown(clocalLibrary, rlocalLibrary, 660, String.valueOf(stringEncryptor.getPsmUid()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void installNoPsmDrmModules() throws Exception {
|
||||
String nativeLibsFolder = getPsmApp().nativeLibraryDir;
|
||||
|
||||
String rlibPsmKdcFile = new File(nativeLibsFolder, "libpsmkdc_jni.so").getAbsolutePath();
|
||||
String rlibDefaultFile = new File(nativeLibsFolder, "libdefault.so").getAbsolutePath();
|
||||
String realLibDefaultFile = new File(nativeLibsFolder, "libdefault_real.so").getAbsolutePath();
|
||||
boolean realLibDefaultExist = fileExistRoot(realLibDefaultFile);
|
||||
|
||||
if(!realLibDefaultExist) {
|
||||
// if libdefault_real.so not found, then rename libdefault.so to libdefault_real.so ...
|
||||
moveFileRoot(rlibDefaultFile, realLibDefaultFile);
|
||||
}
|
||||
|
||||
String clibDefault = new File(ctx.getCacheDir(), "libdefault.so").getAbsolutePath();
|
||||
String clibPsmKdc = new File(ctx.getCacheDir(), "libpsmkdc_jni.so").getAbsolutePath();
|
||||
|
||||
// unpack the library files ...
|
||||
unpackResource(R.raw.libdefault, new File(clibDefault));
|
||||
unpackResource(R.raw.libpsmkdc_jni, new File(clibPsmKdc));
|
||||
|
||||
copyChmodAndChown(clibDefault, rlibDefaultFile, 755, "system");
|
||||
copyChmodAndChown(clibPsmKdc, rlibPsmKdcFile, 755, "system");
|
||||
|
||||
}
|
||||
private void installDatabase() throws Exception {
|
||||
String libraryDbFile = new File(ctx.getCacheDir(), "library.db").getAbsolutePath();
|
||||
|
||||
String databasesFolder = new File(getPsmApp().dataDir, "databases").getAbsolutePath();
|
||||
String rlibraryDbFile = new File(databasesFolder, "library.db").getAbsolutePath();
|
||||
|
||||
unpackResource(R.raw.library, new File(libraryDbFile));
|
||||
copyChmodAndChown(libraryDbFile, rlibraryDbFile, 660, String.valueOf(stringEncryptor.getPsmUid()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
Shell.setRedirectDeprecated(false);
|
||||
|
||||
dialog = new ProgressDialog(ctx);
|
||||
dialog.setTitle("Installing NoPsmDrm ...");
|
||||
dialog.setMessage("Please Wait ...");
|
||||
dialog.setIndeterminate(true);
|
||||
dialog.setCancelable(false);
|
||||
dialog.show();
|
||||
}
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
|
||||
try {
|
||||
setupBusyBox();
|
||||
|
||||
if (isPsmInstalled()) {
|
||||
backupPsm();
|
||||
makeDirs();
|
||||
patchSharedPrefs();
|
||||
installNoPsmDrmModules();
|
||||
installDatabase();
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
this.wasError = true;
|
||||
this.errorMsg = e.getMessage();
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
dialog.dismiss();
|
||||
|
||||
if(wasError) {
|
||||
new AlertDialog.Builder((Activity)ctx)
|
||||
.setTitle("Error Occurred.")
|
||||
.setMessage(this.errorMsg)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
else{
|
||||
new AlertDialog.Builder((Activity)ctx)
|
||||
.setTitle("Installed!")
|
||||
.setMessage("Your PSM Application was patched successfully!\nNote: WI-FI has to be turned off for games to work.")
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.psmreborn.nopsmdrm;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
Helper.setContext(this);
|
||||
|
||||
(new Startup(this)).execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed(){
|
||||
finish();
|
||||
}
|
||||
public void installStart(View view) {
|
||||
(new Installer(this)).execute();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package com.psmreborn.nopsmdrm;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Environment;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
import static com.psmreborn.nopsmdrm.Helper.*;
|
||||
|
||||
public class Startup extends AsyncTask<Void, Void, Void> {
|
||||
private Context ctx;
|
||||
|
||||
private boolean wasError = false;
|
||||
private String errorMsg = "";
|
||||
|
||||
public Startup(Context context) {
|
||||
this.ctx = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
|
||||
if(!Shell.SU.available()){
|
||||
wasError = true;
|
||||
errorMsg = "Unable to get root permission.";
|
||||
}
|
||||
if(!Helper.isPsmInstalled()){
|
||||
wasError = true;
|
||||
errorMsg = "PSM Application is not installed, please install it first!";
|
||||
}
|
||||
if(!(getPsmApp().sourceDir.startsWith("/data/") || getPsmApp().sourceDir.startsWith("/system/"))){
|
||||
wasError = true;
|
||||
errorMsg = "PSM Application is installed to the SD Card not internal storage.";
|
||||
}
|
||||
if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
|
||||
wasError = true;
|
||||
errorMsg = "No SD Card inserted.";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
wasError = true;
|
||||
errorMsg = e.getMessage();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
if(!wasError) {
|
||||
Button installButton = (Button) ((Activity)ctx).findViewById(R.id.installPsm);
|
||||
installButton.setEnabled(true);
|
||||
}
|
||||
else{
|
||||
TextView rootDetectedTV = (TextView) ((Activity)ctx).findViewById(R.id.errorMsg);
|
||||
rootDetectedTV.setText("Error: "+errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package com.psmreborn.nopsmdrm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class StringEncryptor {
|
||||
private static final byte[] iv = {-126, -30, -6, -75, -99, -117, -66, 117, 39, -65, -126, -27, -12, 38, -99, 86};
|
||||
private static final byte[] salt = {-92, -102, -105, -123, 71, -33, 69, -39, -27, -32, 21, 33, 126, -81, 69, 59, 57, 29, -83, -15};
|
||||
|
||||
private Context ctx;
|
||||
|
||||
StringEncryptor(Context ctx){
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
public String getAndroidId(){
|
||||
return Settings.Secure.getString(ctx.getContentResolver(), "android_id");
|
||||
}
|
||||
public int getPsmUid() {
|
||||
try{
|
||||
ApplicationInfo psmAppInfo = this.ctx.getPackageManager().getApplicationInfo("com.playstation.psstore", 0);
|
||||
if(psmAppInfo != null) {
|
||||
return psmAppInfo.uid;
|
||||
}
|
||||
}
|
||||
catch (PackageManager.NameNotFoundException e) { };
|
||||
return 0;
|
||||
}
|
||||
private String base64(byte[] data){
|
||||
return Base64.encodeToString(data, Base64.DEFAULT);
|
||||
}
|
||||
|
||||
public String encryptString(String str) {
|
||||
byte[] data = str.getBytes();
|
||||
byte[] encryptedData = encrypt(data);
|
||||
if(encryptedData != null){
|
||||
byte[] encodedData = Arrays.copyOf(encryptedData, encryptedData.length + 2);
|
||||
encodedData[encodedData.length - 2] = 1;
|
||||
encodedData[encodedData.length - 1] = 1;
|
||||
|
||||
return base64(encodedData);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] encrypt(byte[] input){
|
||||
try {
|
||||
Cipher cipher = generateKeyCipher(StringEncryptor.salt, StringEncryptor.iv, Cipher.ENCRYPT_MODE);
|
||||
if (cipher != null) {
|
||||
return cipher.doFinal(input);
|
||||
}
|
||||
else {
|
||||
Log.e("STRINGENCRYPTOR", "cipher was null");
|
||||
}
|
||||
} catch (BadPaddingException | IllegalBlockSizeException e) { System.out.println(e.toString()) ;}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
private Cipher generateKeyCipher(final byte[] salt, final byte[] iv, final int opmode) {
|
||||
try {
|
||||
final String androidId = getAndroidId();
|
||||
final String psmUid = String.valueOf(getPsmUid());
|
||||
if (androidId == null || psmUid == null) {
|
||||
throw new InvalidParameterException();
|
||||
}
|
||||
final SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEWITHSHAAND256BITAES-CBC-BC");
|
||||
final char[] charArray = (androidId + psmUid + " com.playstation.psstore ").toCharArray();
|
||||
final PBEKeySpec keySpec = new PBEKeySpec(charArray, salt, 16, 256);
|
||||
Arrays.fill(charArray, '\0');
|
||||
final SecretKeySpec key = new SecretKeySpec(skeyFactory.generateSecret(keySpec).getEncoded(), "AES");
|
||||
final Cipher newCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
newCipher.init(opmode, key, new IvParameterSpec(iv), new SecureRandom());
|
||||
return newCipher;
|
||||
}
|
||||
catch (NoSuchPaddingException ex) {
|
||||
Log.e("STRINGENCRYPTOR", ex.toString());
|
||||
return null;
|
||||
}
|
||||
catch (InvalidAlgorithmParameterException ex2) {
|
||||
Log.e("STRINGENCRYPTOR", ex2.toString());
|
||||
return null;
|
||||
}
|
||||
catch (InvalidKeyException ex3) {
|
||||
Log.e("STRINGENCRYPTOR", ex3.toString());
|
||||
return null;
|
||||
}
|
||||
catch (InvalidKeySpecException ex4) {
|
||||
Log.e("STRINGENCRYPTOR", ex4.toString());
|
||||
return null;
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex5) {
|
||||
Log.e("STRINGENCRYPTOR", ex5.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
|
@ -0,0 +1,30 @@
|
|||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:keepScreenOn="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/installPsm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black"
|
||||
android:onClick="installStart"
|
||||
android:textSize="30sp"
|
||||
android:enabled="false"
|
||||
android:text="Install NoPsmDrm"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorMsg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:layout_below="@+id/installPsm"
|
||||
android:text=""/>
|
||||
</RelativeLayout>
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="gray">#80808080</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">NoPsmDrm Installer</string>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '8.3.2' apply false
|
||||
id 'com.android.library' version '8.3.2' apply false
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
#Mon Aug 14 02:01:41 CEST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,89 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
146
install.bat
146
install.bat
|
@ -1,146 +0,0 @@
|
|||
@echo off
|
||||
|
||||
echo This requires both JAVA 17 installed
|
||||
echo and ADB setup, and your device to be plugged into the PC !!!
|
||||
echo you must have the appropriate ADB drivers for your device already and all that.
|
||||
|
||||
set PATH=%PATH%;%CD%\res;
|
||||
|
||||
echo press any key to install
|
||||
|
||||
set /P EXISTING_INSTALL=Do you already have existing data in the PSM App installed? [Y/N]
|
||||
|
||||
set BUSYBOX_LOCATION=/data/local/tmp/busybox
|
||||
set PSM_DATA_FOLDER=/data/data/com.playstation.psstore
|
||||
|
||||
echo backing up existing psm data ...
|
||||
|
||||
REM copy busybox over
|
||||
adb push res\busybox /data/local/tmp/busybox
|
||||
adb shell "chmod 777 /data/local/tmp/busybox"
|
||||
|
||||
if /i "%EXISTING_INSTALL%" == "N" Goto :installPSM
|
||||
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% tar c %PSM_DATA_FOLDER% -v -f /data/local/tmp/psm_backup.tar'"
|
||||
adb pull /data/local/tmp/psm_backup.tar
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% rm /data/local/tmp/psm_backup.tar'"
|
||||
|
||||
goto :InstallLIBRARY
|
||||
:installPSM
|
||||
|
||||
REM everything breaks if its on external.
|
||||
adb shell pm setInstallLocation 1
|
||||
|
||||
REM ok now we can do stuff ..
|
||||
echo installing PSM
|
||||
adb install res/Psm1.7.0.apk
|
||||
|
||||
REM Install android id application
|
||||
echo installing android_id.apk
|
||||
adb install res/android_id.apk
|
||||
|
||||
adb shell "am start -n pink.yuv.android_id/.MainActivity"
|
||||
|
||||
if "%EXISTING_INSTALL%" == "N" Goto :InstallFAKEPSN
|
||||
goto InstallLIBRARY
|
||||
|
||||
:InstallFAKEPSN
|
||||
|
||||
echo Make sure you enter these PERFECTLY or it wont work !!!
|
||||
set /P ANDROID_ID=Enter "android_id" :
|
||||
set /P PSM_UID=Enter "psstore_uid" :
|
||||
|
||||
set PSN_EMAIL=nopsmdrm@transrights.lgbt
|
||||
set PSN_PASSWORD=password123
|
||||
set PSN_ACCOUNT_ID=0123456789ABCDEF
|
||||
|
||||
echo uninstalling android_id.apk
|
||||
adb uninstall pink.yuv.android_id
|
||||
|
||||
REM generate credentials cache
|
||||
echo generating fake psn account cache ...
|
||||
|
||||
java -jar res\psm.jar %ANDROID_ID% %PSM_UID% %PSN_EMAIL% %PSN_PASSWORD% %PSN_ACCOUNT_ID%
|
||||
|
||||
REM prepare xperia part
|
||||
|
||||
echo creating data folders ...
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% mkdir %PSM_DATA_FOLDER%/shared_prefs'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 771 %PSM_DATA_FOLDER%/shared_prefs'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown %PSM_UID%:%PSM_UID% %PSM_DATA_FOLDER%/shared_prefs'"
|
||||
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% mkdir %PSM_DATA_FOLDER%/files'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 771 %PSM_DATA_FOLDER%/files'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown %PSM_UID%:%PSM_UID% %PSM_DATA_FOLDER%/files'"
|
||||
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% mkdir %PSM_DATA_FOLDER%/files/kdc'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 771 %PSM_DATA_FOLDER%/files/kdc'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown %PSM_UID%:%PSM_UID% %PSM_DATA_FOLDER%/files/kdc'"
|
||||
|
||||
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% mkdir %PSM_DATA_FOLDER%/databases'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 771 %PSM_DATA_FOLDER%/databases'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown %PSM_UID%:%PSM_UID% %PSM_DATA_FOLDER%/databases'"
|
||||
|
||||
|
||||
echo transferring cache data ...
|
||||
|
||||
adb push shared_prefs\com.playstation.psstore_preferences.xml /data/local/tmp/pref.xml
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% cp /data/local/tmp/pref.xml %PSM_DATA_FOLDER%/shared_prefs/com.playstation.psstore_preferences.xml'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 660 %PSM_DATA_FOLDER%/shared_prefs/com.playstation.psstore_preferences.xml'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown %PSM_UID%:%PSM_UID% %PSM_DATA_FOLDER%/shared_prefs/com.playstation.psstore_preferences.xml'"
|
||||
|
||||
adb push shared_prefs\LocalLibrary.xml /data/local/tmp/pref.xml
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% cp /data/local/tmp/pref.xml %PSM_DATA_FOLDER%/shared_prefs/LocalLibrary.xml'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 660 %PSM_DATA_FOLDER%/shared_prefs/LocalLibrary.xml'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown %PSM_UID%:%PSM_UID% %PSM_DATA_FOLDER%/shared_prefs/LocalLibrary.xml'"
|
||||
|
||||
adb push shared_prefs\RunningContentInfo.xml /data/local/tmp/pref.xml
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% cp /data/local/tmp/pref.xml %PSM_DATA_FOLDER%/shared_prefs/RunningContentInfo.xml'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 660 %PSM_DATA_FOLDER%/shared_prefs/RunningContentInfo.xml'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown %PSM_UID%:%PSM_UID% %PSM_DATA_FOLDER%/shared_prefs/RunningContentInfo.xml'"
|
||||
|
||||
adb push shared_prefs\SigninInfo.xml /data/local/tmp/pref.xml
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% cp /data/local/tmp/pref.xml %PSM_DATA_FOLDER%/shared_prefs/SigninInfo.xml'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 660 %PSM_DATA_FOLDER%/shared_prefs/SigninInfo.xml'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown %PSM_UID%:%PSM_UID% %PSM_DATA_FOLDER%/shared_prefs/SigninInfo.xml'"
|
||||
|
||||
adb shell "%BUSYBOX_LOCATION% rm /data/local/tmp/pref.xml"
|
||||
|
||||
:InstallLIBRARY
|
||||
|
||||
REM library.db
|
||||
echo copying library.db ...
|
||||
adb push res\library.db /data/local/tmp/library.db
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% cp /data/local/tmp/library.db %PSM_DATA_FOLDER%/databases/library.db'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 660 %PSM_DATA_FOLDER%/databases/library.db'"
|
||||
if "%EXISTING_INSTALL%" == "N" Goto :chownDB
|
||||
goto delLIBRARY
|
||||
:chownDB
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown %PSM_UID%:%PSM_UID% %PSM_DATA_FOLDER%/databases/library.db'"
|
||||
:delLIBRARY
|
||||
adb shell "%BUSYBOX_LOCATION% rm /data/local/tmp/library.db"
|
||||
|
||||
|
||||
REM install nopsmdrm ...
|
||||
|
||||
echo installing libpsmdrm ...
|
||||
adb push res\libpsmkdc_jni.so /data/local/tmp/libpsmkdc_jni.so
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% cp /data/local/tmp/libpsmkdc_jni.so %PSM_DATA_FOLDER%/lib/libpsmkdc_jni.so'"
|
||||
adb shell "%BUSYBOX_LOCATION% rm /data/local/tmp/libpsmkdc_jni.so"
|
||||
|
||||
adb push res\libdefault.so /data/local/tmp/libdefault.so
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% mv %PSM_DATA_FOLDER%/lib/libdefault.so %PSM_DATA_FOLDER%/lib/libdefault_real.so'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% cp /data/local/tmp/libdefault.so %PSM_DATA_FOLDER%/lib/libdefault.so'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chmod 755 %PSM_DATA_FOLDER%/lib/libdefault.so'"
|
||||
adb shell "su -c '%BUSYBOX_LOCATION% chown system:system %PSM_DATA_FOLDER%/lib/libdefault.so'"
|
||||
adb shell "%BUSYBOX_LOCATION% rm /data/local/tmp/libdefault.so"
|
||||
|
||||
echo cleaning up busybox.
|
||||
adb shell "%BUSYBOX_LOCATION% rm %BUSYBOX_LOCATION%"
|
||||
|
||||
echo done.
|
||||
|
||||
echo Your PSM app should be patched!
|
||||
echo (remember you need to have wifi off to play anything!)
|
||||
pause>nul
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,27 @@
|
|||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'eu.chainfire.libsuperuser'
|
||||
compileSdk 10
|
||||
|
||||
defaultConfig {
|
||||
minSdk 10
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.annotation:annotation-jvm:1.7.1'
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.chainfire.libsuperuser"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
</manifest>
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.chainfire.libsuperuser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Base application class to extend from, solving some issues with
|
||||
* toasts and AsyncTasks you are likely to run into
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class Application extends android.app.Application {
|
||||
/**
|
||||
* Shows a toast message
|
||||
*
|
||||
* @param context Any context belonging to this application
|
||||
* @param message The message to show
|
||||
*/
|
||||
@AnyThread
|
||||
public static void toast(@Nullable Context context, @NonNull String message) {
|
||||
// this is a static method so it is easier to call,
|
||||
// as the context checking and casting is done for you
|
||||
|
||||
if (context == null) return;
|
||||
|
||||
if (!(context instanceof Application)) {
|
||||
context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
if (context instanceof Application) {
|
||||
final Context c = context;
|
||||
final String m = message;
|
||||
|
||||
((Application) context).runInApplicationThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(c, m, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final Handler mApplicationHandler = new Handler();
|
||||
|
||||
/**
|
||||
* Run a runnable in the main application thread
|
||||
*
|
||||
* @param r Runnable to run
|
||||
*/
|
||||
@AnyThread
|
||||
public void runInApplicationThread(@NonNull Runnable r) {
|
||||
mApplicationHandler.post(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
try {
|
||||
// workaround bug in AsyncTask, can show up (for example) when you toast from a service
|
||||
// this makes sure AsyncTask's internal handler is created from the right (main) thread
|
||||
Class.forName("android.os.AsyncTask");
|
||||
} catch (ClassNotFoundException e) {
|
||||
// will never happen
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.chainfire.libsuperuser;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.os.Process;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Utility class for logging and debug features that (by default) does nothing when not in debug mode
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "unused"})
|
||||
@AnyThread
|
||||
public class Debug {
|
||||
|
||||
// ----- DEBUGGING -----
|
||||
|
||||
private static boolean debug = true;
|
||||
|
||||
/**
|
||||
* <p>Enable or disable debug mode</p>
|
||||
*
|
||||
* <p>By default, debug mode is enabled for development
|
||||
* builds and disabled for exported APKs - see
|
||||
* BuildConfig.DEBUG</p>
|
||||
*
|
||||
* @param enable Enable debug mode ?
|
||||
*/
|
||||
public static void setDebug(boolean enable) {
|
||||
debug = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Is debug mode enabled ?</p>
|
||||
*
|
||||
* @return Debug mode enabled
|
||||
*/
|
||||
public static boolean getDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
// ----- LOGGING -----
|
||||
|
||||
public interface OnLogListener {
|
||||
void onLog(int type, String typeIndicator, String message);
|
||||
}
|
||||
|
||||
public static final String TAG = "libsuperuser";
|
||||
|
||||
public static final int LOG_GENERAL = 0x0001;
|
||||
public static final int LOG_COMMAND = 0x0002;
|
||||
public static final int LOG_OUTPUT = 0x0004;
|
||||
public static final int LOG_POOL = 0x0008;
|
||||
|
||||
public static final int LOG_NONE = 0x0000;
|
||||
public static final int LOG_ALL = 0xFFFF;
|
||||
|
||||
private static int logTypes = LOG_ALL;
|
||||
|
||||
@Nullable
|
||||
private static OnLogListener logListener = null;
|
||||
|
||||
/**
|
||||
* <p>Log a message (internal)</p>
|
||||
*
|
||||
* <p>Current debug and enabled logtypes decide what gets logged -
|
||||
* even if a custom callback is registered</p>
|
||||
*
|
||||
* @param type Type of message to log
|
||||
* @param typeIndicator String indicator for message type
|
||||
* @param message The message to log
|
||||
*/
|
||||
private static void logCommon(int type, @NonNull String typeIndicator, @NonNull String message) {
|
||||
if (debug && ((logTypes & type) == type)) {
|
||||
if (logListener != null) {
|
||||
logListener.onLog(type, typeIndicator, message);
|
||||
} else {
|
||||
Log.d(TAG, "[" + TAG + "][" + typeIndicator + "]" + (!message.startsWith("[") && !message.startsWith(" ") ? " " : "") + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Log a "general" message</p>
|
||||
*
|
||||
* <p>These messages are infrequent and mostly occur at startup/shutdown or on error</p>
|
||||
*
|
||||
* @param message The message to log
|
||||
*/
|
||||
public static void log(@NonNull String message) {
|
||||
logCommon(LOG_GENERAL, "G", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Log a "per-command" message</p>
|
||||
*
|
||||
* <p>This could produce a lot of output if the client runs many commands in the session</p>
|
||||
*
|
||||
* @param message The message to log
|
||||
*/
|
||||
public static void logCommand(@NonNull String message) {
|
||||
logCommon(LOG_COMMAND, "C", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Log a line of stdout/stderr output</p>
|
||||
*
|
||||
* <p>This could produce a lot of output if the shell commands are noisy</p>
|
||||
*
|
||||
* @param message The message to log
|
||||
*/
|
||||
public static void logOutput(@NonNull String message) {
|
||||
logCommon(LOG_OUTPUT, "O", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Log pool event</p>
|
||||
*
|
||||
* @param message The message to log
|
||||
*/
|
||||
public static void logPool(@NonNull String message) {
|
||||
logCommon(LOG_POOL, "P", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Enable or disable logging specific types of message</p>
|
||||
*
|
||||
* <p>You may | (or) LOG_* constants together. Note that
|
||||
* debug mode must also be enabled for actual logging to
|
||||
* occur.</p>
|
||||
*
|
||||
* @param type LOG_* constants
|
||||
* @param enable Enable or disable
|
||||
*/
|
||||
public static void setLogTypeEnabled(int type, boolean enable) {
|
||||
if (enable) {
|
||||
logTypes |= type;
|
||||
} else {
|
||||
logTypes &= ~type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Is logging for specific types of messages enabled ?</p>
|
||||
*
|
||||
* <p>You may | (or) LOG_* constants together, to learn if
|
||||
* <b>all</b> passed message types are enabled for logging. Note
|
||||
* that debug mode must also be enabled for actual logging
|
||||
* to occur.</p>
|
||||
*
|
||||
* @param type LOG_* constants
|
||||
* @return enabled?
|
||||
*/
|
||||
public static boolean getLogTypeEnabled(int type) {
|
||||
return ((logTypes & type) == type);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Is logging for specific types of messages enabled ?</p>
|
||||
*
|
||||
* <p>You may | (or) LOG_* constants together, to learn if
|
||||
* <b>all</b> message types are enabled for logging. Takes
|
||||
* debug mode into account for the result.</p>
|
||||
*
|
||||
* @param type LOG_* constants
|
||||
* @return enabled and in debug mode?
|
||||
*/
|
||||
public static boolean getLogTypeEnabledEffective(int type) {
|
||||
return getDebug() && getLogTypeEnabled(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Register a custom log handler</p>
|
||||
*
|
||||
* <p>Replaces the log method (write to logcat) with your own
|
||||
* handler. Whether your handler gets called is still dependent
|
||||
* on debug mode and message types being enabled for logging.</p>
|
||||
*
|
||||
* @param onLogListener Custom log listener or NULL to revert to default
|
||||
*/
|
||||
public static void setOnLogListener(@Nullable OnLogListener onLogListener) {
|
||||
logListener = onLogListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get the currently registered custom log handler</p>
|
||||
*
|
||||
* @return Current custom log handler or NULL if none is present
|
||||
*/
|
||||
@Nullable
|
||||
public static OnLogListener getOnLogListener() {
|
||||
return logListener;
|
||||
}
|
||||
|
||||
// ----- SANITY CHECKS -----
|
||||
|
||||
private static boolean sanityChecks = true;
|
||||
|
||||
/**
|
||||
* <p>Enable or disable sanity checks</p>
|
||||
*
|
||||
* <p>Enables or disables the library crashing when su is called
|
||||
* from the main thread.</p>
|
||||
*
|
||||
* @param enable Enable or disable
|
||||
*/
|
||||
public static void setSanityChecksEnabled(boolean enable) {
|
||||
sanityChecks = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Are sanity checks enabled ?</p>
|
||||
*
|
||||
* <p>Note that debug mode must also be enabled for actual
|
||||
* sanity checks to occur.</p>
|
||||
*
|
||||
* @return True if enabled
|
||||
*/
|
||||
public static boolean getSanityChecksEnabled() {
|
||||
return sanityChecks;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Are sanity checks enabled ?</p>
|
||||
*
|
||||
* <p>Takes debug mode into account for the result.</p>
|
||||
*
|
||||
* @return True if enabled
|
||||
*/
|
||||
public static boolean getSanityChecksEnabledEffective() {
|
||||
return getDebug() && getSanityChecksEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Are we running on the main thread ?</p>
|
||||
*
|
||||
* @return Running on main thread ?
|
||||
*/
|
||||
public static boolean onMainThread() {
|
||||
return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper()) && (Process.myUid() != 0));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.chainfire.libsuperuser;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Base receiver to extend to catch notifications when overlays should be
|
||||
* hidden.
|
||||
* </p>
|
||||
* <p>
|
||||
* Tapjacking protection in SuperSU prevents some dialogs from receiving user
|
||||
* input when overlays are present. For security reasons this notification is
|
||||
* only sent to apps that have previously been granted root access, so even if
|
||||
* your app does not <em>require</em> root, you still need to <em>request</em>
|
||||
* it, and the user must grant it.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that the word overlay as used here should be interpreted as "any view or
|
||||
* window possibly obscuring SuperSU dialogs".
|
||||
* </p>
|
||||
*/
|
||||
@SuppressWarnings({"unused"})
|
||||
public abstract class HideOverlaysReceiver extends BroadcastReceiver {
|
||||
public static final String ACTION_HIDE_OVERLAYS = "eu.chainfire.supersu.action.HIDE_OVERLAYS";
|
||||
public static final String CATEGORY_HIDE_OVERLAYS = Intent.CATEGORY_INFO;
|
||||
public static final String EXTRA_HIDE_OVERLAYS = "eu.chainfire.supersu.extra.HIDE";
|
||||
|
||||
@Override
|
||||
public final void onReceive(Context context, Intent intent) {
|
||||
if (intent.hasExtra(EXTRA_HIDE_OVERLAYS)) {
|
||||
onHideOverlays(context, intent, intent.getBooleanExtra(EXTRA_HIDE_OVERLAYS, false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when overlays <em>should</em> be hidden or <em>may</em> be shown
|
||||
* again.
|
||||
*
|
||||
* @param context App context
|
||||
* @param intent Received intent
|
||||
* @param hide Should overlays be hidden?
|
||||
*/
|
||||
public abstract void onHideOverlays(Context context, Intent intent, boolean hide);
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.chainfire.libsuperuser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@AnyThread
|
||||
public class MarkerInputStream extends InputStream {
|
||||
private static final String EXCEPTION_EOF = "EOF encountered, shell probably died";
|
||||
|
||||
@NonNull
|
||||
private final StreamGobbler gobbler;
|
||||
private final InputStream inputStream;
|
||||
private final byte[] marker;
|
||||
private final int markerLength;
|
||||
private final int markerMaxLength;
|
||||
private final byte[] read1 = new byte[1];
|
||||
private final byte[] buffer = new byte[65536];
|
||||
private int bufferUsed = 0;
|
||||
private volatile boolean eof = false;
|
||||
private volatile boolean done = false;
|
||||
|
||||
public MarkerInputStream(@NonNull StreamGobbler gobbler, @NonNull String marker) throws UnsupportedEncodingException {
|
||||
this.gobbler = gobbler;
|
||||
this.gobbler.suspendGobbling();
|
||||
this.inputStream = gobbler.getInputStream();
|
||||
this.marker = marker.getBytes("UTF-8");
|
||||
this.markerLength = marker.length();
|
||||
this.markerMaxLength = marker.length() + 5; // marker + space + exitCode(max(3)) + \n
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
while (true) {
|
||||
int r = read(read1, 0, 1);
|
||||
if (r < 0) return -1;
|
||||
if (r == 0) {
|
||||
// wait for data to become available
|
||||
try {
|
||||
Thread.sleep(16);
|
||||
} catch (InterruptedException e) {
|
||||
// no action
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return (int)read1[0] & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
private void fill(int safeSizeToWaitFor) {
|
||||
// fill up our own buffer
|
||||
if (isEOF()) return;
|
||||
try {
|
||||
int a;
|
||||
while (((a = inputStream.available()) > 0) || (safeSizeToWaitFor > 0)) {
|
||||
int left = buffer.length - bufferUsed;
|
||||
if (left == 0) return;
|
||||
int r = inputStream.read(buffer, bufferUsed, Math.max(safeSizeToWaitFor, Math.min(a, left)));
|
||||
if (r >= 0) {
|
||||
bufferUsed += r;
|
||||
safeSizeToWaitFor -= r;
|
||||
} else {
|
||||
// This shouldn't happen *unless* we have both the full content and the end
|
||||
// marker, otherwise the shell was interrupted/died. An IOException is raised
|
||||
// in read() below if that is the case.
|
||||
setEOF();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
setEOF();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
if (done) return -1;
|
||||
|
||||
fill(markerLength - bufferUsed);
|
||||
|
||||
// we need our buffer to be big enough to detect the marker
|
||||
if (bufferUsed < markerLength) return 0;
|
||||
|
||||
// see if we have our marker
|
||||
int match = -1;
|
||||
for (int i = Math.max(0, bufferUsed - markerMaxLength); i < bufferUsed - markerLength; i++) {
|
||||
boolean found = true;
|
||||
for (int j = 0; j < markerLength; j++) {
|
||||
if (buffer[i + j] != marker[j]) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
match = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match == 0) {
|
||||
// marker is at the front of the buffer
|
||||
while (buffer[bufferUsed -1] != (byte)'\n') {
|
||||
if (isEOF()) throw new IOException(EXCEPTION_EOF);
|
||||
fill(1);
|
||||
}
|
||||
if (gobbler.getOnLineListener() != null) gobbler.getOnLineListener().onLine(new String(buffer, 0, bufferUsed - 1, "UTF-8"));
|
||||
done = true;
|
||||
return -1;
|
||||
} else {
|
||||
int ret;
|
||||
if (match == -1) {
|
||||
if (isEOF()) throw new IOException(EXCEPTION_EOF);
|
||||
|
||||
// marker isn't in the buffer, drain as far as possible while keeping some space
|
||||
// leftover so we can still find the marker if its read is split between two fill()
|
||||
// calls
|
||||
ret = Math.min(len, bufferUsed - markerMaxLength);
|
||||
} else {
|
||||
// even if eof, it is possibly we have both the content and the end marker, which
|
||||
// counts as a completed command, so we don't throw IOException here
|
||||
|
||||
// marker found, max drain up to marker, this will eventually cause the marker to be
|
||||
// at the front of the buffer
|
||||
ret = Math.min(len, match);
|
||||
}
|
||||
if (ret > 0) {
|
||||
System.arraycopy(buffer, 0, b, off, ret);
|
||||
bufferUsed -= ret;
|
||||
System.arraycopy(buffer, ret, buffer, 0, bufferUsed);
|
||||
} else {
|
||||
try {
|
||||
// prevent 100% CPU on reading from for example /dev/random
|
||||
Thread.sleep(4);
|
||||
} catch (Exception e) {
|
||||
// no action
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
if (!isEOF() && !done) {
|
||||
// drain
|
||||
byte[] buffer = new byte[1024];
|
||||
while (read(buffer) >= 0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isEOF() {
|
||||
return eof;
|
||||
}
|
||||
|
||||
public synchronized void setEOF() {
|
||||
eof = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.chainfire.libsuperuser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
/**
|
||||
* Helper class for modifying SELinux policies, reducing the number of calls to a minimum.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
*
|
||||
* private class Policy extends eu.chainfire.libsuperuser.Policy {
|
||||
* {@literal @}Override protected String[] getPolicies() {
|
||||
* return new String[] {
|
||||
* "allow sdcardd unlabeled dir { append create execute write relabelfrom link unlink ioctl getattr setattr read rename lock mounton quotaon swapon rmdir audit_access remove_name add_name reparent execmod search open }",
|
||||
* "allow sdcardd unlabeled file { append create write relabelfrom link unlink ioctl getattr setattr read rename lock mounton quotaon swapon audit_access open }",
|
||||
* "allow unlabeled unlabeled filesystem associate"
|
||||
* };
|
||||
* }
|
||||
* };
|
||||
* private Policy policy = new Policy();
|
||||
*
|
||||
* public void someFunctionNotCalledOnMainThread() {
|
||||
* policy.inject();
|
||||
* }
|
||||
*
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "unused"})
|
||||
public abstract class Policy {
|
||||
/**
|
||||
* supolicy should be called as little as possible. We batch policies together. The command
|
||||
* line is guaranteed to be able to take 4096 characters. Reduce by a bit for supolicy itself.
|
||||
*/
|
||||
private static final int MAX_POLICY_LENGTH = 4096 - 32;
|
||||
|
||||
private static final Object synchronizer = new Object();
|
||||
@Nullable
|
||||
private static volatile Boolean canInject = null;
|
||||
private static volatile boolean injected = false;
|
||||
|
||||
/**
|
||||
* @return Have we injected our policies already?
|
||||
*/
|
||||
@AnyThread
|
||||
public static boolean haveInjected() {
|
||||
return injected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset policies-have-been-injected state, if you really need to inject them again. Extremely
|
||||
* rare, you will probably never need this.
|
||||
*/
|
||||
@AnyThread
|
||||
public static void resetInjected() {
|
||||
synchronized (synchronizer) {
|
||||
injected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to return a array of strings containing the policies you want to inject.
|
||||
*
|
||||
* @return Policies to inject
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract String[] getPolicies();
|
||||
|
||||
/**
|
||||
* Detects availability of the supolicy tool. Only useful if Shell.SU.isSELinuxEnforcing()
|
||||
* returns true. Caches return value, can safely be called from the UI thread <b>if</b> a
|
||||
* a cached value exists.
|
||||
*
|
||||
* @see #resetCanInject()
|
||||
*
|
||||
* @return canInject?
|
||||
*/
|
||||
@SuppressWarnings({"deprecation", "ConstantConditions"})
|
||||
@WorkerThread // first call only
|
||||
public static boolean canInject() {
|
||||
synchronized (synchronizer) {
|
||||
if (canInject != null) return canInject;
|
||||
|
||||
canInject = false;
|
||||
|
||||
// We are making the assumption here that if supolicy is called without parameters,
|
||||
// it will return output (such as a usage notice) on STDOUT (not STDERR) that contains
|
||||
// at least the word "supolicy". This is true at least for SuperSU.
|
||||
|
||||
List<String> result = Shell.run("sh", new String[] { "supolicy" }, null, false);
|
||||
if (result != null) {
|
||||
for (String line : result) {
|
||||
if (line.contains("supolicy")) {
|
||||
canInject = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return canInject;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset cached can-inject state and force redetection on nect canInject() call
|
||||
*/
|
||||
@AnyThread
|
||||
public static void resetCanInject() {
|
||||
synchronized (synchronizer) {
|
||||
canInject = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the policies defined by getPolicies() into a set of shell commands
|
||||
*
|
||||
* @return Possibly empty List of commands, or null
|
||||
*/
|
||||
@Nullable
|
||||
protected List<String> getInjectCommands() {
|
||||
return getInjectCommands(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the policies defined by getPolicies() into a set of shell commands
|
||||
*
|
||||
* @param allowBlocking allow method to perform blocking I/O for extra checks
|
||||
* @return Possibly empty List of commands, or null
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("all")
|
||||
@WorkerThread // if allowBlocking
|
||||
protected List<String> getInjectCommands(boolean allowBlocking) {
|
||||
synchronized (synchronizer) {
|
||||
// No reason to bother if we're in permissive mode
|
||||
if (!Shell.SU.isSELinuxEnforcing()) return null;
|
||||
|
||||
// If we can't inject, no use continuing
|
||||
if (allowBlocking && !canInject()) return null;
|
||||
|
||||
// Been there, done that
|
||||
if (injected) return null;
|
||||
|
||||
// Retrieve policies
|
||||
String[] policies = getPolicies();
|
||||
if ((policies != null) && (policies.length > 0)) {
|
||||
List<String> commands = new ArrayList<String>();
|
||||
|
||||
// Combine the policies into a minimal number of commands
|
||||
String command = "";
|
||||
for (String policy : policies) {
|
||||
if ((command.length() == 0) || (command.length() + policy.length() + 3 < MAX_POLICY_LENGTH)) {
|
||||
command = command + " \"" + policy + "\"";
|
||||
} else {
|
||||
commands.add("supolicy --live" + command);
|
||||
command = "";
|
||||
}
|
||||
}
|
||||
if (command.length() > 0) {
|
||||
commands.add("supolicy --live" + command);
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
// No policies
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the policies defined by getPolicies(). Throws an exception if called from
|
||||
* the main thread in debug mode.
|
||||
*/
|
||||
@SuppressWarnings({"deprecation"})
|
||||
@WorkerThread
|
||||
public void inject() {
|
||||
synchronized (synchronizer) {
|
||||
// Get commands that inject our policies
|
||||
List<String> commands = getInjectCommands();
|
||||
|
||||
// Execute them, if any
|
||||
if ((commands != null) && (commands.size() > 0)) {
|
||||
Shell.SU.run(commands);
|
||||
}
|
||||
|
||||
// We survived without throwing
|
||||
injected = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the policies defined by getPolicies(). Throws an exception if called from
|
||||
* the main thread in debug mode if waitForIdle is true. If waitForIdle is false
|
||||
* however, it cannot be guaranteed the command was executed and the policies injected
|
||||
* upon return.
|
||||
*
|
||||
* @param shell Interactive shell to execute commands on
|
||||
* @param waitForIdle wait for the command to complete before returning?
|
||||
*/
|
||||
@WorkerThread // if waitForIdle
|
||||
public void inject(@NonNull Shell.Interactive shell, boolean waitForIdle) {
|
||||
synchronized (synchronizer) {
|
||||
// Get commands that inject our policies
|
||||
List<String> commands = getInjectCommands(waitForIdle);
|
||||
|
||||
// Execute them, if any
|
||||
if ((commands != null) && (commands.size() > 0)) {
|
||||
shell.addCommand(commands);
|
||||
if (waitForIdle) {
|
||||
shell.waitForIdle();
|
||||
}
|
||||
}
|
||||
|
||||
// We survived without throwing
|
||||
injected = true;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.chainfire.libsuperuser;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
/**
|
||||
* Thread utility class continuously reading from an InputStream
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess"})
|
||||
public class StreamGobbler extends Thread {
|
||||
private static int threadCounter = 0;
|
||||
private static int incThreadCounter() {
|
||||
synchronized (StreamGobbler.class) {
|
||||
int ret = threadCounter;
|
||||
threadCounter++;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Line callback interface
|
||||
*/
|
||||
public interface OnLineListener {
|
||||
/**
|
||||
* <p>Line callback</p>
|
||||
*
|
||||
* <p>This callback should process the line as quickly as possible.
|
||||
* Delays in this callback may pause the native process or even
|
||||
* result in a deadlock</p>
|
||||
*
|
||||
* @param line String that was gobbled
|
||||
*/
|
||||
void onLine(String line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream closed callback interface
|
||||
*/
|
||||
public interface OnStreamClosedListener {
|
||||
/**
|
||||
* <p>Stream closed callback</p>
|
||||
*/
|
||||
void onStreamClosed();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private final String shell;
|
||||
@NonNull
|
||||
private final InputStream inputStream;
|
||||
@NonNull
|
||||
private final BufferedReader reader;
|
||||
@Nullable
|
||||
private final List<String> writer;
|
||||
@Nullable
|
||||
private final OnLineListener lineListener;
|
||||
@Nullable
|
||||
private final OnStreamClosedListener streamClosedListener;
|
||||
private volatile boolean active = true;
|
||||
private volatile boolean calledOnClose = false;
|
||||
|
||||
/**
|
||||
* <p>StreamGobbler constructor</p>
|
||||
*
|
||||
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
|
||||
* possible to prevent a deadlock from occurring, or Process.waitFor() never
|
||||
* returning (as the buffer is full, pausing the native process)</p>
|
||||
*
|
||||
* @param shell Name of the shell
|
||||
* @param inputStream InputStream to read from
|
||||
* @param outputList {@literal List<String>} to write to, or null
|
||||
*/
|
||||
@AnyThread
|
||||
public StreamGobbler(@NonNull String shell, @NonNull InputStream inputStream, @Nullable List<String> outputList) {
|
||||
super("Gobbler#" + incThreadCounter());
|
||||
this.shell = shell;
|
||||
this.inputStream = inputStream;
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
writer = outputList;
|
||||
lineListener = null;
|
||||
streamClosedListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>StreamGobbler constructor</p>
|
||||
*
|
||||
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
|
||||
* possible to prevent a deadlock from occurring, or Process.waitFor() never
|
||||
* returning (as the buffer is full, pausing the native process)</p>
|
||||
*
|
||||
* @param shell Name of the shell
|
||||
* @param inputStream InputStream to read from
|
||||
* @param onLineListener OnLineListener callback
|
||||
* @param onStreamClosedListener OnStreamClosedListener callback
|
||||
*/
|
||||
@AnyThread
|
||||
public StreamGobbler(@NonNull String shell, @NonNull InputStream inputStream, @Nullable OnLineListener onLineListener, @Nullable OnStreamClosedListener onStreamClosedListener) {
|
||||
super("Gobbler#" + incThreadCounter());
|
||||
this.shell = shell;
|
||||
this.inputStream = inputStream;
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
lineListener = onLineListener;
|
||||
streamClosedListener = onStreamClosedListener;
|
||||
writer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// keep reading the InputStream until it ends (or an error occurs)
|
||||
// optionally pausing when a command is executed that consumes the InputStream itself
|
||||
try {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Debug.logOutput(String.format(Locale.ENGLISH, "[%s] %s", shell, line));
|
||||
if (writer != null) writer.add(line);
|
||||
if (lineListener != null) lineListener.onLine(line);
|
||||
while (!active) {
|
||||
synchronized (this) {
|
||||
try {
|
||||
this.wait(128);
|
||||
} catch (InterruptedException e) {
|
||||
// no action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// reader probably closed, expected exit condition
|
||||
if (streamClosedListener != null) {
|
||||
calledOnClose = true;
|
||||
streamClosedListener.onStreamClosed();
|
||||
}
|
||||
}
|
||||
|
||||
// make sure our stream is closed and resources will be freed
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
// read already closed
|
||||
}
|
||||
|
||||
if (!calledOnClose) {
|
||||
if (streamClosedListener != null) {
|
||||
calledOnClose = true;
|
||||
streamClosedListener.onStreamClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Resume consuming the input from the stream</p>
|
||||
*/
|
||||
@AnyThread
|
||||
public void resumeGobbling() {
|
||||
if (!active) {
|
||||
synchronized (this) {
|
||||
active = true;
|
||||
this.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Suspend gobbling, so other code may read from the InputStream instead</p>
|
||||
*
|
||||
* <p>This should <i>only</i> be called from the OnLineListener callback!</p>
|
||||
*/
|
||||
@AnyThread
|
||||
public void suspendGobbling() {
|
||||
synchronized (this) {
|
||||
active = false;
|
||||
this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Wait for gobbling to be suspended</p>
|
||||
*
|
||||
* <p>Obviously this cannot be called from the same thread as {@link #suspendGobbling()}</p>
|
||||
*/
|
||||
@WorkerThread
|
||||
public void waitForSuspend() {
|
||||
synchronized (this) {
|
||||
while (active) {
|
||||
try {
|
||||
this.wait(32);
|
||||
} catch (InterruptedException e) {
|
||||
// no action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Is gobbling suspended ?</p>
|
||||
*
|
||||
* @return is gobbling suspended?
|
||||
*/
|
||||
@AnyThread
|
||||
public boolean isSuspended() {
|
||||
synchronized (this) {
|
||||
return !active;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get current source InputStream</p>
|
||||
*
|
||||
* @return source InputStream
|
||||
*/
|
||||
@NonNull
|
||||
@AnyThread
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get current OnLineListener</p>
|
||||
*
|
||||
* @return OnLineListener
|
||||
*/
|
||||
@Nullable
|
||||
@AnyThread
|
||||
public OnLineListener getOnLineListener() {
|
||||
return lineListener;
|
||||
}
|
||||
|
||||
void conditionalJoin() throws InterruptedException {
|
||||
if (calledOnClose) return; // deadlock from callback, we're inside exit procedure
|
||||
if (Thread.currentThread() == this) return; // can't join self
|
||||
join();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.chainfire.libsuperuser;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
/**
|
||||
* Utility class to decide between toolbox and toybox calls on M.
|
||||
* Note that some calls (such as 'ls') are present in both, this
|
||||
* class will favor toybox variants.
|
||||
*
|
||||
* This may not be what you want, as both syntax and output may
|
||||
* differ between the variants.
|
||||
*
|
||||
* Very specific warning, the 'mount' included with toybox tends
|
||||
* to segfault, at least on the first few 6.0 firmwares.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
|
||||
public class Toolbox {
|
||||
private static final int TOYBOX_SDK = 23;
|
||||
|
||||
private static final Object synchronizer = new Object();
|
||||
@Nullable
|
||||
private static volatile String toybox = null;
|
||||
|
||||
/**
|
||||
* Initialize. Asks toybox which commands it supports. Throws an exception if called from
|
||||
* the main thread in debug mode.
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
@WorkerThread
|
||||
public static void init() {
|
||||
// already inited ?
|
||||
if (toybox != null) return;
|
||||
|
||||
// toybox is M+
|
||||
if (Build.VERSION.SDK_INT < TOYBOX_SDK) {
|
||||
toybox = "";
|
||||
} else {
|
||||
if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) {
|
||||
Debug.log(Shell.ShellOnMainThreadException.EXCEPTION_TOOLBOX);
|
||||
throw new Shell.ShellOnMainThreadException(Shell.ShellOnMainThreadException.EXCEPTION_TOOLBOX);
|
||||
}
|
||||
|
||||
// ask toybox which commands it has, and store the info
|
||||
synchronized (synchronizer) {
|
||||
toybox = "";
|
||||
|
||||
List<String> output = Shell.SH.run("toybox");
|
||||
if (output != null) {
|
||||
toybox = " ";
|
||||
for (String line : output) {
|
||||
toybox = toybox + line.trim() + " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a command string, deciding on toolbox or toybox for its execution
|
||||
*
|
||||
* If init() has not already been called, it is called for you, which may throw an exception
|
||||
* if we're in the main thread.
|
||||
*
|
||||
* Example:
|
||||
* Toolbox.command("chmod 0.0 %s", "/some/file/somewhere");
|
||||
*
|
||||
* Output:
|
||||
* < M: "toolbox chmod 0.0 /some/file/somewhere"
|
||||
* M+ : "toybox chmod 0.0 /some/file/somewhere"
|
||||
*
|
||||
* @param format String to format. First word is the applet name.
|
||||
* @param args Arguments passed to String.format
|
||||
* @return Formatted String prefixed with either toolbox or toybox
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@WorkerThread // if init() not yet called
|
||||
public static String command(@NonNull String format, Object... args) {
|
||||
if (Build.VERSION.SDK_INT < TOYBOX_SDK) {
|
||||
return String.format(Locale.ENGLISH, "toolbox " + format, args);
|
||||
}
|
||||
|
||||
if (toybox == null) init();
|
||||
|
||||
format = format.trim();
|
||||
String applet;
|
||||
int p = format.indexOf(' ');
|
||||
if (p >= 0) {
|
||||
applet = format.substring(0, p);
|
||||
} else {
|
||||
applet = format;
|
||||
}
|
||||
|
||||
if (toybox.contains(" " + applet + " ")) {
|
||||
return String.format(Locale.ENGLISH, "toybox " + format, args);
|
||||
} else {
|
||||
return String.format(Locale.ENGLISH, "toolbox " + format, args);
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
BIN
res/Psm1.7.0.apk
BIN
res/Psm1.7.0.apk
Binary file not shown.
BIN
res/adb.exe
BIN
res/adb.exe
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
res/psm.jar
BIN
res/psm.jar
Binary file not shown.
|
@ -0,0 +1,18 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
rootProject.name = "nopsmdrm"
|
||||
include ':app'
|
||||
include ':libsuperuser'
|
Loading…
Reference in New Issue