Change to an android app

This commit is contained in:
Li 2024-04-21 01:57:35 +12:00
parent db61025c7b
commit c3c854a8dc
53 changed files with 5907 additions and 169 deletions

20
.gitignore vendored
View File

@ -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/*

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
nopsmdrm

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -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>

20
.idea/gradle.xml Normal file
View File

@ -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>

10
.idea/migrations.xml Normal file
View File

@ -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>

9
.idea/misc.xml Normal file
View File

@ -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>

View File

@ -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.

View File

@ -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 ?

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

35
app/build.gradle Normal file
View File

@ -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')
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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.

View File

@ -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>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">NoPsmDrm Installer</string>
</resources>

6
build.gradle Normal file
View File

@ -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
}

21
gradle.properties Normal file
View File

@ -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

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -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

185
gradlew vendored Normal file
View File

@ -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" "$@"

89
gradlew.bat vendored Normal file
View File

@ -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

View File

@ -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

1
libsuperuser/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

27
libsuperuser/build.gradle Normal file
View File

@ -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'
}

View File

@ -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>

View File

@ -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
}
}
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -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:
* &lt; 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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

18
settings.gradle Normal file
View File

@ -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'