Compare commits

...

1 Commits

Author SHA1 Message Date
OMGeeky
62aa912517 Import Export 2022-10-09 01:53:14 +02:00
8 changed files with 1740 additions and 1187 deletions

View File

@@ -1,16 +1,26 @@
package com.gpl.rpg.AndorsTrail.activity; package com.gpl.rpg.AndorsTrail.activity;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import android.Manifest; import android.Manifest;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.ClipData;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v4.provider.DocumentFile;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -22,17 +32,24 @@ import android.widget.Toast;
import com.gpl.rpg.AndorsTrail.AndorsTrailApplication; import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
import com.gpl.rpg.AndorsTrail.AndorsTrailPreferences; import com.gpl.rpg.AndorsTrail.AndorsTrailPreferences;
import com.gpl.rpg.AndorsTrail.R; import com.gpl.rpg.AndorsTrail.R;
import com.gpl.rpg.AndorsTrail.controller.Constants;
import com.gpl.rpg.AndorsTrail.model.ModelContainer; import com.gpl.rpg.AndorsTrail.model.ModelContainer;
import com.gpl.rpg.AndorsTrail.resource.tiles.TileManager; import com.gpl.rpg.AndorsTrail.resource.tiles.TileManager;
import com.gpl.rpg.AndorsTrail.savegames.Savegames; import com.gpl.rpg.AndorsTrail.savegames.Savegames;
import com.gpl.rpg.AndorsTrail.savegames.Savegames.FileHeader; import com.gpl.rpg.AndorsTrail.savegames.Savegames.FileHeader;
import com.gpl.rpg.AndorsTrail.util.AndroidStorage;
import com.gpl.rpg.AndorsTrail.util.ThemeHelper; import com.gpl.rpg.AndorsTrail.util.ThemeHelper;
import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory; import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory;
public final class LoadSaveActivity extends AndorsTrailBaseActivity implements OnClickListener { public final class LoadSaveActivity extends AndorsTrailBaseActivity implements OnClickListener {
private boolean isLoading = true; private boolean isLoading = true;
//region special slot numbers
private static final int SLOT_NUMBER_CREATE_NEW_SLOT = -1; private static final int SLOT_NUMBER_CREATE_NEW_SLOT = -1;
public static final int SLOT_NUMBER_EXPORT_SAVEGAMES = -2;
public static final int SLOT_NUMBER_IMPORT_SAVEGAMES = -3;
public static final int SLOT_NUMBER_IMPORT_WORLDMAP = -4;
private static final int SLOT_NUMBER_FIRST_SLOT = 1; private static final int SLOT_NUMBER_FIRST_SLOT = 1;
//endregion
private ModelContainer model; private ModelContainer model;
private TileManager tileManager; private TileManager tileManager;
private AndorsTrailPreferences preferences; private AndorsTrailPreferences preferences;
@@ -70,6 +87,17 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
ViewGroup newSlotContainer = (ViewGroup) findViewById(R.id.loadsave_save_to_new_slot_container); ViewGroup newSlotContainer = (ViewGroup) findViewById(R.id.loadsave_save_to_new_slot_container);
Button createNewSlot = (Button) findViewById(R.id.loadsave_save_to_new_slot); Button createNewSlot = (Button) findViewById(R.id.loadsave_save_to_new_slot);
Button exportSaves = (Button) findViewById(R.id.loadsave_export_save);
Button importSaves = (Button) findViewById(R.id.loadsave_import_save);
Button importWorldmap = (Button) findViewById(R.id.loadsave_import_worldmap);
exportSaves.setTag(SLOT_NUMBER_EXPORT_SAVEGAMES);
importSaves.setTag(SLOT_NUMBER_IMPORT_SAVEGAMES);
importWorldmap.setTag(SLOT_NUMBER_IMPORT_WORLDMAP);
ViewGroup exportImportContainer = (ViewGroup) findViewById(R.id.loadsave_export_import_save_container);
addSavegameSlotButtons(slotList, params, Savegames.getUsedSavegameSlots(this)); addSavegameSlotButtons(slotList, params, Savegames.getUsedSavegameSlots(this));
checkAndRequestPermissions(); checkAndRequestPermissions();
@@ -78,15 +106,22 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
createNewSlot.setTag(SLOT_NUMBER_CREATE_NEW_SLOT); createNewSlot.setTag(SLOT_NUMBER_CREATE_NEW_SLOT);
createNewSlot.setOnClickListener(this); createNewSlot.setOnClickListener(this);
newSlotContainer.setVisibility(View.VISIBLE); newSlotContainer.setVisibility(View.VISIBLE);
exportImportContainer.setVisibility(View.GONE);
} else { } else {
newSlotContainer.setVisibility(View.GONE); newSlotContainer.setVisibility(View.GONE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
exportSaves.setOnClickListener(this);
importSaves.setOnClickListener(this);
importWorldmap.setOnClickListener(this);
exportImportContainer.setVisibility(View.VISIBLE);
}
} }
} }
private static final int READ_EXTERNAL_STORAGE_REQUEST = 1; private static final int READ_EXTERNAL_STORAGE_REQUEST = 1;
private static final int WRITE_EXTERNAL_STORAGE_REQUEST = 2; private static final int WRITE_EXTERNAL_STORAGE_REQUEST = 2;
@TargetApi(23)
private void checkAndRequestPermissions() { private void checkAndRequestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
@@ -135,24 +170,39 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
} }
} }
public void loadsave(int slot) {
private void completeLoadSaveActivity(int slot) {
Intent i = new Intent();
if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) { if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) {
List<Integer> usedSlots = Savegames.getUsedSavegameSlots(this); List<Integer> usedSlots = Savegames.getUsedSavegameSlots(this);
if (usedSlots.isEmpty()) slot = SLOT_NUMBER_FIRST_SLOT; if (usedSlots.isEmpty())
slot = SLOT_NUMBER_FIRST_SLOT;
else slot = Collections.max(usedSlots) + 1; else slot = Collections.max(usedSlots) + 1;
} } else if (slot == SLOT_NUMBER_EXPORT_SAVEGAMES
if (slot < SLOT_NUMBER_FIRST_SLOT) slot = SLOT_NUMBER_FIRST_SLOT; || slot == SLOT_NUMBER_IMPORT_SAVEGAMES
|| slot == SLOT_NUMBER_IMPORT_WORLDMAP) {
i.putExtra("import_export", true);
} else if (slot < SLOT_NUMBER_FIRST_SLOT)
slot = SLOT_NUMBER_FIRST_SLOT;
Intent i = new Intent();
i.putExtra("slot", slot); i.putExtra("slot", slot);
setResult(Activity.RESULT_OK, i); setResult(Activity.RESULT_OK, i);
LoadSaveActivity.this.finish(); LoadSaveActivity.this.finish();
} }
private String getConfirmOverwriteQuestion(int slot) { private String getConfirmOverwriteQuestion(int slot) {
if (isLoading) return null; if (isLoading)
if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) return null; // if we're creating a new slot return null;
if (!Savegames.getSlotFile(slot, this).exists()) return null;
return getConfirmOverwriteQuestionIgnoringLoading(slot);
}
private String getConfirmOverwriteQuestionIgnoringLoading(int slot) {
if (slot == SLOT_NUMBER_CREATE_NEW_SLOT)
return null;//creating a new savegame
if (!Savegames.getSlotFile(slot, this).exists())
return null;//nothing in slot to overwrite
if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_ALWAYS) { if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_ALWAYS) {
return getString(R.string.loadsave_save_overwrite_confirmation_all); return getString(R.string.loadsave_save_overwrite_confirmation_all);
@@ -175,23 +225,389 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
public void onClick(View view) { public void onClick(View view) {
final int slot = (Integer) view.getTag(); final int slot = (Integer) view.getTag();
if (!isLoading && slot != SLOT_NUMBER_CREATE_NEW_SLOT && AndorsTrailApplication.CURRENT_VERSION == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch (slot) {
case SLOT_NUMBER_IMPORT_WORLDMAP:
clickImportWorldmap();
return;
case SLOT_NUMBER_IMPORT_SAVEGAMES:
clickImportSaveGames();
return;
case SLOT_NUMBER_EXPORT_SAVEGAMES:
clickExportSaveGames();
return;
}
}
if (!isLoading
&& slot != SLOT_NUMBER_CREATE_NEW_SLOT
&& AndorsTrailApplication.CURRENT_VERSION == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) {
if (!isOverwriteTargetInIncompatibleVersion(slot)) {
saveOrOverwriteSavegame(slot);
}
} else if (isLoading) {
loadSaveGame(slot);
} else {
saveOrOverwriteSavegame(slot);
}
}
private void saveOrOverwriteSavegame(int slot) {
final String message = getConfirmOverwriteQuestion(slot);
if (message != null) {
showConfirmoverwriteQuestion(slot, message);
} else {
completeLoadSaveActivity(slot);
}
}
private boolean isOverwriteTargetInIncompatibleVersion(int slot) {
final FileHeader header = Savegames.quickload(this, slot); final FileHeader header = Savegames.quickload(this, slot);
if (header != null && header.fileversion != AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) { if (header != null && header.fileversion != AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) {
final Dialog d = CustomDialogFactory.createErrorDialog(this, "Overwriting not allowed", "You are currently using a development version of Andor's trail. Overwriting a regular savegame is not allowed in development mode.");
CustomDialogFactory.show(d);
return true;
}
return false;
}
//region Imports/Exports
private void exportSaveGames(Intent data) {
Uri uri = data.getData();
Context context = getApplicationContext();
ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this).getContentResolver();
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
DocumentFile source = DocumentFile.fromFile(storageDir);
DocumentFile target = DocumentFile.fromTreeUri(context, uri);
if (target == null) {
return;
}
DocumentFile[] files = source.listFiles();
boolean hasExistingFiles = false;
for (DocumentFile file :
files) {
String fileName = file.getName();
if (fileName == null)
continue;
DocumentFile existingFile = target.findFile(fileName);
if (existingFile != null) {
hasExistingFiles = true;
break;
}
}
if (hasExistingFiles) {
showConfirmOverwriteByExportQuestion(resolver, target, files);
} else {
exportSaveGamesFolderContentToFolder(resolver, target, files);
}
}
private void exportSaveGamesFolderContentToFolder(ContentResolver resolver, DocumentFile target, DocumentFile[] files) {
for (DocumentFile file : files) {
String fileName = file.getName();
DocumentFile existingFile = target.findFile(fileName);
boolean hasExistingFile = existingFile != null && existingFile.exists();
if (file.isFile()) {
try {
if (hasExistingFile)
existingFile.delete();
AndroidStorage.copyDocumentFileToNewOrExistingFile(file, resolver, target);
} catch (IOException e) {
e.printStackTrace();
}
} else if (file.isDirectory()) {
DocumentFile targetWorlmap = existingFile;
//if the folder exists already, put the files in the existing folder. (should not happen because of check earlier)
if (!hasExistingFile)
//create a new folder for the worldmap-files
targetWorlmap = target.createDirectory(Constants.FILENAME_WORLDMAP_DIRECTORY);
if (targetWorlmap == null)//Unable to create worldmap folder for some reason
continue;
DocumentFile[] worldmapFiles = file.listFiles();
for (DocumentFile f : worldmapFiles) {
try {
AndroidStorage.copyDocumentFileToNewOrExistingFile(f, resolver, targetWorlmap);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
completeLoadSaveActivity(SLOT_NUMBER_EXPORT_SAVEGAMES);
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
private void importSaveGames(Intent data) {
Uri uri = data.getData();
ClipData uris = data.getClipData();
if (uri == null && uris == null) {
//no file was selected
return;
}
Context context = getApplicationContext();
ContentResolver resolver = context.getContentResolver();
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
DocumentFile appSavegameFolder = DocumentFile.fromFile(storageDir);
List<Uri> uriList = new ArrayList<>();
if (uri != null) {
uriList.add(uri);
} else {
for (int i = 0; i < uris.getItemCount(); i++)
uriList.add(uris.getItemAt(i).getUri());
}
importSaveGamesFromUris(context, resolver, appSavegameFolder, uriList);
}
private void importSaveGamesFromUris(Context context, ContentResolver resolver, DocumentFile appSavegameFolder, List<Uri> uriList) {
int count = uriList.size();
ArrayList<DocumentFile> alreadyExistingFiles = new ArrayList<>();
ArrayList<DocumentFile> newFiles = new ArrayList<>();
for (int i = 0; i < count; i++) {
Uri item = uriList.get(i);
DocumentFile itemFile = DocumentFile.fromSingleUri(context, item);
boolean fileAlreadyExists = getExistsSavegameInOwnFiles(itemFile, appSavegameFolder);
if (fileAlreadyExists)
alreadyExistingFiles.add(itemFile);
else
newFiles.add(itemFile);
}
if (alreadyExistingFiles.size() > 0) {
showConfirmOverwriteByImportQuestion(resolver, appSavegameFolder, alreadyExistingFiles, newFiles);
} else {
importSaveGames(resolver, appSavegameFolder, newFiles);
}
}
private void importSaveGames(ContentResolver resolver, DocumentFile appSavegameFolder, List<DocumentFile> saveFiles) {
for (DocumentFile file : saveFiles) {
int slot = getSlotFromSavegameFileName(file.getName());
importSaveGameFile(resolver, appSavegameFolder, file, slot);
}
}
private void completeSavegameImportAndCheckIfDone(List<Integer> importsNeedingConfirmation, int slot) {
importsNeedingConfirmation.remove((Object) slot);
if (importsNeedingConfirmation.isEmpty()) {
completeLoadSaveActivity(SLOT_NUMBER_IMPORT_SAVEGAMES);
}
}
private boolean getExistsSavegameInOwnFiles(DocumentFile savegameFile, DocumentFile appSavegameFolder) {
if (savegameFile == null)
return false;
DocumentFile foundFile = appSavegameFolder.findFile(Objects.requireNonNull(savegameFile.getName()));
return foundFile != null && foundFile.exists();
}
private int getSlotFromSavegameFileName(String fileName) {
if (fileName == null || !fileName.startsWith(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX)) {
//TODO: Maybe output a message that the file didn't have the right name?
return -1;
}
String slotStr = fileName.substring(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX.length());
int slot;
try {
slot = Integer.parseInt(slotStr);
return slot;
} catch (NumberFormatException e) {
//TODO: Maybe output a message that the file didn't have the right name?
return -1;
}
}
private void importSaveGameFile(ContentResolver resolver, DocumentFile appSavegameFolder, DocumentFile itemFile, int slot) {
String targetName = Savegames.getSlotFileName(slot);
DocumentFile targetFile = getOrCreateDocumentFile(appSavegameFolder, targetName);
if (targetFile == null || !targetName.equals(targetFile.getName())) {
showErrorImportingSaveGameUnknown();//TODO: maybe replace with a more specific error message
return;
}
try {
AndroidStorage.copyDocumentFile(itemFile, resolver, targetFile);
} catch (IOException e) {
showErrorImportingSaveGameUnknown();
e.printStackTrace();
}
}
private DocumentFile getOrCreateDocumentFile(DocumentFile folder, String targetName) {
DocumentFile targetFile = folder.findFile(targetName);//try finding the file
if (targetFile == null)//no file found, creating new one
targetFile = folder.createFile(Constants.NO_FILE_EXTENSION_MIME_TYPE, targetName);
return targetFile;
}
private void importWorldmap(Intent data) {
Uri uri = data.getData();
Context context = getApplicationContext();
ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this).getContentResolver();
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
DocumentFile storageFolder = DocumentFile.fromFile(storageDir);
DocumentFile ownWorldmapFolder = storageFolder.findFile(Constants.FILENAME_WORLDMAP_DIRECTORY);
if (ownWorldmapFolder == null) {
ownWorldmapFolder = storageFolder.createDirectory(Constants.FILENAME_WORLDMAP_DIRECTORY);
}
DocumentFile chosenFolder = DocumentFile.fromTreeUri(context, uri);
if (chosenFolder == null || !chosenFolder.isDirectory()) {
showErrorImportingWorldmapWrongDirectory();
return;
}
if (!Constants.FILENAME_WORLDMAP_DIRECTORY.equals(chosenFolder.getName())) {
//user did not select the worldmap folder directly
DocumentFile file = chosenFolder.findFile(Constants.FILENAME_WORLDMAP_DIRECTORY);
if (file == null || !file.isDirectory() || !Constants.FILENAME_WORLDMAP_DIRECTORY.equals(file.getName())) {
//could not find a worldmap folder in the users selection
showErrorImportingWorldmapWrongDirectory();
return;
}
chosenFolder = file;
}
DocumentFile[] files = chosenFolder.listFiles();
for (DocumentFile file : files) {
if (file.isFile()) {
try {
AndroidStorage.copyDocumentFileToNewOrExistingFile(file, resolver, ownWorldmapFolder);
} catch (IOException e) {
e.printStackTrace();
}
}
}
completeLoadSaveActivity(SLOT_NUMBER_IMPORT_WORLDMAP);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void clickExportSaveGames() {
startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(), -SLOT_NUMBER_EXPORT_SAVEGAMES);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void clickImportSaveGames() {
startActivityForResult(AndroidStorage.getNewSelectMultipleSavegameFilesIntent(), -SLOT_NUMBER_IMPORT_SAVEGAMES);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void clickImportWorldmap() {
startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(), -SLOT_NUMBER_IMPORT_WORLDMAP);
}
private void showConfirmOverwriteByExportQuestion(ContentResolver resolver, DocumentFile targetFolder, DocumentFile[] files) {
final Dialog d = CustomDialogFactory.createDialog(this, final Dialog d = CustomDialogFactory.createDialog(this,
"Overwriting not allowed", getString(R.string.loadsave_export_overwrite_confirmation_title),
getResources().getDrawable(android.R.drawable.ic_dialog_alert), getResources().getDrawable(android.R.drawable.ic_dialog_alert),
"You are currently using a development version of Andor's trail. Overwriting a regular savegame is not allowed in development mode.", getString(R.string.loadsave_export_overwrite_confirmation),
null, null,
true); true);
CustomDialogFactory.addDismissButton(d, android.R.string.ok);
CustomDialogFactory.addButton(d, android.R.string.yes, v -> exportSaveGamesFolderContentToFolder(resolver, targetFolder, files));
CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
}
private void showConfirmOverwriteByImportQuestion(ContentResolver resolver,
DocumentFile appSavegameFolder,
List<DocumentFile> alreadyExistingFiles,
List<DocumentFile> newFiles) {
final String title = getString(R.string.loadsave_import_overwrite_confirmation_title);
String message = getString(R.string.loadsave_import_overwrite_confirmation);
StringBuilder sb = new StringBuilder();
sb.append('\n');
int amount = alreadyExistingFiles.size();
Context context = AndorsTrailApplication.getApplicationFromActivity(this).getApplicationContext();
for (int i = 0; i < amount && i < 3; i++) {
DocumentFile alreadyExistingFile = alreadyExistingFiles.get(i);
String alreadyExistingFileName = alreadyExistingFile.getName();
FileHeader fileHeader = Savegames.quickload(context, getSlotFromSavegameFileName(alreadyExistingFileName));
sb.append('\n');
String fileHeaderDesription = "";
if (fileHeader != null)
fileHeaderDesription = fileHeader.describe();
sb.append(getString(R.string.loadsave_import_overwrite_confirmation_file_pattern, alreadyExistingFileName, fileHeaderDesription));
// sb.append(alreadyExistingFile.getName());
}
if (amount > 3) {
sb.append("\n...");
}
message = message + sb;
final Dialog d = CustomDialogFactory.createDialog(this,
title,
getResources().getDrawable(android.R.drawable.ic_dialog_alert),
message,
null,
true);
CustomDialogFactory.addButton(d, android.R.string.yes, v -> newFiles.addAll(alreadyExistingFiles));
CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.setDismissListener(d, dialog -> {
importSaveGames(resolver, appSavegameFolder, newFiles);
completeLoadSaveActivity(SLOT_NUMBER_IMPORT_SAVEGAMES);
});
CustomDialogFactory.show(d);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch (-requestCode) {
case SLOT_NUMBER_EXPORT_SAVEGAMES:
exportSaveGames(data);
return;
case SLOT_NUMBER_IMPORT_SAVEGAMES:
importSaveGames(data);
return;
case SLOT_NUMBER_IMPORT_WORLDMAP:
importWorldmap(data);
return; return;
} }
} }
}
if (isLoading) { //endregion
private void loadSaveGame(int slot) {
if (!Savegames.getSlotFile(slot, this).exists()) { if (!Savegames.getSlotFile(slot, this).exists()) {
showErrorLoadingEmptySlot(); showErrorLoadingEmptySlot();
} else { } else {
@@ -199,27 +615,31 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
if (header != null && !header.hasUnlimitedSaves) { if (header != null && !header.hasUnlimitedSaves) {
showSlotGetsDeletedOnLoadWarning(slot); showSlotGetsDeletedOnLoadWarning(slot);
} else { } else {
loadsave(slot); completeLoadSaveActivity(slot);
}
}
} else {
final String message = getConfirmOverwriteQuestion(slot);
if (message != null) {
showConfirmoverwriteQuestion(slot, message);
} else {
loadsave(slot);
} }
} }
} }
//region show Dialogs
private void showErrorImportingWorldmapWrongDirectory() {
final Dialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.loadsave_import_worldmap_unsuccessfull),
getString(R.string.loadsave_import_worldmap_unsuccessfull_wrong_directory));
CustomDialogFactory.show(d);
}
private void showErrorImportingSaveGameUnknown() {
final Dialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.loadsave_import_save_unsuccessfull),
getString(R.string.loadsave_import_save_unsuccessfull_unknown));
CustomDialogFactory.show(d);
}
private void showErrorLoadingEmptySlot() { private void showErrorLoadingEmptySlot() {
final Dialog d = CustomDialogFactory.createDialog(this, final Dialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.startscreen_error_loading_game), getString(R.string.startscreen_error_loading_game),
getResources().getDrawable(android.R.drawable.ic_dialog_alert), getString(R.string.startscreen_error_loading_empty_slot));
getString(R.string.startscreen_error_loading_empty_slot),
null,
true);
CustomDialogFactory.addDismissButton(d, android.R.string.ok);
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
} }
@@ -230,12 +650,7 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
getString(R.string.startscreen_attention_message_slot_gets_delete_on_load), getString(R.string.startscreen_attention_message_slot_gets_delete_on_load),
null, null,
true); true);
CustomDialogFactory.addButton(d, android.R.string.ok, new View.OnClickListener() { CustomDialogFactory.addButton(d, android.R.string.ok, v -> completeLoadSaveActivity(slot));
@Override
public void onClick(View v) {
loadsave(slot);
}
});
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
} }
@@ -250,13 +665,11 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
null, null,
true); true);
CustomDialogFactory.addButton(d, android.R.string.yes, new View.OnClickListener() { CustomDialogFactory.addButton(d, android.R.string.yes, v -> completeLoadSaveActivity(slot));
@Override
public void onClick(View v) {
loadsave(slot);
}
});
CustomDialogFactory.addDismissButton(d, android.R.string.no); CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
} }
//endregion
} }

