diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index a52dd60..a0de2a1 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -16,6 +16,5 @@
-
\ No newline at end of file
diff --git a/app/src/main/assets/apk/StardewModdingAPI.dll b/app/src/main/assets/apk/StardewModdingAPI.dll
index bb87236..18b3869 100644
Binary files a/app/src/main/assets/apk/StardewModdingAPI.dll and b/app/src/main/assets/apk/StardewModdingAPI.dll differ
diff --git a/app/src/main/assets/mods/console-commands.zip b/app/src/main/assets/mods/console-commands.zip
deleted file mode 100644
index 773ca0e..0000000
Binary files a/app/src/main/assets/mods/console-commands.zip and /dev/null differ
diff --git a/app/src/main/assets/mods/custom-localization.zip b/app/src/main/assets/mods/custom-localization.zip
deleted file mode 100644
index 6b3892e..0000000
Binary files a/app/src/main/assets/mods/custom-localization.zip and /dev/null differ
diff --git a/app/src/main/assets/mods/save-backup.zip b/app/src/main/assets/mods/save-backup.zip
deleted file mode 100644
index f205ad0..0000000
Binary files a/app/src/main/assets/mods/save-backup.zip and /dev/null differ
diff --git a/app/src/main/assets/mods/virtual-keyboard.zip b/app/src/main/assets/mods/virtual-keyboard.zip
index 490da11..b58cbb0 100644
Binary files a/app/src/main/assets/mods/virtual-keyboard.zip and b/app/src/main/assets/mods/virtual-keyboard.zip differ
diff --git a/app/src/main/assets/mods_manifest.json b/app/src/main/assets/mods_manifest.json
index 084b30c..317759c 100644
--- a/app/src/main/assets/mods_manifest.json
+++ b/app/src/main/assets/mods_manifest.json
@@ -3,23 +3,5 @@
"assetPath":"mods/virtual-keyboard.zip",
"Name": "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"
}
]
\ No newline at end of file
diff --git a/app/src/main/assets/smapi_files_manifest.json b/app/src/main/assets/smapi_files_manifest.json
index 17d5d76..0965b40 100644
--- a/app/src/main/assets/smapi_files_manifest.json
+++ b/app/src/main/assets/smapi_files_manifest.json
@@ -11,7 +11,7 @@
},
{
"targetPath": "smapi-internal/",
- "assetPath": "assemblies/*.dll",
+ "assetPath": "assemblies/",
"origin": 1
}
]
\ No newline at end of file
diff --git a/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java b/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java
index 5407012..b7f2f65 100644
--- a/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java
+++ b/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java
@@ -26,6 +26,8 @@ public class Constants {
* 安装包目标包名
*/
public static final String TARGET_PACKAGE_NAME = "com.zane.stardewvalley";
+
+ public static final String TARGET_DATA_FILE_URI = "Android/data/" + Constants.TARGET_PACKAGE_NAME;
/**
* 安装包目标包名
*/
diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java b/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java
index 51a44bc..0044645 100644
--- a/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java
+++ b/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java
@@ -20,6 +20,9 @@ import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
+import androidx.annotation.RequiresApi;
+import androidx.documentfile.provider.DocumentFile;
+
import com.afollestad.materialdialogs.MaterialDialog;
import com.fasterxml.jackson.core.type.TypeReference;
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.ZipUtils;
+import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.zeroturnaround.zip.ZipUtil;
@@ -48,6 +52,7 @@ import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.channels.Channels;
import java.util.ArrayList;
import java.util.Collections;
@@ -58,9 +63,6 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
-import androidx.annotation.RequiresApi;
-import androidx.documentfile.provider.DocumentFile;
-
import pxb.android.axml.AxmlReader;
import pxb.android.axml.AxmlVisitor;
import pxb.android.axml.AxmlWriter;
@@ -215,7 +217,7 @@ public class CommonLogic {
* @param versionCode 版本号
* @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 apkFilesManifests = CommonLogic.findAllApkFileManifest(context);
filterManifest(apkFilesManifests, packageName, versionCode);
List manifestEntries = null;
@@ -270,15 +272,49 @@ public class CommonLogic {
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;
}
+ 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) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
File pathFrom = new File(FileUtils.getStadewValleyBasePath(), "Android/obb/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE);
File pathTo = new File(FileUtils.getStadewValleyBasePath(), "StardewValley");
if (pathFrom.exists() && pathFrom.isDirectory()) {
- if(!checkObbRootPermission((Activity) context, ActivityResultHandler.REQUEST_CODE_OBB_FILES_ACCESS_PERMISSION, (success) -> checkMusic(context))) {
+ if (!checkObbRootPermission((Activity) context, ActivityResultHandler.REQUEST_CODE_OBB_FILES_ACCESS_PERMISSION, (success) -> checkMusic(context))) {
return false;
}
if (!pathTo.exists()) {
@@ -314,7 +350,7 @@ public class CommonLogic {
} catch (IOException ignore) {
}
} 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,9 +512,25 @@ public class CommonLogic {
}
}
- public static boolean checkDataRootPermission(Activity context, int REQUEST_CODE_FOR_DIR, Consumer callback) {
- Uri targetDirUri = pathToUri("Android/data/" + Constants.ORIGIN_PACKAGE_NAME_GOOGLE);
- if(checkPathPermission(context, targetDirUri)) {
+ public static boolean checkDataRootPermission(Activity context) {
+ 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 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)) {
return true;
}
ActivityResultHandler.registerListener(ActivityResultHandler.REQUEST_CODE_DATA_FILES_ACCESS_PERMISSION, (resultCode, data) -> {
@@ -504,32 +556,32 @@ public class CommonLogic {
if (uri == null) {
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);
} else {
callback.accept(false);
}
}
- public static Uri pathToUri(String path) {
- return Uri.parse("content://com.android.externalstorage.documents/tree/primary%3A" + path.replace("/", "%3A"));
+ public static Uri pathToTreeUri(String path) {
+ 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) {
- if(DocumentFile.fromTreeUri(context, targetDirUri).canWrite()) {
+ if (DocumentFile.fromTreeUri(context, targetDirUri).canWrite()) {
return true;
}
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 callback) {
- Uri targetDirUri = pathToUri("Android/obb");
- if(checkPathPermission(context, targetDirUri)) {
- return true;
+ Uri targetDirUri = pathToTreeUri("Android/obb");
+ if (checkPathPermission(context, targetDirUri)) {
+ return true;
}
ActivityResultHandler.registerListener(ActivityResultHandler.REQUEST_CODE_OBB_FILES_ACCESS_PERMISSION, (resultCode, data) -> {
takePermission(resultCode, data, context.getContentResolver(), callback);
diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java
index aa37924..84ddeda 100644
--- a/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java
+++ b/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java
@@ -15,23 +15,21 @@ import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.databinding.FragmentInstallBinding;
import com.zane.smapiinstaller.dto.Tuple2;
+import com.zane.smapiinstaller.logic.ActivityResultHandler;
import com.zane.smapiinstaller.logic.ApkPatcher;
import com.zane.smapiinstaller.logic.CommonLogic;
+import com.zane.smapiinstaller.logic.ModAssetsManager;
import com.zane.smapiinstaller.ui.main.MainTabsFragmentDirections;
import com.zane.smapiinstaller.utils.ConfigUtils;
import com.zane.smapiinstaller.utils.DialogUtils;
import com.zane.smapiinstaller.utils.FileUtils;
-import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
@@ -115,9 +113,9 @@ public class InstallFragment extends Fragment {
* 安装逻辑
*/
private void installLogic(boolean isAdv) {
-// if (!CommonLogic.checkDataRootPermission(context, ActivityResultHandler.REQUEST_CODE_DATA_FILES_ACCESS_PERMISSION, (success) -> installLogic(isAdv))) {
-// return;
-// }
+ if (!CommonLogic.requestDataRootPermission(context, ActivityResultHandler.REQUEST_CODE_DATA_FILES_ACCESS_PERMISSION, (success) -> installLogic(isAdv))) {
+ return;
+ }
if (task != null) {
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)));
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);
File targetApk = new File(dest, "base.apk");
if (!patcher.patch(paths.getFirst(), paths.getSecond(), targetApk, isAdv, false)) {
@@ -162,21 +152,21 @@ public class InstallFragment extends Fragment {
}
return;
}
-// List resourcePacks = new ArrayList<>();
-// if (paths.getSecond() != null) {
-// for (String resourcePack : paths.getSecond()) {
-// File targetResourcePack = new File(dest, FilenameUtils.getName(resourcePack));
-// patcher.patch(resourcePack, paths.getSecond(), targetResourcePack, false, true);
-// resourcePacks.add(targetResourcePack.getAbsolutePath());
-// }
-// }
+ DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.unpacking_smapi_files, null);
+ if (!CommonLogic.unpackSmapiFiles(context, targetApk.getAbsolutePath(), 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.signing_package, null);
String signPath = patcher.sign(targetApk.getAbsolutePath());
if (signPath == null) {
DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game)));
return;
}
-// List signedResourcePacks = resourcePacks.stream().map(patcher::sign).collect(Collectors.toList());
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.installing_package, null);
patcher.install(signPath);
}));