Fix for Android SAF framework
This commit is contained in:
parent
c7fcaa26f3
commit
6b101f394d
|
@ -16,6 +16,5 @@
|
||||||
</option>
|
</option>
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
<option name="offlineMode" value="true" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,23 +3,5 @@
|
||||||
"assetPath":"mods/virtual-keyboard.zip",
|
"assetPath":"mods/virtual-keyboard.zip",
|
||||||
"Name": "VirtualKeyboard",
|
"Name": "VirtualKeyboard",
|
||||||
"UniqueID": "VirtualKeyboard"
|
"UniqueID": "VirtualKeyboard"
|
||||||
},
|
|
||||||
{
|
|
||||||
"assetPath":"mods/custom-localization.zip",
|
|
||||||
"Name": "CustomLocalization",
|
|
||||||
"UniqueID": "ZaneYork.CustomLocalization",
|
|
||||||
"CleanInstall": true,
|
|
||||||
"Version": "1.1.0",
|
|
||||||
"OriginUniqueId": ["SMAPI.CustomLocalization"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"assetPath":"mods/console-commands.zip",
|
|
||||||
"Name": "Console Commands",
|
|
||||||
"UniqueID": "SMAPI.ConsoleCommands"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"assetPath":"mods/save-backup.zip",
|
|
||||||
"Name": "SaveBackup",
|
|
||||||
"UniqueID": "SMAPI.SaveBackup"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -11,7 +11,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targetPath": "smapi-internal/",
|
"targetPath": "smapi-internal/",
|
||||||
"assetPath": "assemblies/*.dll",
|
"assetPath": "assemblies/",
|
||||||
"origin": 1
|
"origin": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -26,6 +26,8 @@ public class Constants {
|
||||||
* 安装包目标包名
|
* 安装包目标包名
|
||||||
*/
|
*/
|
||||||
public static final String TARGET_PACKAGE_NAME = "com.zane.stardewvalley";
|
public static final String TARGET_PACKAGE_NAME = "com.zane.stardewvalley";
|
||||||
|
|
||||||
|
public static final String TARGET_DATA_FILE_URI = "Android/data/" + Constants.TARGET_PACKAGE_NAME;
|
||||||
/**
|
/**
|
||||||
* 安装包目标包名
|
* 安装包目标包名
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,6 +20,9 @@ import android.view.animation.Animation;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
import com.afollestad.materialdialogs.MaterialDialog;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
@ -39,6 +42,7 @@ import com.zane.smapiinstaller.utils.FileUtils;
|
||||||
import com.zane.smapiinstaller.utils.StringUtils;
|
import com.zane.smapiinstaller.utils.StringUtils;
|
||||||
import com.zane.smapiinstaller.utils.ZipUtils;
|
import com.zane.smapiinstaller.utils.ZipUtils;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.apache.commons.io.filefilter.WildcardFileFilter;
|
import org.apache.commons.io.filefilter.WildcardFileFilter;
|
||||||
import org.zeroturnaround.zip.ZipUtil;
|
import org.zeroturnaround.zip.ZipUtil;
|
||||||
|
|
||||||
|
@ -48,6 +52,7 @@ import java.io.FileFilter;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -58,9 +63,6 @@ import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
|
||||||
|
|
||||||
import pxb.android.axml.AxmlReader;
|
import pxb.android.axml.AxmlReader;
|
||||||
import pxb.android.axml.AxmlVisitor;
|
import pxb.android.axml.AxmlVisitor;
|
||||||
import pxb.android.axml.AxmlWriter;
|
import pxb.android.axml.AxmlWriter;
|
||||||
|
@ -215,7 +217,7 @@ public class CommonLogic {
|
||||||
* @param versionCode 版本号
|
* @param versionCode 版本号
|
||||||
* @return 操作是否成功
|
* @return 操作是否成功
|
||||||
*/
|
*/
|
||||||
public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode, String packageName, long versionCode) {
|
public static boolean unpackSmapiFiles(Activity context, String apkPath, boolean checkMode, String packageName, long versionCode) {
|
||||||
List<ApkFilesManifest> apkFilesManifests = CommonLogic.findAllApkFileManifest(context);
|
List<ApkFilesManifest> apkFilesManifests = CommonLogic.findAllApkFileManifest(context);
|
||||||
filterManifest(apkFilesManifests, packageName, versionCode);
|
filterManifest(apkFilesManifests, packageName, versionCode);
|
||||||
List<ManifestEntry> manifestEntries = null;
|
List<ManifestEntry> manifestEntries = null;
|
||||||
|
@ -270,9 +272,43 @@ public class CommonLogic {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (CommonLogic.checkDataRootPermission(context)) {
|
||||||
|
Uri targetDirUri = pathToTreeUri(Constants.TARGET_DATA_FILE_URI);
|
||||||
|
DocumentFile documentFile = DocumentFile.fromTreeUri(context, targetDirUri);
|
||||||
|
for (DocumentFile file : documentFile.listFiles()) {
|
||||||
|
if (file.getName().equals("files")) {
|
||||||
|
copyDocument(context, new File(basePath, "smapi-internal"), file);
|
||||||
|
copyDocument(context, new File(basePath, "Mods"), file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void copyDocument(Activity context, File src, DocumentFile dest) {
|
||||||
|
if (src.isDirectory()) {
|
||||||
|
DocumentFile documentFile = dest.findFile(src.getName());
|
||||||
|
if (documentFile == null) {
|
||||||
|
documentFile = dest.createDirectory(src.getName());
|
||||||
|
}
|
||||||
|
for (File file : src.listFiles()) {
|
||||||
|
copyDocument(context, file, documentFile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DocumentFile documentFile = dest.findFile(src.getName());
|
||||||
|
if (documentFile == null) {
|
||||||
|
documentFile = dest.createFile("application/x-binary", src.getName());
|
||||||
|
}
|
||||||
|
if(documentFile.length() != src.length()) {
|
||||||
|
try (OutputStream outputStream = context.getContentResolver().openOutputStream(documentFile.getUri())) {
|
||||||
|
FileUtils.copy(src, outputStream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean checkMusic(Context context) {
|
private static boolean checkMusic(Context context) {
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
|
||||||
File pathFrom = new File(FileUtils.getStadewValleyBasePath(), "Android/obb/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE);
|
File pathFrom = new File(FileUtils.getStadewValleyBasePath(), "Android/obb/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE);
|
||||||
|
@ -314,7 +350,7 @@ public class CommonLogic {
|
||||||
} catch (IOException ignore) {
|
} catch (IOException ignore) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath(), targetFile);
|
ZipUtil.unpack(new File(apkPath), targetFile, name -> name.startsWith(entry.getAssetPath()) ? FilenameUtils.getName(name) : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,8 +512,24 @@ public class CommonLogic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkDataRootPermission(Activity context, int REQUEST_CODE_FOR_DIR, Consumer<Boolean> callback) {
|
public static boolean checkDataRootPermission(Activity context) {
|
||||||
Uri targetDirUri = pathToUri("Android/data/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE);
|
File pathFrom = new File(FileUtils.getStadewValleyBasePath(), "Android/data/" + Constants.TARGET_PACKAGE_NAME + "/files/");
|
||||||
|
if (!pathFrom.exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Uri targetDirUri = pathToTreeUri(Constants.TARGET_DATA_FILE_URI);
|
||||||
|
if (checkPathPermission(context, targetDirUri)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean requestDataRootPermission(Activity context, int REQUEST_CODE_FOR_DIR, Consumer<Boolean> callback) {
|
||||||
|
File pathFrom = new File(FileUtils.getStadewValleyBasePath(), "Android/data/" + Constants.TARGET_PACKAGE_NAME + "/files");
|
||||||
|
if (!pathFrom.exists()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Uri targetDirUri = pathToTreeUri(Constants.TARGET_DATA_FILE_URI);
|
||||||
if (checkPathPermission(context, targetDirUri)) {
|
if (checkPathPermission(context, targetDirUri)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -504,15 +556,19 @@ public class CommonLogic {
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
context.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
callback.accept(true);
|
callback.accept(true);
|
||||||
} else {
|
} else {
|
||||||
callback.accept(false);
|
callback.accept(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri pathToUri(String path) {
|
public static Uri pathToTreeUri(String path) {
|
||||||
return Uri.parse("content://com.android.externalstorage.documents/tree/primary%3A" + path.replace("/", "%3A"));
|
return Uri.parse("content://com.android.externalstorage.documents/tree/primary%3A" + path.replace("/", "%2F"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri pathToSingleUri(String path) {
|
||||||
|
return Uri.parse("content://com.android.externalstorage.documents/document/primary%3A" + path.replace("/", "%2F"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkPathPermission(Context context, Uri targetDirUri) {
|
public static boolean checkPathPermission(Context context, Uri targetDirUri) {
|
||||||
|
@ -522,12 +578,8 @@ public class CommonLogic {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkPathPermission(Context context, String path) {
|
|
||||||
return checkPathPermission(context, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean checkObbRootPermission(Activity context, int REQUEST_CODE_FOR_DIR, Consumer<Boolean> callback) {
|
public static boolean checkObbRootPermission(Activity context, int REQUEST_CODE_FOR_DIR, Consumer<Boolean> callback) {
|
||||||
Uri targetDirUri = pathToUri("Android/obb");
|
Uri targetDirUri = pathToTreeUri("Android/obb");
|
||||||
if (checkPathPermission(context, targetDirUri)) {
|
if (checkPathPermission(context, targetDirUri)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,23 +15,21 @@ import com.zane.smapiinstaller.constant.Constants;
|
||||||
import com.zane.smapiinstaller.constant.DialogAction;
|
import com.zane.smapiinstaller.constant.DialogAction;
|
||||||
import com.zane.smapiinstaller.databinding.FragmentInstallBinding;
|
import com.zane.smapiinstaller.databinding.FragmentInstallBinding;
|
||||||
import com.zane.smapiinstaller.dto.Tuple2;
|
import com.zane.smapiinstaller.dto.Tuple2;
|
||||||
|
import com.zane.smapiinstaller.logic.ActivityResultHandler;
|
||||||
import com.zane.smapiinstaller.logic.ApkPatcher;
|
import com.zane.smapiinstaller.logic.ApkPatcher;
|
||||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||||
|
import com.zane.smapiinstaller.logic.ModAssetsManager;
|
||||||
import com.zane.smapiinstaller.ui.main.MainTabsFragmentDirections;
|
import com.zane.smapiinstaller.ui.main.MainTabsFragmentDirections;
|
||||||
import com.zane.smapiinstaller.utils.ConfigUtils;
|
import com.zane.smapiinstaller.utils.ConfigUtils;
|
||||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||||
import com.zane.smapiinstaller.utils.FileUtils;
|
import com.zane.smapiinstaller.utils.FileUtils;
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
|
||||||
import org.apache.commons.lang3.RegExUtils;
|
import org.apache.commons.lang3.RegExUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
@ -115,9 +113,9 @@ public class InstallFragment extends Fragment {
|
||||||
* 安装逻辑
|
* 安装逻辑
|
||||||
*/
|
*/
|
||||||
private void installLogic(boolean isAdv) {
|
private void installLogic(boolean isAdv) {
|
||||||
// if (!CommonLogic.checkDataRootPermission(context, ActivityResultHandler.REQUEST_CODE_DATA_FILES_ACCESS_PERMISSION, (success) -> installLogic(isAdv))) {
|
if (!CommonLogic.requestDataRootPermission(context, ActivityResultHandler.REQUEST_CODE_DATA_FILES_ACCESS_PERMISSION, (success) -> installLogic(isAdv))) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
task.interrupt();
|
task.interrupt();
|
||||||
}
|
}
|
||||||
|
@ -138,14 +136,6 @@ public class InstallFragment extends Fragment {
|
||||||
DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found)));
|
DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, null);
|
|
||||||
// if (!CommonLogic.unpackSmapiFiles(context, apkPath, false, patcher.getGamePackageName(), patcher.getGameVersionCode())) {
|
|
||||||
// DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files)));
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// ModAssetsManager modAssetsManager = new ModAssetsManager(binding.getRoot());
|
|
||||||
// DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, 6);
|
|
||||||
// modAssetsManager.installDefaultMods();
|
|
||||||
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.patching_package, 8);
|
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.patching_package, 8);
|
||||||
File targetApk = new File(dest, "base.apk");
|
File targetApk = new File(dest, "base.apk");
|
||||||
if (!patcher.patch(paths.getFirst(), paths.getSecond(), targetApk, isAdv, false)) {
|
if (!patcher.patch(paths.getFirst(), paths.getSecond(), targetApk, isAdv, false)) {
|
||||||
|
@ -162,21 +152,21 @@ public class InstallFragment extends Fragment {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// List<String> resourcePacks = new ArrayList<>();
|
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, null);
|
||||||
// if (paths.getSecond() != null) {
|
if (!CommonLogic.unpackSmapiFiles(context, targetApk.getAbsolutePath(), false, patcher.getGamePackageName(), patcher.getGameVersionCode())) {
|
||||||
// for (String resourcePack : paths.getSecond()) {
|
DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files)));
|
||||||
// File targetResourcePack = new File(dest, FilenameUtils.getName(resourcePack));
|
return;
|
||||||
// patcher.patch(resourcePack, paths.getSecond(), targetResourcePack, false, true);
|
}
|
||||||
// resourcePacks.add(targetResourcePack.getAbsolutePath());
|
ModAssetsManager modAssetsManager = new ModAssetsManager(binding.getRoot());
|
||||||
// }
|
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, 6);
|
||||||
// }
|
modAssetsManager.installDefaultMods();
|
||||||
|
|
||||||
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.signing_package, null);
|
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.signing_package, null);
|
||||||
String signPath = patcher.sign(targetApk.getAbsolutePath());
|
String signPath = patcher.sign(targetApk.getAbsolutePath());
|
||||||
if (signPath == null) {
|
if (signPath == null) {
|
||||||
DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game)));
|
DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// List<String> signedResourcePacks = resourcePacks.stream().map(patcher::sign).collect(Collectors.toList());
|
|
||||||
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.installing_package, null);
|
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.installing_package, null);
|
||||||
patcher.install(signPath);
|
patcher.install(signPath);
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in New Issue