View File

@@ -345,6 +345,8 @@ public class StartScreenActivity_MainMenu extends Fragment {
switch (requestCode) { switch (requestCode) {
case INTENTREQUEST_LOADGAME: case INTENTREQUEST_LOADGAME:
if (resultCode != Activity.RESULT_OK) break; if (resultCode != Activity.RESULT_OK) break;
final boolean wasImportOrExport = data.getBooleanExtra("import_export", false);
if (wasImportOrExport) break;
final int slot = data.getIntExtra("slot", 1); final int slot = data.getIntExtra("slot", 1);
continueGame(false, slot, null); continueGame(false, slot, null);
break; break;

View File

@@ -51,6 +51,10 @@ public final class Constants {
public static final String CHEAT_DETECTION_FOLDER = "dEAGyGE3YojqXjI3x4x7"; public static final String CHEAT_DETECTION_FOLDER = "dEAGyGE3YojqXjI3x4x7";
public static final String PASSIVE_ACHIEVEMENT_CHECK_PHRASE = "passive_achievement_check"; public static final String PASSIVE_ACHIEVEMENT_CHECK_PHRASE = "passive_achievement_check";
public static final String SAVEGAME_FILE_MIME_TYPE = "application/octet-stream";
public static final String WORLDMAP_FILE_MIME_TYPE = "image/png";
public static final String NO_FILE_EXTENSION_MIME_TYPE = "application/no_file_extension_mime_type";
public static final Random rnd = new Random(); public static final Random rnd = new Random();
public static int rollValue(final ConstRange r) { return rollValue(r.max, r.current); } public static int rollValue(final ConstRange r) { return rollValue(r.max, r.current); }
public static int rollValue(final ConstRange r, int bias) { return rollValue((r.max + 1) * 100 -1, r.current * 100 + bias)/100; } public static int rollValue(final ConstRange r, int bias) { return rollValue((r.max + 1) * 100 -1, r.current * 100 + bias)/100; }

View File

@@ -86,7 +86,7 @@ public final class Savegames {
private static void writeBackup(Context androidContext, byte[] savegame, String playerId) throws IOException { private static void writeBackup(Context androidContext, byte[] savegame, String playerId) throws IOException {
File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER);
if (!cheatDetectionFolder.exists()) cheatDetectionFolder.mkdir(); ensureDirExists(cheatDetectionFolder);
File backupFile = new File(cheatDetectionFolder, playerId + "X"); File backupFile = new File(cheatDetectionFolder, playerId + "X");
FileOutputStream fileOutputStream = new FileOutputStream(backupFile); FileOutputStream fileOutputStream = new FileOutputStream(backupFile);
fileOutputStream.write(savegame); fileOutputStream.write(savegame);
@@ -130,7 +130,7 @@ public final class Savegames {
private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException { private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException {
long savedVersionToCheck = 0; long savedVersionToCheck = 0;
File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER);
if (!cheatDetectionFolder.exists()) cheatDetectionFolder.mkdir(); ensureDirExists(cheatDetectionFolder);
File cheatDetectionFile = new File(cheatDetectionFolder, fh.playerId); File cheatDetectionFile = new File(cheatDetectionFolder, fh.playerId);
if (cheatDetectionFile.exists()) { if (cheatDetectionFile.exists()) {
FileInputStream fileInputStream = new FileInputStream(cheatDetectionFile); FileInputStream fileInputStream = new FileInputStream(cheatDetectionFile);
@@ -151,8 +151,7 @@ public final class Savegames {
final CheatDetection cheatDetection = new CheatDetection(dataInputStream); final CheatDetection cheatDetection = new CheatDetection(dataInputStream);
if (cheatDetection.savedVersion == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) { if (cheatDetection.savedVersion == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) {
savedVersionToCheck = DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED; savedVersionToCheck = DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED;
} } else if (cheatDetection.savedVersion > savedVersionToCheck) {
else if (cheatDetection.savedVersion > savedVersionToCheck) {
savedVersionToCheck = cheatDetection.savedVersion; savedVersionToCheck = cheatDetection.savedVersion;
} }
@@ -175,10 +174,20 @@ public final class Savegames {
return new FileOutputStream(getSlotFile(slot, androidContext)); return new FileOutputStream(getSlotFile(slot, androidContext));
} }
} }
private static void ensureSavegameDirectoryExists(Context context) { private static void ensureSavegameDirectoryExists(Context context) {
File dir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); File dir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
if (!dir.exists()) dir.mkdir(); ensureDirExists(dir);
} }
public static boolean ensureDirExists(File dir) {
if (!dir.exists()) {
boolean worked = dir.mkdir();
return worked;
}
return true;
}
private static FileInputStream getInputFile(Context androidContext, int slot) throws IOException { private static FileInputStream getInputFile(Context androidContext, int slot) throws IOException {
if (slot == SLOT_QUICKSAVE) { if (slot == SLOT_QUICKSAVE) {
return androidContext.openFileInput(Constants.FILENAME_SAVEGAME_QUICKSAVE); return androidContext.openFileInput(Constants.FILENAME_SAVEGAME_QUICKSAVE);
@@ -189,9 +198,16 @@ public final class Savegames {
public static File getSlotFile(int slot, Context context) { public static File getSlotFile(int slot, Context context) {
File root = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); File root = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
return new File(root, Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + slot); return getSlotFile(slot, root);
} }
public static File getSlotFile(int slot, File directory) {
return new File(directory, getSlotFileName(slot));
}
public static String getSlotFileName(int slot) {
return Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + slot;
}
public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException { public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException {
@@ -210,7 +226,8 @@ public final class Savegames {
public static LoadSavegameResult loadWorld(Resources res, WorldContext world, ControllerContext controllers, InputStream inState, FileHeader fh) throws IOException { public static LoadSavegameResult loadWorld(Resources res, WorldContext world, ControllerContext controllers, InputStream inState, FileHeader fh) throws IOException {
DataInputStream src = new DataInputStream(inState); DataInputStream src = new DataInputStream(inState);
final FileHeader header = new FileHeader(src, fh.skipIcon); final FileHeader header = new FileHeader(src, fh.skipIcon);
if (header.fileversion > AndorsTrailApplication.CURRENT_VERSION) return LoadSavegameResult.savegameIsFromAFutureVersion; if (header.fileversion > AndorsTrailApplication.CURRENT_VERSION)
return LoadSavegameResult.savegameIsFromAFutureVersion;
world.maps.readFromParcel(src, world, controllers, header.fileversion); world.maps.readFromParcel(src, world, controllers, header.fileversion);
world.model = new ModelContainer(src, world, controllers, header.fileversion); world.model = new ModelContainer(src, world, controllers, header.fileversion);
@@ -251,7 +268,7 @@ public final class Savegames {
private static void writeCheatCheck(Context androidContext, long savedVersion, String playerId) throws IOException { private static void writeCheatCheck(Context androidContext, long savedVersion, String playerId) throws IOException {
File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER);
if (!cheatDetectionFolder.exists()) cheatDetectionFolder.mkdir(); ensureDirExists(cheatDetectionFolder);
File cheatDetectionFile = new File(cheatDetectionFolder, playerId); File cheatDetectionFile = new File(cheatDetectionFolder, playerId);
FileOutputStream fileOutputStream = new FileOutputStream(cheatDetectionFile); FileOutputStream fileOutputStream = new FileOutputStream(cheatDetectionFile);
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
@@ -285,7 +302,7 @@ public final class Savegames {
Collections.sort(result); Collections.sort(result);
return result; return result;
} catch (Exception e) { } catch (Exception e) {
return null; return new ArrayList<Integer>();
} }
} }
@@ -307,7 +324,6 @@ public final class Savegames {
} }
public static final class FileHeader { public static final class FileHeader {
public final int fileversion; public final int fileversion;
public final String playerName; public final String playerName;
@@ -328,7 +344,8 @@ public final class Savegames {
public FileHeader(DataInputStream src, boolean skipIcon) throws IOException { public FileHeader(DataInputStream src, boolean skipIcon) throws IOException {
int fileversion = src.readInt(); int fileversion = src.readInt();
if (fileversion == 11) fileversion = 5; // Fileversion 5 had no version identifier, but the first byte was 11. if (fileversion == 11)
fileversion = 5; // Fileversion 5 had no version identifier, but the first byte was 11.
this.fileversion = fileversion; this.fileversion = fileversion;
if (fileversion >= 14) { // Before fileversion 14 (0.6.7), we had no file header. if (fileversion >= 14) { // Before fileversion 14 (0.6.7), we had no file header.
this.playerName = src.readUTF(); this.playerName = src.readUTF();

View File

@@ -1,10 +1,16 @@
package com.gpl.rpg.AndorsTrail.util; package com.gpl.rpg.AndorsTrail.util;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.content.FileProvider; import android.support.v4.content.FileProvider;
import android.support.v4.provider.DocumentFile;
import android.webkit.MimeTypeMap;
import com.gpl.rpg.AndorsTrail.controller.Constants; import com.gpl.rpg.AndorsTrail.controller.Constants;
@@ -17,7 +23,7 @@ import java.io.OutputStream;
public final class AndroidStorage { public final class AndroidStorage {
public static File getStorageDirectory(Context context, String name) { public static File getStorageDirectory(Context context, String name) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return context.getExternalFilesDir(name); return context.getExternalFilesDir(name);
} }
else { else {
@@ -25,6 +31,7 @@ public final class AndroidStorage {
return new File(root, name); return new File(root, name);
} }
} }
public static boolean shouldMigrateToInternalStorage(Context context) { public static boolean shouldMigrateToInternalStorage(Context context) {
boolean ret = false; boolean ret = false;
File externalSaveGameDirectory = new File(Environment.getExternalStorageDirectory(), Constants.FILENAME_SAVEGAME_DIRECTORY); File externalSaveGameDirectory = new File(Environment.getExternalStorageDirectory(), Constants.FILENAME_SAVEGAME_DIRECTORY);
@@ -76,33 +83,90 @@ public final class AndroidStorage {
} }
} }
private static void copyFile(File source, File target) throws IOException { public static void copyFile(File source, File target) throws IOException {
InputStream in = null; try (InputStream in = new FileInputStream(source); OutputStream out = new FileOutputStream(target)) {
OutputStream out = null; copyStream(in, out);
try { }
in = new FileInputStream(source); }
out = new FileOutputStream(target);
public static void copyStream(InputStream in, OutputStream out) throws IOException {
byte[] buf = new byte[1024]; byte[] buf = new byte[1024];
int length; int length;
while ((length = in.read(buf)) > 0) { while ((length = in.read(buf)) > 0) {
out.write(buf, 0, length); out.write(buf, 0, length);
} }
} finally {
if (in != null) {
in.close();
} }
if (out != null) {
out.close();
public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFolder) throws IOException {
copyDocumentFileToNewOrExistingFile(sourceFile, resolver, targetFolder, Constants.NO_FILE_EXTENSION_MIME_TYPE);
} }
public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFolder, String mimeType) throws IOException {
String fileName = sourceFile.getName();
DocumentFile file = targetFolder.findFile(fileName);
if (file == null)
file = targetFolder.createFile(mimeType, fileName);
if (file == null)
return;
AndroidStorage.copyDocumentFile(sourceFile, resolver, file);
}
public static void copyDocumentFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFile) throws IOException {
try (OutputStream outputStream = resolver.openOutputStream(targetFile.getUri());
InputStream inputStream = resolver.openInputStream(sourceFile.getUri())) {
copyStream(inputStream, outputStream);
} }
} }
/**
* Gets the MIME-Type for a file.<p/>
* Fallback value is '* / *' (without spaces) <p/>
* Mostly copied together from: <a href="https://stackoverflow.com/q/8589645/17292289">StackOverflow</a>
*/
@NonNull
public static String getMimeType(ContentResolver resolver, Uri uri) {
String type = null;
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
type = resolver.getType(uri);
return type;
}
final String extension = MimeTypeMap.getFileExtensionFromUrl(uri.getPath());
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
}
if (type == null) {
type = "*/*"; // fallback type.
}
return type;
}
public static String getUrlForFile(Context context, File worldmap) { public static String getUrlForFile(Context context, File worldmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Uri uri = FileProvider.getUriForFile(context, "com.gpl.rpg.AndorsTrail.fileprovider", worldmap); Uri uri = FileProvider.getUriForFile(context, "com.gpl.rpg.AndorsTrail.fileprovider", worldmap);//TODO: remove fixed fileprovider
return uri.toString(); return uri.toString();
} else { } else {
return "file://" + worldmap.getAbsolutePath(); return "file://" + worldmap.getAbsolutePath();
} }
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static Intent getNewOpenDirectoryIntent() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
return intent;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static Intent getNewSelectMultipleSavegameFilesIntent() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.setType(Constants.SAVEGAME_FILE_MIME_TYPE);
return intent;
}
} }

View File

@@ -89,6 +89,17 @@ public class CustomDialogFactory {
return dialog; return dialog;
} }
public static CustomDialog createErrorDialog(final Context context, String title, String description) {
final CustomDialog d = createDialog(context,
title,
context.getResources().getDrawable(android.R.drawable.ic_dialog_alert),
description,
null,
true);
CustomDialogFactory.addDismissButton(d, android.R.string.ok);
return d;
}
public static CustomDialog setTitle(final CustomDialog dialog, String title, Drawable icon) { public static CustomDialog setTitle(final CustomDialog dialog, String title, Drawable icon) {
TextView titleView = (TextView) dialog.findViewById(R.id.dialog_title); TextView titleView = (TextView) dialog.findViewById(R.id.dialog_title);
if (title != null || icon != null) { if (title != null || icon != null) {

View File

@@ -66,4 +66,30 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/loadsave_export_import_save_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
style="@style/AndorsTrail_Style_StdFrame"
>
<Button
android:id="@+id/loadsave_export_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/loadsave_export_save"
/>
<Button
android:id="@+id/loadsave_import_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/loadsave_import_save"
/>
<Button
android:id="@+id/loadsave_import_worldmap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/loadsave_import_worldmap"
/>
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -489,6 +489,22 @@
<string name="loadsave_save_overwrite_confirmation_title">Overwrite savegame?</string> <string name="loadsave_save_overwrite_confirmation_title">Overwrite savegame?</string>
<string name="loadsave_save_overwrite_confirmation">This savegame contains a different player name (%1$s) than your current player name (%2$s). Are you sure you want to overwrite this savegame?</string> <string name="loadsave_save_overwrite_confirmation">This savegame contains a different player name (%1$s) than your current player name (%2$s). Are you sure you want to overwrite this savegame?</string>
<string name="loadsave_export_save">Export savegames</string>
<string name="loadsave_export_save_successfull">Export successful</string>
<string name="loadsave_export_overwrite_confirmation_title">Overwrite Existing Files?</string>
<string name="loadsave_export_overwrite_confirmation">The target Folder contains existing files with the same name of some Files that should be exported. Are you sure you want to overwrite those files?</string>
<string name="loadsave_import_overwrite_confirmation_title">Overwrite Existing Files?</string>
<string name="loadsave_import_overwrite_confirmation">The target Folder contains existing files with the same name. Are you sure you want to overwrite these files:</string>
<string name="loadsave_import_overwrite_confirmation_file_pattern">(%1$s) - (%2$s)</string>
<string name="loadsave_import_save">Import savegames</string>
<string name="loadsave_import_save_successfull">Import successful</string>
<string name="loadsave_import_save_unsuccessfull">Import unsuccessful</string>
<string name="loadsave_import_save_unsuccessfull_unknown">An unknown error occurred.</string>
<string name="loadsave_import_worldmap">Import worldmap</string>
<string name="loadsave_import_worldmap_successfull">Import of Worldmap successful</string>
<string name="loadsave_import_worldmap_unsuccessfull">Import of Worldmap unsuccessful</string>
<string name="loadsave_import_worldmap_unsuccessfull_wrong_directory">Are you sure there is a worldmap in this folder? Please select the folder called worldmap inside your export location.</string>
<string name="iteminfo_displaytypes_ordinary">Ordinary</string> <string name="iteminfo_displaytypes_ordinary">Ordinary</string>
<string name="iteminfo_displaytypes_quest">Quest item</string> <string name="iteminfo_displaytypes_quest">Quest item</string>
<string name="iteminfo_displaytypes_legendary">Legendary</string> <string name="iteminfo_displaytypes_legendary">Legendary</string>