mirror of
https://github.com/xoureldeen/Vectras-VM-Android.git
synced 2026-05-09 19:53:54 +00:00
commit
ad4edad271
16 changed files with 315 additions and 114 deletions
|
|
@ -13,7 +13,7 @@ android {
|
|||
minSdk minApi
|
||||
targetSdk targetApi
|
||||
versionCode 20
|
||||
versionName "v2.9.4-genoise"
|
||||
versionName "v2.9.4-honeycake"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -578,6 +578,11 @@ public class MainSettingsManager extends AppCompatActivity
|
|||
return prefs.getBoolean("useDefaultBios", true);
|
||||
}
|
||||
|
||||
public static boolean useMemoryOvercommit(Activity activity) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
return prefs.getBoolean("useMemoryOvercommit", true);
|
||||
}
|
||||
|
||||
public static boolean useLocalTime(Activity activity) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
return prefs.getBoolean("useLocalTime", true);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ public class AppConfig {
|
|||
}
|
||||
public static String basefiledir = "";
|
||||
public static String maindirpath = "";
|
||||
public static String recyclebin = "";
|
||||
//public static String basefiledir = datadirpath(SplashActivity.activity) + "/.qemu/";
|
||||
//public static String maindirpath = FileUtils.getExternalFilesDirectory(SplashActivity.activity).getPath() + "/";
|
||||
public static String sharedFolder = maindirpath + "SharedFolder/";
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import com.vectras.vm.logger.VectrasStatus;
|
|||
import com.vectras.vm.utils.FileUtils;
|
||||
import com.vectras.vm.utils.UIUtils;
|
||||
|
||||
import org.checkerframework.checker.units.qual.C;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
|
@ -743,19 +744,7 @@ public class CustomRomActivity extends AppCompatActivity {
|
|||
|
||||
private void checkJsonFile() {
|
||||
if (isFileExists(AppConfig.romsdatajson)) {
|
||||
if (!VectrasApp.checkJSONIsNormal(AppConfig.romsdatajson)) {
|
||||
alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
|
||||
alertDialog.setTitle(getResources().getString(R.string.oops));
|
||||
alertDialog.setMessage(getResources().getString(R.string.need_fix_json_before_create));
|
||||
alertDialog.setCancelable(true);
|
||||
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getResources().getString(R.string.delete_all), (dialog, which) -> {
|
||||
VectrasApp.writeToFile(AppConfig.maindirpath, "roms-data.json", "[]");
|
||||
});
|
||||
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getResources().getString(R.string.cancel), (dialog, which) -> {
|
||||
|
||||
});
|
||||
alertDialog.show();
|
||||
} else {
|
||||
if (VMManager.isRomsDataJsonNormal(true, CustomRomActivity.this)) {
|
||||
startCreateNewVM();
|
||||
}
|
||||
|
||||
|
|
@ -1064,8 +1053,9 @@ public class CustomRomActivity extends AppCompatActivity {
|
|||
setDefault();
|
||||
}
|
||||
drive.setText(_getDiskFile);
|
||||
VMManager.setArch("X86_64", CustomRomActivity.this);
|
||||
}
|
||||
VMManager.setArch("X86_64", getApplicationContext());
|
||||
|
||||
VectrasApp.oneDialog(getResources().getString(R.string.oops), getResources().getString(R.string.error_CR_CVBI2), true, false, CustomRomActivity.this);
|
||||
} else {
|
||||
//Error code: CR_CVBI3
|
||||
|
|
@ -1110,9 +1100,9 @@ public class CustomRomActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
if (jObj.has("arch") && !jObj.isNull("arch")) {
|
||||
VMManager.setArch(jObj.getString("arch"), getApplicationContext());
|
||||
VMManager.setArch(jObj.getString("arch"), CustomRomActivity.this);
|
||||
} else {
|
||||
VMManager.setArch("x86_64", getApplicationContext());
|
||||
VMManager.setArch("x86_64", CustomRomActivity.this);
|
||||
}
|
||||
|
||||
VectrasApp.moveAFile(AppConfig.vmFolder + _filename.replace(".cvbi", ""), AppConfig.vmFolder + vmID);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import static android.content.Intent.ACTION_VIEW;
|
|||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import com.termux.app.TermuxService;
|
||||
|
||||
import static com.vectras.vm.VectrasApp.getApp;
|
||||
import static com.vectras.vm.VectrasApp.getAppInfo;
|
||||
import static com.vectras.vm.utils.UIUtils.UIAlert;
|
||||
|
||||
|
|
@ -1364,21 +1365,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
|
||||
private void errorjsondialog() {
|
||||
if (VectrasApp.isFileExists(AppConfig.romsdatajson)) {
|
||||
if (!VectrasApp.checkJSONIsNormal(AppConfig.romsdatajson)) {
|
||||
alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
|
||||
alertDialog.setTitle(getResources().getString(R.string.oops));
|
||||
alertDialog.setMessage(getResources().getString(R.string.an_error_occurred_with_the_vm_list_data));
|
||||
alertDialog.setCancelable(true);
|
||||
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getResources().getString(R.string.delete_all), (dialog, which) -> {
|
||||
VectrasApp.writeToFile(AppConfig.maindirpath, "roms-data.json", "[]");
|
||||
loadDataVbi();
|
||||
mdatasize();
|
||||
});
|
||||
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getResources().getString(R.string.cancel), (dialog, which) -> {
|
||||
|
||||
});
|
||||
alertDialog.show();
|
||||
} else {
|
||||
if (VMManager.isRomsDataJsonNormal(true, MainActivity.this)) {
|
||||
loadDataVbi();
|
||||
mdatasize();
|
||||
}
|
||||
|
|
@ -1426,7 +1413,11 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
public static void mdatasize2() {
|
||||
linearnothinghere.setVisibility(View.VISIBLE);
|
||||
if (MainActivity.data.isEmpty()) {
|
||||
linearnothinghere.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
linearnothinghere.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkpermissions() {
|
||||
|
|
|
|||
|
|
@ -193,8 +193,11 @@ public class StartVM {
|
|||
}
|
||||
}
|
||||
|
||||
params.add("-overcommit");
|
||||
params.add("mem-lock=off");
|
||||
if (MainSettingsManager.useMemoryOvercommit(MainActivity.activity)) {
|
||||
params.add("-overcommit");
|
||||
params.add("mem-lock=off");
|
||||
}
|
||||
|
||||
|
||||
if (MainSettingsManager.useLocalTime(MainActivity.activity)) {
|
||||
params.add("-rtc");
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
package com.vectras.vm;
|
||||
|
||||
import static com.vectras.vm.VectrasApp.isFileExists;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.vectras.qemu.MainSettingsManager;
|
||||
import com.vectras.vm.MainRoms.AdapterMainRoms;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
|
@ -77,11 +85,11 @@ public class VMManager {
|
|||
public static String idGenerator() {
|
||||
String _result = startRamdomVMID();
|
||||
|
||||
if (VectrasApp.isFileExists(AppConfig.maindirpath + "/roms/" + _result)) {
|
||||
if (isFileExists(AppConfig.maindirpath + "/roms/" + _result)) {
|
||||
_result = startRamdomVMID();
|
||||
}
|
||||
|
||||
if (VectrasApp.isFileExists(AppConfig.maindirpath + "/roms/" + _result)) {
|
||||
if (isFileExists(AppConfig.maindirpath + "/roms/" + _result)) {
|
||||
_result = startRamdomVMID();
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +148,7 @@ public class VMManager {
|
|||
if (!_filelist.isEmpty()) {
|
||||
for (int _repeat = 0; _repeat < (int)(_filelist.size()); _repeat++) {
|
||||
if (_startRepeat < _filelist.size()) {
|
||||
if (VectrasApp.isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
if (isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
_currentVMIDToScan = VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/vmID.txt").replace("\n", "");
|
||||
if (!_currentVMIDToScan.isEmpty()) {
|
||||
if (_currentVMIDToScan.equals(pendingVMID)) {
|
||||
|
|
@ -169,7 +177,7 @@ public class VMManager {
|
|||
if (!_filelist.isEmpty()) {
|
||||
for (int _repeat = 0; _repeat < (int)(_filelist.size()); _repeat++) {
|
||||
if (_startRepeat < _filelist.size()) {
|
||||
if (VectrasApp.isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
if (isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
_currentVMIDToScan = VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/vmID.txt").replace("\n", "");
|
||||
if (!_currentVMIDToScan.isEmpty()) {
|
||||
if (_currentVMIDToScan.equals(_vmID)) {
|
||||
|
|
@ -200,7 +208,7 @@ public class VMManager {
|
|||
if (!_filelist.isEmpty()) {
|
||||
for (int _repeat = 0; _repeat < (int)(_filelist.size()); _repeat++) {
|
||||
if (_startRepeat < _filelist.size()) {
|
||||
if (VectrasApp.isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
if (isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
_currentVMIDToScan = VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/vmID.txt").replace("\n", "");
|
||||
if (!_currentVMIDToScan.isEmpty()) {
|
||||
if (_currentVMIDToScan.equals(pendingVMID)) {
|
||||
|
|
@ -224,7 +232,7 @@ public class VMManager {
|
|||
if (!_filelist.isEmpty()) {
|
||||
for (int _repeat = 0; _repeat < (int)(_filelist.size()); _repeat++) {
|
||||
if (_startRepeat < _filelist.size()) {
|
||||
if (!VectrasApp.isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
if (!isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
if (!finalJson.contains(_filelist.get((int) (_startRepeat)))) {
|
||||
VectrasApp.deleteDirectory(_filelist.get((int) (_startRepeat)));
|
||||
}
|
||||
|
|
@ -246,8 +254,8 @@ public class VMManager {
|
|||
if (!_filelist.isEmpty()) {
|
||||
for (int _repeat = 0; _repeat < (int)(_filelist.size()); _repeat++) {
|
||||
if (_startRepeat < _filelist.size()) {
|
||||
if (!VectrasApp.isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
if (VectrasApp.isFileExists(_filelist.get((int)(_startRepeat)) + "/rom-data.json")) {
|
||||
if (!isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
if (isFileExists(_filelist.get((int)(_startRepeat)) + "/rom-data.json")) {
|
||||
if (VectrasApp.checkJSONMapIsNormalFromString(VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json"))) {
|
||||
if (_resulttemp.contains("}")) {
|
||||
_resulttemp += "," + VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json");
|
||||
|
|
@ -260,7 +268,7 @@ public class VMManager {
|
|||
} else {
|
||||
_result = VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json");
|
||||
}
|
||||
if (VectrasApp.isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt")) {
|
||||
if (isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt")) {
|
||||
enableVMID(VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt"));
|
||||
} else {
|
||||
VectrasApp.writeToFile(_filelist.get((int)(_startRepeat)), "/vmID.txt", VMManager.idGenerator());
|
||||
|
|
@ -272,7 +280,7 @@ public class VMManager {
|
|||
} else {
|
||||
_result = "," + VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json");
|
||||
}
|
||||
if (VectrasApp.isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt")) {
|
||||
if (isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt")) {
|
||||
enableVMID(VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt"));
|
||||
} else {
|
||||
VectrasApp.writeToFile(_filelist.get((int)(_startRepeat)), "/vmID.txt", VMManager.idGenerator());
|
||||
|
|
@ -289,7 +297,7 @@ public class VMManager {
|
|||
if (_startRepeat == _filelist.size()) {
|
||||
if (!_result.isEmpty()) {
|
||||
if (VectrasApp.checkJSONIsNormalFromString("[" + _result + "]")) {
|
||||
if (VectrasApp.isFileExists(AppConfig.romsdatajson)) {
|
||||
if (isFileExists(AppConfig.romsdatajson)) {
|
||||
if (VectrasApp.checkJSONIsNormal(AppConfig.romsdatajson)) {
|
||||
String _JSONcontent = VectrasApp.readFile(AppConfig.romsdatajson);
|
||||
String _JSONcontentnew = _JSONcontent.replaceAll("]", _result + "]");
|
||||
|
|
@ -317,6 +325,77 @@ public class VMManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static void startFixRomsDataJson() {
|
||||
int _startRepeat = 0;
|
||||
String _resulttemp ="";
|
||||
String _result ="";
|
||||
restoredVMs = 0;
|
||||
ArrayList<String> _filelist = new ArrayList<>();
|
||||
VectrasApp.listDir(AppConfig.vmFolder, _filelist);
|
||||
if (!_filelist.isEmpty()) {
|
||||
for (int _repeat = 0; _repeat < (int)(_filelist.size()); _repeat++) {
|
||||
if (_startRepeat < _filelist.size()) {
|
||||
if (isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.txt")) {
|
||||
if (isFileExists(_filelist.get((int)(_startRepeat)) + "/rom-data.json")) {
|
||||
if (VectrasApp.checkJSONMapIsNormalFromString(VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json"))) {
|
||||
if (_resulttemp.contains("}")) {
|
||||
_resulttemp += "," + VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json");
|
||||
} else {
|
||||
_resulttemp = VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json");
|
||||
}
|
||||
if (VectrasApp.checkJSONIsNormalFromString(VectrasApp.readFile(AppConfig.maindirpath + "/roms-data.json").replaceAll("]", _resulttemp + "]"))) {
|
||||
if (_result.contains("}")) {
|
||||
_result += "," + VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json");
|
||||
} else {
|
||||
_result = VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json");
|
||||
}
|
||||
restoredVMs++;
|
||||
} else if (VectrasApp.checkJSONIsNormalFromString(VectrasApp.readFile(AppConfig.maindirpath + "/roms-data.json").replaceAll("]", "," + _resulttemp + "]"))) {
|
||||
if (_result.contains("}")) {
|
||||
_result += "," + VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json");
|
||||
} else {
|
||||
_result = "," + VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/rom-data.json");
|
||||
}
|
||||
restoredVMs++;
|
||||
} else {
|
||||
Log.i("CqcmActivity", VectrasApp.readFile(AppConfig.maindirpath + "/roms-data.json").replaceAll("]", _resulttemp + "]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_startRepeat++;
|
||||
if (_startRepeat == _filelist.size()) {
|
||||
if (!_result.isEmpty()) {
|
||||
if (VectrasApp.checkJSONIsNormalFromString("[" + _result + "]")) {
|
||||
if (isFileExists(AppConfig.romsdatajson)) {
|
||||
if (VectrasApp.checkJSONIsNormal(AppConfig.romsdatajson)) {
|
||||
String _JSONcontent = VectrasApp.readFile(AppConfig.romsdatajson);
|
||||
String _JSONcontentnew = _JSONcontent.replaceAll("]", _result + "]");
|
||||
if (VectrasApp.checkJSONIsNormalFromString(_JSONcontentnew.replaceAll("u003d", "="))) {
|
||||
VectrasApp.writeToFile(AppConfig.maindirpath, "roms-data.json", _JSONcontentnew.replaceAll("u003d", "="));
|
||||
} else {
|
||||
restoredVMs = 0;
|
||||
}
|
||||
} else {
|
||||
restoredVMs = 0;
|
||||
}
|
||||
} else {
|
||||
VectrasApp.writeToFile(AppConfig.maindirpath, "roms-data.json", "[" + _result + "]");
|
||||
}
|
||||
} else {
|
||||
restoredVMs = 0;
|
||||
}
|
||||
} else {
|
||||
restoredVMs = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void enableVMID(String _vmID) {
|
||||
if (_vmID.isEmpty())
|
||||
return;
|
||||
|
|
@ -326,7 +405,7 @@ public class VMManager {
|
|||
if (!_filelist.isEmpty()) {
|
||||
for (int _repeat = 0; _repeat < (int)(_filelist.size()); _repeat++) {
|
||||
if (_startRepeat < _filelist.size()) {
|
||||
if (VectrasApp.isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt")) {
|
||||
if (isFileExists(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt")) {
|
||||
if (VectrasApp.readFile(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt").equals(_vmID)) {
|
||||
VectrasApp.moveAFile(_filelist.get((int)(_startRepeat)) + "/vmID.old.txt", _filelist.get((int)(_startRepeat)) + "/vmID.txt");
|
||||
}
|
||||
|
|
@ -337,6 +416,31 @@ public class VMManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static void movetoRecycleBin() {
|
||||
File vDir = new File(AppConfig.recyclebin);
|
||||
if (!vDir.exists()) {
|
||||
if (!vDir.mkdirs()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
finalJson = VectrasApp.readFile(AppConfig.romsdatajson);
|
||||
if (!finalJson.isEmpty()) {
|
||||
int _startRepeat = 0;
|
||||
ArrayList<String> _filelist = new ArrayList<>();
|
||||
VectrasApp.listDir(AppConfig.vmFolder, _filelist);
|
||||
if (!_filelist.isEmpty()) {
|
||||
for (int _repeat = 0; _repeat < (int)(_filelist.size()); _repeat++) {
|
||||
if (_startRepeat < _filelist.size()) {
|
||||
if (!finalJson.contains(Uri.parse(_filelist.get((int) (_startRepeat))).getLastPathSegment())) {
|
||||
VectrasApp.moveAFile(_filelist.get((int) (_startRepeat)), AppConfig.recyclebin + Uri.parse(_filelist.get((int) (_startRepeat))).getLastPathSegment());
|
||||
}
|
||||
}
|
||||
_startRepeat++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String quickScanDiskFileInFolder(String _foderpath) {
|
||||
if (!_foderpath.isEmpty()) {
|
||||
int _startRepeat = 0;
|
||||
|
|
@ -397,20 +501,117 @@ public class VMManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static void setArch(String _arch, Context _context) {
|
||||
public static void setArch(String _arch, Activity _activity) {
|
||||
switch (_arch) {
|
||||
case "I386":
|
||||
MainSettingsManager.setArch(MainActivity.activity, "I386");
|
||||
MainSettingsManager.setArch(_activity, "I386");
|
||||
break;
|
||||
case "ARM64":
|
||||
MainSettingsManager.setArch(MainActivity.activity, "ARM64");
|
||||
MainSettingsManager.setArch(_activity, "ARM64");
|
||||
break;
|
||||
case "PPC":
|
||||
MainSettingsManager.setArch(MainActivity.activity, "PPC");
|
||||
MainSettingsManager.setArch(_activity, "PPC");
|
||||
break;
|
||||
default:
|
||||
MainSettingsManager.setArch(MainActivity.activity, "X86_64");
|
||||
MainSettingsManager.setArch(_activity, "X86_64");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isExecutedCommandError(String _command, String _result, Activity _activity) {
|
||||
if (!_command.contains("qemu-system") || _result.contains("Killed"))
|
||||
return false;
|
||||
//Error code: PROOT_IS_MISSING_0
|
||||
if (_result.contains("proot\": error=2,")) {
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(_activity, R.style.MainDialogTheme).create();
|
||||
alertDialog.setTitle(_activity.getResources().getString(R.string.problem_has_been_detected));
|
||||
alertDialog.setMessage(_activity.getResources().getString(R.string.error_PROOT_IS_MISSING_0));
|
||||
alertDialog.setCancelable(false);
|
||||
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, _activity.getResources().getString(R.string.continuetext), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
MainActivity.isActivate = false;
|
||||
VectrasApp.deleteDirectory(_activity.getFilesDir().getAbsolutePath() + "/data");
|
||||
VectrasApp.deleteDirectory(_activity.getFilesDir().getAbsolutePath() + "/distro");
|
||||
VectrasApp.deleteDirectory(_activity.getFilesDir().getAbsolutePath() + "/usr");
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(_activity, SplashActivity.class);
|
||||
_activity.startActivity(intent);
|
||||
_activity.finish();
|
||||
}
|
||||
});
|
||||
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, _activity.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
});
|
||||
alertDialog.show();
|
||||
return true;
|
||||
} else if (_result.contains(") exists") && _result.contains("drive with bus")) {
|
||||
//Error code: DRIVE_INDEX_0_EXISTS
|
||||
VectrasApp.oneDialog(_activity.getResources().getString(R.string.problem_has_been_detected), _activity.getResources().getString(R.string.error_DRIVE_INDEX_0_EXISTS), true, false, _activity);
|
||||
return true;
|
||||
} else if (_result.contains("gtk initialization failed") || _result.contains("x11 not available")) {
|
||||
//Error code: X11_NOT_AVAILABLE
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(_activity, R.style.MainDialogTheme).create();
|
||||
alertDialog.setTitle(_activity.getResources().getString(R.string.problem_has_been_detected));
|
||||
alertDialog.setMessage(_activity.getResources().getString(R.string.error_X11_NOT_AVAILABLE));
|
||||
alertDialog.setCancelable(false);
|
||||
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, _activity.getResources().getString(R.string.continuetext), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
MainSettingsManager.setVmUi(_activity, "VNC");
|
||||
VectrasApp.oneDialog(_activity.getResources().getString(R.string.done), _activity.getResources().getString(R.string.switched_to_VNC), true, false, _activity);
|
||||
}
|
||||
});
|
||||
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, _activity.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
});
|
||||
alertDialog.show();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isRomsDataJsonNormal(Boolean _needfix, Context _context) {
|
||||
if (isFileExists(AppConfig.romsdatajson)) {
|
||||
if (!VectrasApp.checkJSONIsNormal(AppConfig.romsdatajson)) {
|
||||
if (_needfix) {
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(_context, R.style.MainDialogTheme).create();
|
||||
alertDialog.setTitle(_context.getResources().getString(R.string.oops));
|
||||
alertDialog.setMessage(_context.getResources().getString(R.string.need_fix_json_before_create));
|
||||
alertDialog.setCancelable(true);
|
||||
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, _context.getResources().getString(R.string.continuetext), (dialog, which) -> {
|
||||
VectrasApp.moveAFile(AppConfig.maindirpath + "roms-data.json", AppConfig.maindirpath + "roms-data.old.json");
|
||||
VectrasApp.writeToFile(AppConfig.maindirpath, "roms-data.json", "[]");
|
||||
startFixRomsDataJson();
|
||||
fixRomsDataJsonResult(_context);
|
||||
});
|
||||
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, _context.getResources().getString(R.string.cancel), (dialog, which) -> {
|
||||
|
||||
});
|
||||
alertDialog.show();
|
||||
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
VectrasApp.writeToFile(AppConfig.maindirpath, "roms-data.json", "[]");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void fixRomsDataJsonResult(Context _context) {
|
||||
if (restoredVMs == 0) {
|
||||
VectrasApp.oneDialogWithContext(_context.getString(R.string.done), _context.getString(R.string.roms_data_json_fixed_unsuccessfully),true, _context);
|
||||
} else {
|
||||
VectrasApp.oneDialogWithContext(_context.getString(R.string.done), _context.getString(R.string.roms_data_json_fixed_successfully),true, _context);
|
||||
}
|
||||
MainActivity.loadDataVbi();
|
||||
MainActivity.mdatasize2();
|
||||
movetoRecycleBin();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -718,6 +718,21 @@ public class VectrasApp extends Application {
|
|||
alertDialog.show();
|
||||
}
|
||||
|
||||
public static void oneDialogWithContext(String _title, String _message, boolean _cancel, Context _context) {
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(_context, R.style.MainDialogTheme).create();
|
||||
alertDialog.setTitle(_title);
|
||||
alertDialog.setMessage(_message);
|
||||
if (!_cancel) {
|
||||
alertDialog.setCancelable(false);
|
||||
}
|
||||
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
});
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
public static void prepareDataForAppConfig(Activity _activity) {
|
||||
AppConfig.vectrasVersion = "2.9.4";
|
||||
AppConfig.vectrasWebsite = "https://vectras.vercel.app/";
|
||||
|
|
@ -741,6 +756,7 @@ public class VectrasApp extends Application {
|
|||
AppConfig.downloadsFolder = AppConfig.maindirpath + "Downloads/";
|
||||
AppConfig.romsdatajson = AppConfig.maindirpath + "roms-data.json";
|
||||
AppConfig.vmFolder = AppConfig.maindirpath + "roms/";
|
||||
AppConfig.recyclebin = AppConfig.maindirpath + "recyclebin/";
|
||||
}
|
||||
|
||||
public static PackageInfo getAppInfo(Context context) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.net.InetAddress;
|
|||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.vectras.qemu.MainSettingsManager;
|
||||
import com.vectras.qemu.MainVNCActivity;
|
||||
|
|
@ -29,6 +30,7 @@ import com.vectras.vm.MainActivity;
|
|||
import com.vectras.vm.MainService;
|
||||
import com.vectras.vm.R;
|
||||
import com.vectras.vm.SplashActivity;
|
||||
import com.vectras.vm.VMManager;
|
||||
import com.vectras.vm.VectrasApp;
|
||||
|
||||
public class Terminal {
|
||||
|
|
@ -62,59 +64,8 @@ public class Terminal {
|
|||
}
|
||||
|
||||
private void showDialog(String message, Activity activity, String usercommand) {
|
||||
if (!usercommand.contains("qemu-system") || message.contains("Killed"))
|
||||
if (VMManager.isExecutedCommandError(usercommand, message, activity))
|
||||
return;
|
||||
//Error code: PROOT_IS_MISSING_0
|
||||
if (message.contains("proot\": error=2,")) {
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
|
||||
alertDialog.setTitle(activity.getResources().getString(R.string.problem_has_been_detected));
|
||||
alertDialog.setMessage(activity.getResources().getString(R.string.error_PROOT_IS_MISSING_0));
|
||||
alertDialog.setCancelable(false);
|
||||
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getResources().getString(R.string.continuetext), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
MainActivity.isActivate = false;
|
||||
VectrasApp.deleteDirectory(activity.getFilesDir().getAbsolutePath() + "/data");
|
||||
VectrasApp.deleteDirectory(activity.getFilesDir().getAbsolutePath() + "/distro");
|
||||
VectrasApp.deleteDirectory(activity.getFilesDir().getAbsolutePath() + "/usr");
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(activity, SplashActivity.class);
|
||||
activity.startActivity(intent);
|
||||
activity.finish();
|
||||
return;
|
||||
}
|
||||
});
|
||||
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, activity.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
});
|
||||
alertDialog.show();
|
||||
return;
|
||||
} else if (message.contains(") exists") && message.contains("drive with bus")) {
|
||||
//Error code: DRIVE_INDEX_0_EXISTS
|
||||
VectrasApp.oneDialog(activity.getResources().getString(R.string.problem_has_been_detected), activity.getResources().getString(R.string.error_DRIVE_INDEX_0_EXISTS), true, false, activity);
|
||||
return;
|
||||
} else if (message.contains("gtk initialization failed") || message.contains("x11 not available")) {
|
||||
//Error code: X11_NOT_AVAILABLE
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
|
||||
alertDialog.setTitle(activity.getResources().getString(R.string.problem_has_been_detected));
|
||||
alertDialog.setMessage(activity.getResources().getString(R.string.error_X11_NOT_AVAILABLE));
|
||||
alertDialog.setCancelable(false);
|
||||
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getResources().getString(R.string.continuetext), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
MainSettingsManager.setVmUi(activity, "VNC");
|
||||
VectrasApp.oneDialog(activity.getResources().getString(R.string.done), activity.getResources().getString(R.string.switched_to_VNC), true, false, activity);
|
||||
return;
|
||||
}
|
||||
});
|
||||
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, activity.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
});
|
||||
alertDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create();
|
||||
dialog.setTitle("Execution Result");
|
||||
|
|
@ -144,9 +95,9 @@ public class Terminal {
|
|||
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
|
||||
// Adjust these environment variables as necessary for your app
|
||||
String filesDir = context.getFilesDir().getAbsolutePath();
|
||||
String filesDir = Objects.requireNonNull(context.getFilesDir().getAbsolutePath());
|
||||
|
||||
File tmpDir = new File(context.getFilesDir(), "usr/tmp");
|
||||
File tmpDir = new File(Objects.requireNonNull(context.getFilesDir()), "usr/tmp");
|
||||
|
||||
// Setup environment for the PRoot qemuProcess
|
||||
processBuilder.environment().put("PROOT_TMP_DIR", tmpDir.getAbsolutePath());
|
||||
|
|
|
|||
10
app/src/main/res/drawable/memory_alt_24px.xml
Normal file
10
app/src/main/res/drawable/memory_alt_24px.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M240,600L320,600L320,360L240,360L240,600ZM440,600L520,600L520,360L440,360L440,600ZM640,600L720,600L720,360L640,360L640,600ZM160,680L800,680Q800,680 800,680Q800,680 800,680L800,280Q800,280 800,280Q800,280 800,280L160,280Q160,280 160,280Q160,280 160,280L160,680Q160,680 160,680Q160,680 160,680ZM160,680Q160,680 160,680Q160,680 160,680L160,280Q160,280 160,280Q160,280 160,280L160,280Q160,280 160,280Q160,280 160,280L160,680Q160,680 160,680Q160,680 160,680ZM200,840L200,760L160,760Q127,760 103.5,736.5Q80,713 80,680L80,280Q80,247 103.5,223.5Q127,200 160,200L200,200L200,120L280,120L280,200L440,200L440,120L520,120L520,200L680,200L680,120L760,120L760,200L800,200Q833,200 856.5,223.5Q880,247 880,280L880,680Q880,713 856.5,736.5Q833,760 800,760L760,760L760,840L680,840L680,760L520,760L520,840L440,840L440,760L280,760L280,840L200,840Z"/>
|
||||
</vector>
|
||||
|
|
@ -312,7 +312,7 @@
|
|||
<string name="cpu_not_support_64">نظام Android أو وحدة المعالجة المركزية على جهازك لا تدعم 64 بت، مما يعني أن VM سيكون أداؤه ضعيفًا وغير مستقر عند التشغيل.</string>
|
||||
<string name="an_error_occurred_with_the_vm_list_data">حدث خطأ في بيانات قائمة الآلات الافتراضية. هل تريد حذف الكل؟</string>
|
||||
<string name="delete_all">حذف الكل</string>
|
||||
<string name="need_fix_json_before_create">حدث خطأ في بيانات قائمة الآلات الافتراضية ولا يمكن إنشاء آلة افتراضية جديدة في هذا الوقت. لإنشاء آلة افتراضية جديدة، تحتاج إلى حذف جميع بيانات قائمة الآلات الافتراضية. هل تريد حذف الكل؟</string>
|
||||
<string name="need_fix_json_before_create">بيانات قائمة الأجهزة الافتراضية تالفة وتحتاج إلى الإصلاح. هل تريد أن تبدأ الإصلاحات الآن؟</string>
|
||||
<string name="problem_has_been_detected">تم اكتشاف مشكلة</string>
|
||||
<string name="you_have_not_added_any_storage_devices">لم تقم بإضافة أي أجهزة تخزين لبدء الاستخدام أو تثبيت نظام تشغيل. هل تريد متابعة الإنشاء؟</string>
|
||||
<string name="continuetext">استمر</string>
|
||||
|
|
@ -379,6 +379,9 @@
|
|||
<string name="select">يختار</string>
|
||||
<string name="use_default_bios_uefi">استخدم BIOS/UEFI الافتراضي</string>
|
||||
<string name="use_local_time">استخدم التوقيت المحلي</string>
|
||||
<string name="roms_data_json_fixed_successfully">تم إصلاح المشكلة بنجاح. ولكن قد تختفي بعض الأجهزة الافتراضية ويتم نقلها إلى مجلد سلة المحذوفات.</string>
|
||||
<string name="roms_data_json_fixed_unsuccessfully">فشلت عملية الإصلاح ولم تعد كل الأجهزة الافتراضية الخاصة بك تظهر في قائمة الأجهزة الافتراضية. لقد تم نقلها إلى مجلد سلة المحذوفات.</string>
|
||||
<string name="use_memory_overcommit">استخدم ذاكرة overcommit</string>
|
||||
|
||||
<!--======================TERMUX STRINGS====================-->
|
||||
<string name="application_name">Vterm</string>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<string name="app_name">Vectras VM</string>
|
||||
|
||||
<!--======================VECTRAS STRINGS====================-->
|
||||
<string name="app_version" translatable="false">v2.9.4 (Genoise)</string>
|
||||
<string name="app_version" translatable="false">v2.9.4 (Honey Cake)</string>
|
||||
<string name="qemu_version" translatable="false">Stable</string>
|
||||
<string name="str_home">Home</string>
|
||||
<string name="str_logger">Logger</string>
|
||||
|
|
@ -314,7 +314,7 @@
|
|||
<string name="cpu_not_support_64">The Android OS or CPU on your device does not support 64 bit, which means the VM will have poor performance and be unstable when running.</string>
|
||||
<string name="an_error_occurred_with_the_vm_list_data">An error occurred with the virtual machine list data. Do you want to delete all?</string>
|
||||
<string name="delete_all">Delete all</string>
|
||||
<string name="need_fix_json_before_create">An error occurred with the virtual machine list data and a new virtual machine cannot be created at this time. To create a new virtual machine, you need to delete all virtual machine list data. Do you want to delete all?</string>
|
||||
<string name="need_fix_json_before_create">The virtual machine list data is corrupted and needs to be repaired. Do you want to start repairing now?</string>
|
||||
<string name="problem_has_been_detected">Problem has been detected</string>
|
||||
<string name="you_have_not_added_any_storage_devices">You have not added any storage devices to start using or install an operating system. Do you want to continue creating?</string>
|
||||
<string name="continuetext">Continue</string>
|
||||
|
|
@ -381,6 +381,9 @@
|
|||
<string name="select">Select</string>
|
||||
<string name="use_default_bios_uefi">Use default BIOS/UEFI</string>
|
||||
<string name="use_local_time">Use local time</string>
|
||||
<string name="roms_data_json_fixed_successfully">Fixed successfully. But some virtual machines may disappear and they were moved to the Recycle bin folder.</string>
|
||||
<string name="roms_data_json_fixed_unsuccessfully">The fix fails and all your virtual machines no longer appear in the virtual machine list. They were moved to the Recycle bin folder.</string>
|
||||
<string name="use_memory_overcommit">Use memory overcommit</string>
|
||||
|
||||
|
||||
<!--======================TERMUX STRINGS====================-->
|
||||
|
|
|
|||
|
|
@ -93,6 +93,13 @@
|
|||
android:key="useDefaultBios"
|
||||
android:title="@string/use_default_bios_uefi"
|
||||
app:icon="@drawable/hard_disk_24px" />
|
||||
<SwitchPreferenceCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:defaultValue="true"
|
||||
android:key="useMemoryOvercommit"
|
||||
android:title="@string/use_memory_overcommit"
|
||||
app:icon="@drawable/memory_alt_24px" />
|
||||
<SwitchPreferenceCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
20
shell-loader/release/output-metadata.json
Normal file
20
shell-loader/release/output-metadata.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "com.vectras.vm",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 0,
|
||||
"versionName": "",
|
||||
"outputFile": "loader.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File"
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"versionCode":"20",
|
||||
"versionName":"v2.9.4,v2.9.4-jellybean,v2.9.4-lemonmeringuepie,v2.9.4-newyorkcheesecake,v2.9.4-oatmealcookie,v2.9.4-apricottart,v2.9.4-biscuit,v2.9.4-croissant,v2.9.4-danishpastry,v2.9.4-ecclescake,v2.9.4-frenchtoast,v2.9.4-genoise",
|
||||
"versionName":"v2.9.4,v2.9.4-jellybean,v2.9.4-lemonmeringuepie,v2.9.4-newyorkcheesecake,v2.9.4-oatmealcookie,v2.9.4-apricottart,v2.9.4-biscuit,v2.9.4-croissant,v2.9.4-danishpastry,v2.9.4-ecclescake,v2.9.4-frenchtoast,v2.9.4-genoise,v2.9.4-honeycake",
|
||||
"size": "60 MB",
|
||||
"url": "https://github.com/xoureldeen/Vectras-VM-Android/releases/v2.9.4",
|
||||
"Message": "<h2>v2.9.4</h2><ul><li>Now you can change locale in app settings.</li><li>Now you can access x11 settings.</li><li>New button to set full screen in x11 display.</li><li>Enhanced UI.</li><li>Chinese language by <a href=\"https://github.com/adk23333\" target=\"_blank\">@adk23333</a></li></ul><br><br>New updates are live!",
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
{
|
||||
"rom_name":"PonyOS",
|
||||
"rom_icon":"https://ponyos.org/assets/img/logo-new.png",
|
||||
"rom_url":"https://www.mediafire.com/file/fzcjieuuw8uxb2m/PonyOS.cvbi/file",
|
||||
"rom_url":"https://archive.org/download/blackberry-os/PonyOS.cvbi",
|
||||
"rom_path":"PonyOS.cvbi",
|
||||
"rom_avail":true,
|
||||
"rom_size":"6MB",
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
{
|
||||
"rom_name":"BSDeviant",
|
||||
"rom_icon":"https://archiveos.org/wp-content/uploads/2019/03/bsdeviant.webp",
|
||||
"rom_url":"https://www.mediafire.com/file/y6u4cg0t435scbf/BSDeviant.cvbi/file",
|
||||
"rom_url":"https://archive.org/download/blackberry-os/BSDeviant.cvbi",
|
||||
"rom_path":"BSDeviant.cvbi",
|
||||
"rom_avail":true,
|
||||
"rom_size":"81MB",
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
{
|
||||
"rom_name":"Anonym.OS",
|
||||
"rom_icon":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSQlezHOdq-S2UjgmEoIcVxBA4jlDO6bPGsLA&usqp=CAU",
|
||||
"rom_url":"https://www.mediafire.com/file/laparebxxunsztk/Anonym.OS.cvbi/file",
|
||||
"rom_url":"https://archive.org/download/blackberry-os/Anonym.OS.cvbi",
|
||||
"rom_path":"Anonym.OS.cvbi",
|
||||
"rom_avail":true,
|
||||
"rom_size":"196MB",
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
{
|
||||
"rom_name":"Android 3.2",
|
||||
"rom_icon":"https://static.wikia.nocookie.net/android/images/d/d2/Honeycomb-logo2-e1298681115525.png/revision/latest?cb=20111216204245",
|
||||
"rom_url":"https://www.mediafire.com/file/4rj9x99jmog6u4d/Android_3.2.cvbi/file",
|
||||
"rom_url":"https://archive.org/download/blackberry-os/Android%203.2.cvbi",
|
||||
"rom_path":"Android 3.2.cvbi",
|
||||
"rom_avail":true,
|
||||
"rom_size":"180MB",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue