parent
3569afb695
commit
3763f7757f
|
@ -12,8 +12,8 @@ android {
|
|||
applicationId "com.zane.smapiinstaller"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 30
|
||||
versionCode 70
|
||||
versionName "3.7.6.8"
|
||||
versionCode 71
|
||||
versionName "3.7.6.9"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
|
@ -70,6 +70,7 @@ dependencies {
|
|||
implementation "androidx.navigation:navigation-ui:2.3.1"
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.webkit:webkit:1.3.0'
|
||||
implementation "androidx.documentfile:documentfile:1.0.1"
|
||||
// https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on
|
||||
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.65.01'
|
||||
implementation 'com.afollestad.material-dialogs:core:3.3.0'
|
||||
|
|
Binary file not shown.
|
@ -1,9 +1,11 @@
|
|||
package com.zane.smapiinstaller;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
|
@ -67,6 +69,20 @@ public class MainActivity extends AppCompatActivity {
|
|||
private ActivityMainBinding binding;
|
||||
|
||||
private void requestPermissions() {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
boolean haveInstallPermission = this.getPackageManager().canRequestPackageInstalls();
|
||||
if (!haveInstallPermission) {
|
||||
DialogUtils.showConfirmDialog(MainActivity.instance, R.string.confirm, R.string.request_unknown_source_permission, ((dialog, dialogAction) -> {
|
||||
if (dialogAction == DialogAction.POSITIVE) {
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
|
||||
intent.setData(Uri.parse("package:" + this.getPackageName()));
|
||||
ActivityResultHandler.registerListener(ActivityResultHandler.REQUEST_CODE_APP_INSTALL, (resultCode, data) -> this.requestPermissions());
|
||||
MainActivity.instance.startActivityForResult(intent, ActivityResultHandler.REQUEST_CODE_APP_INSTALL);
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// 先判断有没有权限
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
|
@ -79,7 +95,13 @@ public class MainActivity extends AppCompatActivity {
|
|||
requestPermissions();
|
||||
}
|
||||
});
|
||||
startActivityForResult(new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION), ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION);
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
|
||||
intent.setData(Uri.parse("package:" + this.getPackageName()));
|
||||
startActivityForResult(intent, ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION);
|
||||
} catch (ActivityNotFoundException ignored){
|
||||
startActivityForResult(new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION), ActivityResultHandler.REQUEST_CODE_ALL_FILES_ACCESS_PERMISSION);
|
||||
}
|
||||
} else {
|
||||
this.finish();
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@ public class Constants {
|
|||
* 配置文件路径
|
||||
*/
|
||||
public static final String CONFIG_PATH = "StardewValley/smapi-internal/config.user.json";
|
||||
/**
|
||||
* 原始安装包名
|
||||
*/
|
||||
public static final String ORIGIN_PACKAGE_NAME_GOOGLE = "com.chucklefish.stardewvalley";
|
||||
/**
|
||||
* 安装包目标包名
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.android.apksig.ApkSigner;
|
|||
import com.android.apksig.ApkVerifier;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.zane.smapiinstaller.BuildConfig;
|
||||
|
@ -80,7 +81,12 @@ public class ApkPatcher {
|
|||
|
||||
private final List<Consumer<Integer>> progressListener = new ArrayList<>();
|
||||
|
||||
private final Stopwatch stopwatch = Stopwatch.createUnstarted();
|
||||
private final Stopwatch stopwatch = Stopwatch.createUnstarted(new Ticker() {
|
||||
@Override
|
||||
public long read() {
|
||||
return android.os.SystemClock.elapsedRealtimeNanos();
|
||||
}
|
||||
});
|
||||
|
||||
public ApkPatcher(Context context) {
|
||||
this.context = context;
|
||||
|
@ -165,7 +171,7 @@ public class ApkPatcher {
|
|||
/**
|
||||
* 将指定APK文件重新打包,添加SMAPI,修改AndroidManifest.xml,同时验证版本是否正确
|
||||
*
|
||||
* @param apkPath APK文件路径
|
||||
* @param apkPath APK文件路径
|
||||
* @param isAdvanced 是否高级模式
|
||||
* @return 是否成功打包
|
||||
*/
|
||||
|
@ -325,8 +331,8 @@ public class ApkPatcher {
|
|||
} else {
|
||||
attr.obj = Constants.PATCHED_APP_NAME;
|
||||
}
|
||||
// return Collections.singletonList(new ManifestTagVisitor.AttrArgs(attr.ns, "requestLegacyExternalStorage", -1, NodeVisitor.TYPE_INT_BOOLEAN, true));
|
||||
}
|
||||
// return Collections.singletonList(new ManifestTagVisitor.AttrArgs(attr.ns, "requestLegacyExternalStorage", -1, NodeVisitor.TYPE_INT_BOOLEAN, true));
|
||||
break;
|
||||
case "authorities":
|
||||
if (strObj.contains(packageName.get())) {
|
||||
|
@ -357,11 +363,12 @@ public class ApkPatcher {
|
|||
Function<ManifestTagVisitor.ChildArgs, List<ManifestTagVisitor.ChildArgs>> childProcessLogic = (child -> {
|
||||
if (!permissionAppended.get() && StringUtils.equals(child.name, "uses-permission")) {
|
||||
permissionAppended.set(true);
|
||||
return Collections.singletonList(new ManifestTagVisitor.ChildArgs(
|
||||
child.ns, child.name, Collections.singletonList(
|
||||
new ManifestTagVisitor.AttrArgs(
|
||||
"http://schemas.android.com/apk/res/android", "name", -1,
|
||||
NodeVisitor.TYPE_STRING, "android.permission.MANAGE_EXTERNAL_STORAGE"))));
|
||||
return Collections.singletonList(
|
||||
new ManifestTagVisitor.ChildArgs(child.ns, child.name, Collections.singletonList(
|
||||
new ManifestTagVisitor.AttrArgs(
|
||||
"http://schemas.android.com/apk/res/android", "name", -1,
|
||||
NodeVisitor.TYPE_STRING, "android.permission.MANAGE_EXTERNAL_STORAGE")))
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
@ -453,6 +460,7 @@ public class ApkPatcher {
|
|||
DialogUtils.showConfirmDialog(MainActivity.instance, R.string.confirm, R.string.request_unknown_source_permission, ((dialog, dialogAction) -> {
|
||||
if (dialogAction == DialogAction.POSITIVE) {
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
|
||||
intent.setData(Uri.parse("package:" + context.getPackageName()));
|
||||
ActivityResultHandler.registerListener(ActivityResultHandler.REQUEST_CODE_APP_INSTALL, (resultCode, data) -> this.install(apkPath));
|
||||
MainActivity.instance.startActivityForResult(intent, ActivityResultHandler.REQUEST_CODE_APP_INSTALL);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.lmntrx.android.library.livin.missme.ProgressDialog;
|
|||
import com.microsoft.appcenter.crashes.Crashes;
|
||||
import com.zane.smapiinstaller.MainApplication;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.constant.Constants;
|
||||
import com.zane.smapiinstaller.constant.DialogAction;
|
||||
import com.zane.smapiinstaller.constant.ManifestPatchConstants;
|
||||
import com.zane.smapiinstaller.entity.ApkFilesManifest;
|
||||
|
@ -188,7 +189,7 @@ public class CommonLogic {
|
|||
return apkFilesManifests;
|
||||
}
|
||||
|
||||
public static String computePackageName(PackageInfo packageInfo){
|
||||
public static String computePackageName(PackageInfo packageInfo) {
|
||||
String packageName = packageInfo.packageName;
|
||||
if (StringUtils.endsWith(packageInfo.versionName, ManifestPatchConstants.PATTERN_VERSION_AMAZON)) {
|
||||
packageName = ManifestPatchConstants.APP_PACKAGE_NAME + ManifestPatchConstants.PATTERN_VERSION_AMAZON;
|
||||
|
@ -199,26 +200,28 @@ public class CommonLogic {
|
|||
/**
|
||||
* 提取SMAPI环境文件到内部存储对应位置
|
||||
*
|
||||
* @param context context
|
||||
* @param apkPath 安装包路径
|
||||
* @param checkMode 是否为校验模式
|
||||
* @param context context
|
||||
* @param apkPath 安装包路径
|
||||
* @param checkMode 是否为校验模式
|
||||
* @param packageName 包名
|
||||
* @param versionCode 版本号
|
||||
* @return 操作是否成功
|
||||
*/
|
||||
public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode, String packageName, long versionCode) {
|
||||
checkMusic(packageName, checkMode);
|
||||
List<ApkFilesManifest> apkFilesManifests = CommonLogic.findAllApkFileManifest(context);
|
||||
filterManifest(apkFilesManifests, packageName, versionCode);
|
||||
List<ManifestEntry> manifestEntries = null;
|
||||
ApkFilesManifest apkFilesManifest = null;
|
||||
if(apkFilesManifests.size() > 0) {
|
||||
if (apkFilesManifests.size() > 0) {
|
||||
apkFilesManifest = apkFilesManifests.get(0);
|
||||
String basePath = apkFilesManifest.getBasePath();
|
||||
if(StringUtils.isNoneBlank(basePath)) {
|
||||
manifestEntries = FileUtils.getAssetJson(context, basePath + "smapi_files_manifest.json", new TypeReference<List<ManifestEntry>>() {});
|
||||
if (StringUtils.isNoneBlank(basePath)) {
|
||||
manifestEntries = FileUtils.getAssetJson(context, basePath + "smapi_files_manifest.json", new TypeReference<List<ManifestEntry>>() {
|
||||
});
|
||||
}
|
||||
}
|
||||
if(manifestEntries == null) {
|
||||
if (manifestEntries == null) {
|
||||
manifestEntries = FileUtils.getAssetJson(context, "smapi_files_manifest.json", new TypeReference<List<ManifestEntry>>() {
|
||||
});
|
||||
}
|
||||
|
@ -230,11 +233,10 @@ public class CommonLogic {
|
|||
if (!basePath.mkdir()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(!checkMode) {
|
||||
} else {
|
||||
if (!checkMode) {
|
||||
File[] oldAssemblies = new File(basePath, "smapi-internal").listFiles((FileFilter) new WildcardFileFilter("*.dll"));
|
||||
if(oldAssemblies != null) {
|
||||
if (oldAssemblies != null) {
|
||||
for (File file : oldAssemblies) {
|
||||
FileUtils.deleteQuietly(file);
|
||||
}
|
||||
|
@ -264,9 +266,34 @@ public class CommonLogic {
|
|||
return true;
|
||||
}
|
||||
|
||||
private static void checkMusic(String packageName, boolean checkMode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && StringUtils.equals(packageName, Constants.ORIGIN_PACKAGE_NAME_GOOGLE)) {
|
||||
File pathFrom = new File(FileUtils.getStadewValleyBasePath(), "Android/obb/" + packageName);
|
||||
File pathTo = new File(FileUtils.getStadewValleyBasePath(), "Android/obb/" + Constants.TARGET_PACKAGE_NAME);
|
||||
if (pathFrom.exists() && pathFrom.isDirectory()) {
|
||||
if (!pathTo.exists()) {
|
||||
pathTo.mkdirs();
|
||||
}
|
||||
File[] files = pathFrom.listFiles((dir, name) -> name.contains("com.chucklefish.stardewvalley.obb"));
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
try {
|
||||
File targetFile = new File(pathTo, file.getName());
|
||||
if (!targetFile.exists() || FileUtils.sizeOf(targetFile) != FileUtils.sizeOf(file)) {
|
||||
FileUtils.copyFile(file, targetFile);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Crashes.trackError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void unpackFromApk(String apkPath, boolean checkMode, ManifestEntry entry, File targetFile) {
|
||||
if (!checkMode || !targetFile.exists()) {
|
||||
if(entry.isXALZ()){
|
||||
if (entry.isXALZ()) {
|
||||
byte[] bytes = ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath());
|
||||
if (entry.isXALZ()) {
|
||||
bytes = ZipUtils.decompressXALZ(bytes);
|
||||
|
@ -275,22 +302,20 @@ public class CommonLogic {
|
|||
stream.write(bytes);
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath(), targetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void unpackFromInstaller(Context context, boolean checkMode, ApkFilesManifest apkFilesManifest, File basePath, ManifestEntry entry, File targetFile) {
|
||||
if(entry.isExternal() && apkFilesManifest != null){
|
||||
if (entry.isExternal() && apkFilesManifest != null) {
|
||||
byte[] bytes = FileUtils.getAssetBytes(context, apkFilesManifest.getBasePath() + entry.getAssetPath());
|
||||
try (FileOutputStream outputStream = new FileOutputStream(targetFile)) {
|
||||
outputStream.write(bytes);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (entry.getTargetPath().endsWith("/") && entry.getAssetPath().contains("*")) {
|
||||
String path = StringUtils.substring(entry.getAssetPath(), 0, StringUtils.lastIndexOf(entry.getAssetPath(), "/"));
|
||||
String pattern = StringUtils.substringAfterLast(entry.getAssetPath(), "/");
|
||||
|
@ -306,7 +331,7 @@ public class CommonLogic {
|
|||
}
|
||||
}
|
||||
|
||||
public static void filterManifest(List<ApkFilesManifest> manifests, String packageName, long versionCode){
|
||||
public static void filterManifest(List<ApkFilesManifest> manifests, String packageName, long versionCode) {
|
||||
Iterables.removeIf(manifests, manifest -> {
|
||||
if (manifest == null) {
|
||||
return true;
|
||||
|
@ -322,6 +347,7 @@ public class CommonLogic {
|
|||
return manifest.getTargetPackageName() != null && packageName != null && !manifest.getTargetPackageName().contains(packageName);
|
||||
});
|
||||
}
|
||||
|
||||
private static void unpackFile(Context context, boolean checkMode, String assertPath, File targetFile) {
|
||||
if (!checkMode || !targetFile.exists()) {
|
||||
try (InputStream inputStream = context.getAssets().open(assertPath)) {
|
||||
|
|
|
@ -35,6 +35,9 @@ public class ZipUtils {
|
|||
private final static String FILE_HEADER_XALZ = "XALZ";
|
||||
|
||||
public static byte[] decompressXALZ(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return bytes;
|
||||
}
|
||||
if (FILE_HEADER_XALZ.equals(new String(ByteUtils.subArray(bytes, 0, 4), StandardCharsets.ISO_8859_1))) {
|
||||
byte[] length = ByteUtils.subArray(bytes, 8, 12);
|
||||
int len = (length[0] & 0xff) | ((length[1] & 0xff) << 8) | ((length[2] & 0xff) << 16) | ((length[3] & 0xff) << 24);
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<string name="settings_verbose_logging">Registro detallado</string>
|
||||
<string name="signing_package">Firmando paquete deinstalación</string>
|
||||
<string name="smapi_game_name">SMAPI Stardew Valley</string>
|
||||
<string name="smapi_version">Versión SMAPI: 3.7.6</string>
|
||||
<string name="smapi_version">Versión SMAPI: 3.7.6.1</string>
|
||||
<string name="text_install_tip1">Nota: Requiere la versión del juego 1.4.5.148 o superior</string>
|
||||
<string name="text_install_tip2">El cuerpo del juego debe instalarse durante la actualización o instalación</string>
|
||||
<string name="unpacking_smapi_files">Desempacando</string>
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<string name="settings_verbose_logging">Journalisation détaillée</string>
|
||||
<string name="signing_package">Signature</string>
|
||||
<string name="smapi_game_name">SMAPI Stardew Valley</string>
|
||||
<string name="smapi_version">Version SMAPI: 3.7.6</string>
|
||||
<string name="smapi_version">Version SMAPI: 3.7.6.1</string>
|
||||
<string name="text_install_tip1">Remarques: La version du jeu 1.4.5.148 ou ultérieure est requise.</string>
|
||||
<string name="text_install_tip2">Le jeu de base est requis lors de la mise à jour / installation.</string>
|
||||
<string name="unpacking_smapi_files">Déballage</string>
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<string name="settings_verbose_logging">Catatan Terperinci</string>
|
||||
<string name="signing_package">Menandatangani</string>
|
||||
<string name="smapi_game_name">SMAPI Stardew Valley</string>
|
||||
<string name="smapi_version">Versi SMAPI: 3.7.6</string>
|
||||
<string name="smapi_version">Versi SMAPI: 3.7.6.1</string>
|
||||
<string name="text_install_tip1">Catatan: Dibutuhkan Stardew Valley versi 1.4.5.148 atau yang lebih baru.</string>
|
||||
<string name="text_install_tip2">Permainan dasar diperlukan saat memperbarui/menginstal.</string>
|
||||
<string name="unpacking_smapi_files">Membongkar</string>
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<string name="settings_verbose_logging">자세한 로그</string>
|
||||
<string name="signing_package">설치 패키지 서명</string>
|
||||
<string name="smapi_game_name">SMAPI Stardew Valley</string>
|
||||
<string name="smapi_version">SMAPI버전: 3.7.6</string>
|
||||
<string name="smapi_version">SMAPI버전: 3.7.6.1</string>
|
||||
<string name="text_install_tip1">참고 : 게임 버전 1.4.5.148 이상이 필요합니다</string>
|
||||
<string name="text_install_tip2">업데이트 또는 설치 중에 게임 본체를 설치해야합니다</string>
|
||||
<string name="unpacking_smapi_files">포장 풀기</string>
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<string name="settings_verbose_logging">Log detalhado</string>
|
||||
<string name="signing_package">Assinatura</string>
|
||||
<string name="smapi_game_name">SMAPI Stardew Valley</string>
|
||||
<string name="smapi_version">Versão SMAPI: 3.7.6</string>
|
||||
<string name="smapi_version">Versão SMAPI: 3.7.6.1</string>
|
||||
<string name="text_install_tip1">Notas: É necessária a versão do jogo 1.4.5.148 ou posterior.</string>
|
||||
<string name="text_install_tip2">O jogo base é necessário ao atualizar / instalar.</string>
|
||||
<string name="unpacking_smapi_files">Desembalar</string>
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<string name="settings_verbose_logging">บันทึกอย่างละเอียด</string>
|
||||
<string name="signing_package">กำลังลงทะเบียนแอป</string>
|
||||
<string name="smapi_game_name">SMAPI Stardew Valley</string>
|
||||
<string name="smapi_version">เวอร์ชั่น SMAPI: 3.7.6</string>
|
||||
<string name="smapi_version">เวอร์ชั่น SMAPI: 3.7.6.1</string>
|
||||
<string name="text_install_tip1">หมายเหตุ: ต้องการเกมเวอร์ชั่น 1.4.5.148 หรือใหม่กว่า</string>
|
||||
<string name="text_install_tip2">ต้องการเกมหลักเมื่อทำการอัปเดต / ติดตั้ง</string>
|
||||
<string name="unpacking_smapi_files">กำลังแกะกล่อง</string>
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<string name="settings_verbose_logging">詳細日誌</string>
|
||||
<string name="signing_package">正在簽名安裝包</string>
|
||||
<string name="smapi_game_name">SMAPI 星露谷物語</string>
|
||||
<string name="smapi_version">SMAPI版本: 3.7.6</string>
|
||||
<string name="smapi_version">SMAPI版本: 3.7.6.1</string>
|
||||
<string name="text_install_tip1">注意:需要 1.4.5.148 以上遊戲版本</string>
|
||||
<string name="text_install_tip2">更新或安裝期間需要安裝遊戲</string>
|
||||
<string name="unpacking_smapi_files">正在解包</string>
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<string name="settings_verbose_logging">详细日志</string>
|
||||
<string name="signing_package">正在签名安装包</string>
|
||||
<string name="smapi_game_name">SMAPI星露谷物语</string>
|
||||
<string name="smapi_version">SMAPI版本: 3.7.6</string>
|
||||
<string name="smapi_version">SMAPI版本: 3.7.6.1</string>
|
||||
<string name="text_install_tip1">注意:需要不低于1.4.5.148版本的游戏本体</string>
|
||||
<string name="text_install_tip2">更新或安装期间需要安装游戏本体</string>
|
||||
<string name="unpacking_smapi_files">正在解包</string>
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<string name="settings_verbose_logging">Verbose Logging</string>
|
||||
<string name="signing_package">Signing</string>
|
||||
<string name="smapi_game_name">SMAPI Stardew Valley</string>
|
||||
<string name="smapi_version">SMAPI Version: 3.7.6</string>
|
||||
<string name="smapi_version">SMAPI Version: 3.7.6.1</string>
|
||||
<string name="text_install_tip1">Notes: Game version 1.4.5.148 or later is required.</string>
|
||||
<string name="text_install_tip2">The base game is required when updating/installing.</string>
|
||||
<string name="unpacking_smapi_files">Unpacking</string>
|
||||
|
|
Loading…
Reference in New Issue