summaryrefslogtreecommitdiff
path: root/Source/Android/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Android/app/src')
-rw-r--r--Source/Android/app/src/main/AndroidManifest.xml4
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java5
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java40
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java4
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java4
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java111
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java487
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java6
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AlertDialogItemsBuilder.java40
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CompressCallback.java6
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java39
-rw-r--r--Source/Android/app/src/main/res/layout-w680dp-land/activity_convert.xml50
-rw-r--r--Source/Android/app/src/main/res/layout/activity_convert.xml48
-rw-r--r--Source/Android/app/src/main/res/layout/fragment_convert.xml123
-rw-r--r--Source/Android/app/src/main/res/values/arrays.xml161
-rw-r--r--Source/Android/app/src/main/res/values/strings.xml34
16 files changed, 1084 insertions, 78 deletions
diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml
index 7298c10137..56d6eadfbf 100644
--- a/Source/Android/app/src/main/AndroidManifest.xml
+++ b/Source/Android/app/src/main/AndroidManifest.xml
@@ -96,6 +96,10 @@
</intent-filter>
</activity>
+ <activity
+ android:name=".activities.ConvertActivity"
+ android:theme="@style/DolphinBase" />
+
<service android:name=".utils.DirectoryInitialization"/>
<service android:name=".services.GameFileCacheService"/>
<service
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
index a03376bb8a..757aa74dfc 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
@@ -12,6 +12,7 @@ import android.view.Surface;
import androidx.appcompat.app.AlertDialog;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
+import org.dolphinemu.dolphinemu.utils.CompressCallback;
import org.dolphinemu.dolphinemu.utils.Log;
import org.dolphinemu.dolphinemu.utils.Rumble;
@@ -429,6 +430,10 @@ public final class NativeLibrary
public static native boolean InstallWAD(String file);
+ public static native boolean ConvertDiscImage(String inPath, String outPath, int platform,
+ int format, int blockSize, int compression, int compressionLevel, boolean scrub,
+ CompressCallback callback);
+
public static native String FormatSize(long bytes, int decimals);
public static native void SetObscuredPixelsLeft(int width);
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java
new file mode 100644
index 0000000000..40cf22397b
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java
@@ -0,0 +1,40 @@
+package org.dolphinemu.dolphinemu.activities;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.fragments.ConvertFragment;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class ConvertActivity extends AppCompatActivity
+{
+ private static final String ARG_GAME_PATH = "game_path";
+
+ public static void launch(Context context, String gamePath)
+ {
+ Intent launcher = new Intent(context, ConvertActivity.class);
+ launcher.putExtra(ARG_GAME_PATH, gamePath);
+ context.startActivity(launcher);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_convert);
+
+ String path = getIntent().getStringExtra(ARG_GAME_PATH);
+
+ ConvertFragment fragment = (ConvertFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.fragment_convert);
+ if (fragment == null)
+ {
+ fragment = ConvertFragment.newInstance(path);
+ getSupportFragmentManager().beginTransaction().add(R.id.fragment_convert, fragment).commit();
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java
index ff2db6b6a5..5fd27ad5ab 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java
@@ -156,9 +156,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
return true;
}
- GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile.getPath(),
- gameId, holder.gameFile.getRevision(), holder.gameFile.getPlatform());
-
+ GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile);
((FragmentActivity) view.getContext()).getSupportFragmentManager().beginTransaction()
.add(fragment, GamePropertiesDialog.TAG).commit();
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java
index 6790f331b2..aa600173f4 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java
@@ -100,9 +100,7 @@ public final class GameRowPresenter extends Presenter
return true;
}
- GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile.getPath(),
- gameId, holder.gameFile.getRevision(), holder.gameFile.getPlatform());
-
+ GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile);
((FragmentActivity) view.getContext()).getSupportFragmentManager().beginTransaction()
.add(fragment, GamePropertiesDialog.TAG).commit();
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java
index 15521e6043..6480b24c2c 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java
@@ -9,11 +9,14 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.activities.ConvertActivity;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting;
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity;
+import org.dolphinemu.dolphinemu.model.GameFile;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
+import org.dolphinemu.dolphinemu.utils.AlertDialogItemsBuilder;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
import org.dolphinemu.dolphinemu.utils.Log;
@@ -22,21 +25,22 @@ import java.io.File;
public class GamePropertiesDialog extends DialogFragment
{
public static final String TAG = "GamePropertiesDialog";
- public static final String ARG_PATH = "path";
- public static final String ARG_GAMEID = "game_id";
+ private static final String ARG_PATH = "path";
+ private static final String ARG_GAMEID = "game_id";
public static final String ARG_REVISION = "revision";
- public static final String ARG_PLATFORM = "platform";
+ private static final String ARG_PLATFORM = "platform";
+ private static final String ARG_SHOULD_ALLOW_CONVERSION = "should_allow_conversion";
- public static GamePropertiesDialog newInstance(String path, String gameId, int revision,
- int platform)
+ public static GamePropertiesDialog newInstance(GameFile gameFile)
{
GamePropertiesDialog fragment = new GamePropertiesDialog();
Bundle arguments = new Bundle();
- arguments.putString(ARG_PATH, path);
- arguments.putString(ARG_GAMEID, gameId);
- arguments.putInt(ARG_REVISION, revision);
- arguments.putInt(ARG_PLATFORM, platform);
+ arguments.putString(ARG_PATH, gameFile.getPath());
+ arguments.putString(ARG_GAMEID, gameFile.getGameId());
+ arguments.putInt(ARG_REVISION, gameFile.getRevision());
+ arguments.putInt(ARG_PLATFORM, gameFile.getPlatform());
+ arguments.putBoolean(ARG_SHOULD_ALLOW_CONVERSION, gameFile.shouldAllowConversion());
fragment.setArguments(arguments);
return fragment;
@@ -46,59 +50,60 @@ public class GamePropertiesDialog extends DialogFragment
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
- AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(),
- R.style.DolphinDialogBase);
-
String path = requireArguments().getString(ARG_PATH);
String gameId = requireArguments().getString(ARG_GAMEID);
int revision = requireArguments().getInt(ARG_REVISION);
int platform = requireArguments().getInt(ARG_PLATFORM);
+ boolean shouldAllowConversion = requireArguments().getBoolean(ARG_SHOULD_ALLOW_CONVERSION);
+
+ AlertDialogItemsBuilder itemsBuilder = new AlertDialogItemsBuilder(requireContext());
+
+ itemsBuilder.add(R.string.properties_details, (dialog, i) ->
+ GameDetailsDialog.newInstance(path).show(requireActivity()
+ .getSupportFragmentManager(), "game_details"));
+
+ if (shouldAllowConversion)
+ {
+ itemsBuilder.add(R.string.properties_convert, (dialog, i) ->
+ ConvertActivity.launch(getContext(), path));
+ }
+
+ itemsBuilder.add(R.string.properties_set_default_iso, (dialog, i) ->
+ {
+ try (Settings settings = new Settings())
+ {
+ settings.loadSettings(null);
+ StringSetting.MAIN_DEFAULT_ISO.setString(settings, path);
+ settings.saveSettings(null, getContext());
+ }
+ });
+
+ itemsBuilder.add(R.string.properties_core_settings, (dialog, i) ->
+ SettingsActivity.launch(getContext(), MenuTag.CONFIG, gameId, revision));
+
+ itemsBuilder.add(R.string.properties_gfx_settings, (dialog, i) ->
+ SettingsActivity.launch(getContext(), MenuTag.GRAPHICS, gameId, revision));
+ itemsBuilder.add(R.string.properties_gc_controller, (dialog, i) ->
+ SettingsActivity.launch(getContext(), MenuTag.GCPAD_TYPE, gameId, revision));
+
+ if (platform != Platform.GAMECUBE.toInt())
+ {
+ itemsBuilder.add(R.string.properties_wii_controller, (dialog, i) ->
+ SettingsActivity.launch(getActivity(), MenuTag.WIIMOTE, gameId, revision));
+ }
+
+ itemsBuilder.add(R.string.properties_clear_game_settings, (dialog, i) ->
+ clearGameSettings(gameId));
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(),
+ R.style.DolphinDialogBase);
+ itemsBuilder.applyToBuilder(builder);
builder.setTitle(requireContext()
- .getString(R.string.preferences_game_properties) + ": " + gameId)
- .setItems(platform == Platform.GAMECUBE.toInt() ?
- R.array.gameSettingsMenusGC :
- R.array.gameSettingsMenusWii, (dialog, which) ->
- {
- switch (which)
- {
- case 0:
- GameDetailsDialog.newInstance(path).show((requireActivity())
- .getSupportFragmentManager(), "game_details");
- break;
- case 1:
- try (Settings settings = new Settings())
- {
- settings.loadSettings(null);
- StringSetting.MAIN_DEFAULT_ISO.setString(settings, path);
- settings.saveSettings(null, getContext());
- }
- break;
- case 2:
- SettingsActivity.launch(getContext(), MenuTag.CONFIG, gameId, revision);
- break;
- case 3:
- SettingsActivity.launch(getContext(), MenuTag.GRAPHICS, gameId, revision);
- break;
- case 4:
- SettingsActivity.launch(getContext(), MenuTag.GCPAD_TYPE, gameId, revision);
- break;
- case 5:
- // Clear option for GC, Wii controls for else
- if (platform == Platform.GAMECUBE.toInt())
- clearGameSettings(gameId);
- else
- SettingsActivity.launch(getActivity(), MenuTag.WIIMOTE, gameId, revision);
- break;
- case 6:
- clearGameSettings(gameId);
- break;
- }
- });
+ .getString(R.string.preferences_game_properties) + ": " + gameId);
return builder.create();
}
-
private void clearGameSettings(String gameId)
{
String gameSettingsPath =
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java
new file mode 100644
index 0000000000..ac1237ba59
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java
@@ -0,0 +1,487 @@
+package org.dolphinemu.dolphinemu.fragments;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.DocumentsContract;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.Spinner;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.model.GameFile;
+import org.dolphinemu.dolphinemu.services.GameFileCacheService;
+import org.dolphinemu.dolphinemu.ui.platform.Platform;
+
+import java.io.File;
+import java.util.ArrayList;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+
+public class ConvertFragment extends Fragment implements View.OnClickListener
+{
+ private static class SpinnerValue implements AdapterView.OnItemSelectedListener
+ {
+ private int mValuesId = -1;
+ private int mCurrentPosition = -1;
+ private ArrayList<Runnable> mCallbacks = new ArrayList<>();
+
+ @Override
+ public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id)
+ {
+ if (mCurrentPosition != position)
+ setPosition(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> adapterView)
+ {
+ mCurrentPosition = -1;
+ }
+
+ int getPosition()
+ {
+ return mCurrentPosition;
+ }
+
+ void setPosition(int position)
+ {
+ mCurrentPosition = position;
+ for (Runnable callback : mCallbacks)
+ callback.run();
+ }
+
+ void populate(int valuesId)
+ {
+ mValuesId = valuesId;
+ }
+
+ boolean hasValues()
+ {
+ return mValuesId != -1;
+ }
+
+ int getValue(Context context)
+ {
+ return context.getResources().getIntArray(mValuesId)[mCurrentPosition];
+ }
+
+ int getValueOr(Context context, int defaultValue)
+ {
+ return hasValues() ? getValue(context) : defaultValue;
+ }
+
+ void addCallback(Runnable callback)
+ {
+ mCallbacks.add(callback);
+ }
+ }
+
+ private static final String ARG_GAME_PATH = "game_path";
+
+ private static final String KEY_FORMAT = "convert_format";
+ private static final String KEY_BLOCK_SIZE = "convert_block_size";
+ private static final String KEY_COMPRESSION = "convert_compression";
+ private static final String KEY_COMPRESSION_LEVEL = "convert_compression_level";
+ private static final String KEY_REMOVE_JUNK_DATA = "remove_junk_data";
+
+ private static final int REQUEST_CODE_SAVE_FILE = 0;
+
+ private static final int BLOB_TYPE_PLAIN = 0;
+ private static final int BLOB_TYPE_GCZ = 3;
+ private static final int BLOB_TYPE_WIA = 7;
+ private static final int BLOB_TYPE_RVZ = 8;
+
+ private static final int COMPRESSION_NONE = 0;
+ private static final int COMPRESSION_PURGE = 1;
+ private static final int COMPRESSION_BZIP2 = 2;
+ private static final int COMPRESSION_LZMA = 3;
+ private static final int COMPRESSION_LZMA2 = 4;
+ private static final int COMPRESSION_ZSTD = 5;
+
+ private SpinnerValue mFormat = new SpinnerValue();
+ private SpinnerValue mBlockSize = new SpinnerValue();
+ private SpinnerValue mCompression = new SpinnerValue();
+ private SpinnerValue mCompressionLevel = new SpinnerValue();
+
+ private GameFile gameFile;
+
+ private volatile boolean mCanceled;
+ private volatile Thread mThread = null;
+
+ public static ConvertFragment newInstance(String gamePath)
+ {
+ Bundle args = new Bundle();
+ args.putString(ARG_GAME_PATH, gamePath);
+
+ ConvertFragment fragment = new ConvertFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ gameFile = GameFileCacheService.addOrGet(requireArguments().getString(ARG_GAME_PATH));
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState)
+ {
+ return inflater.inflate(R.layout.fragment_convert, container, false);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, Bundle savedInstanceState)
+ {
+ populateFormats();
+ populateBlockSize();
+ populateCompression();
+ populateCompressionLevel();
+ populateRemoveJunkData();
+
+ mFormat.addCallback(this::populateBlockSize);
+ mFormat.addCallback(this::populateCompression);
+ mCompression.addCallback(this::populateCompressionLevel);
+ mFormat.addCallback(this::populateRemoveJunkData);
+
+ view.findViewById(R.id.button_convert).setOnClickListener(this);
+
+ if (savedInstanceState != null)
+ {
+ setSpinnerSelection(R.id.spinner_format, mFormat, savedInstanceState.getInt(KEY_FORMAT));
+ setSpinnerSelection(R.id.spinner_block_size, mBlockSize,
+ savedInstanceState.getInt(KEY_BLOCK_SIZE));
+ setSpinnerSelection(R.id.spinner_compression, mCompression,
+ savedInstanceState.getInt(KEY_COMPRESSION));
+ setSpinnerSelection(R.id.spinner_compression_level, mCompressionLevel,
+ savedInstanceState.getInt(KEY_COMPRESSION_LEVEL));
+
+ CheckBox removeJunkData = requireView().findViewById(R.id.checkbox_remove_junk_data);
+ removeJunkData.setChecked(savedInstanceState.getBoolean(KEY_REMOVE_JUNK_DATA));
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState)
+ {
+ outState.putInt(KEY_FORMAT, mFormat.getPosition());
+ outState.putInt(KEY_BLOCK_SIZE, mBlockSize.getPosition());
+ outState.putInt(KEY_COMPRESSION, mCompression.getPosition());
+ outState.putInt(KEY_COMPRESSION_LEVEL, mCompressionLevel.getPosition());
+
+ CheckBox removeJunkData = requireView().findViewById(R.id.checkbox_remove_junk_data);
+ outState.putBoolean(KEY_REMOVE_JUNK_DATA, removeJunkData.isChecked());
+ }
+
+ private void setSpinnerSelection(int id, SpinnerValue valueWrapper, int i)
+ {
+ ((Spinner) requireView().findViewById(id)).setSelection(i);
+ valueWrapper.setPosition(i);
+ }
+
+ @Override
+ public void onStop()
+ {
+ super.onStop();
+
+ mCanceled = true;
+ joinThread();
+ }
+
+ private Spinner populateSpinner(int spinnerId, int entriesId, int valuesId,
+ SpinnerValue valueWrapper)
+ {
+ Spinner spinner = requireView().findViewById(spinnerId);
+
+ ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(requireContext(),
+ entriesId, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+
+ spinner.setEnabled(spinner.getCount() > 1);
+
+ valueWrapper.populate(valuesId);
+ valueWrapper.setPosition(spinner.getSelectedItemPosition());
+ spinner.setOnItemSelectedListener(valueWrapper);
+
+ return spinner;
+ }
+
+ private Spinner clearSpinner(int spinnerId, SpinnerValue valueWrapper)
+ {
+ Spinner spinner = requireView().findViewById(spinnerId);
+
+ spinner.setAdapter(null);
+
+ spinner.setEnabled(false);
+
+ valueWrapper.populate(-1);
+ valueWrapper.setPosition(-1);
+ spinner.setOnItemSelectedListener(valueWrapper);
+
+ return spinner;
+ }
+
+ private void populateFormats()
+ {
+ Spinner spinner = populateSpinner(R.id.spinner_format, R.array.convertFormatEntries,
+ R.array.convertFormatValues, mFormat);
+
+ if (gameFile.getBlobType() == BLOB_TYPE_PLAIN)
+ spinner.setSelection(spinner.getCount() - 1);
+ }
+
+ private void populateBlockSize()
+ {
+ switch (mFormat.getValue(requireContext()))
+ {
+ case BLOB_TYPE_GCZ:
+ // In the equivalent DolphinQt code, we have some logic for avoiding block sizes that can
+ // trigger bugs in Dolphin versions older than 5.0-11893, but it was too annoying to port.
+ // TODO: Port it?
+ populateSpinner(R.id.spinner_block_size, R.array.convertBlockSizeGczEntries,
+ R.array.convertBlockSizeGczValues, mBlockSize);
+ break;
+ case BLOB_TYPE_WIA:
+ populateSpinner(R.id.spinner_block_size, R.array.convertBlockSizeWiaEntries,
+ R.array.convertBlockSizeWiaValues, mBlockSize);
+ break;
+ case BLOB_TYPE_RVZ:
+ populateSpinner(R.id.spinner_block_size, R.array.convertBlockSizeRvzEntries,
+ R.array.convertBlockSizeRvzValues, mBlockSize).setSelection(2);
+ break;
+ default:
+ clearSpinner(R.id.spinner_block_size, mBlockSize);
+ }
+ }
+
+ private void populateCompression()
+ {
+ switch (mFormat.getValue(requireContext()))
+ {
+ case BLOB_TYPE_GCZ:
+ populateSpinner(R.id.spinner_compression, R.array.convertCompressionGczEntries,
+ R.array.convertCompressionGczValues, mCompression);
+ break;
+ case BLOB_TYPE_WIA:
+ populateSpinner(R.id.spinner_compression, R.array.convertCompressionWiaEntries,
+ R.array.convertCompressionWiaValues, mCompression);
+ break;
+ case BLOB_TYPE_RVZ:
+ populateSpinner(R.id.spinner_compression, R.array.convertCompressionRvzEntries,
+ R.array.convertCompressionRvzValues, mCompression).setSelection(4);
+ break;
+ default:
+ clearSpinner(R.id.spinner_compression, mCompression);
+ }
+ }
+
+ private void populateCompressionLevel()
+ {
+ switch (mCompression.getValueOr(requireContext(), COMPRESSION_NONE))
+ {
+ case COMPRESSION_BZIP2:
+ case COMPRESSION_LZMA:
+ case COMPRESSION_LZMA2:
+ populateSpinner(R.id.spinner_compression_level, R.array.convertCompressionLevelEntries,
+ R.array.convertCompressionLevelValues, mCompressionLevel).setSelection(4);
+ break;
+ case COMPRESSION_ZSTD:
+ // TODO: Query DiscIO for the supported compression levels, like we do in DolphinQt?
+ populateSpinner(R.id.spinner_compression_level, R.array.convertCompressionLevelZstdEntries,
+ R.array.convertCompressionLevelZstdValues, mCompressionLevel).setSelection(4);
+ break;
+ default:
+ clearSpinner(R.id.spinner_compression_level, mCompressionLevel);
+ }
+ }
+
+ private void populateRemoveJunkData()
+ {
+ boolean scrubbingAllowed = mFormat.getValue(requireContext()) != BLOB_TYPE_RVZ &&
+ !gameFile.isDatelDisc();
+
+ CheckBox removeJunkData = requireView().findViewById(R.id.checkbox_remove_junk_data);
+ removeJunkData.setEnabled(scrubbingAllowed);
+ if (!scrubbingAllowed)
+ removeJunkData.setChecked(false);
+ }
+
+ private boolean getRemoveJunkData()
+ {
+ CheckBox checkBoxScrub = requireView().findViewById(R.id.checkbox_remove_junk_data);
+ return checkBoxScrub.isChecked();
+ }
+
+ @Override
+ public void onClick(View view)
+ {
+ Context context = requireContext();
+
+ boolean scrub = getRemoveJunkData();
+ int format = mFormat.getValue(context);
+
+ boolean iso_warning = scrub && format == BLOB_TYPE_PLAIN;
+ boolean gcz_warning = !scrub && format == BLOB_TYPE_GCZ && !gameFile.isDatelDisc() &&
+ gameFile.getPlatform() == Platform.WII.toInt();
+
+ if (iso_warning || gcz_warning)
+ {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.DolphinDialogBase);
+ builder.setMessage(iso_warning ? R.string.convert_warning_iso : R.string.convert_warning_gcz)
+ .setPositiveButton(R.string.yes, (dialog, i) ->
+ {
+ dialog.dismiss();
+ showSavePrompt();
+ })
+ .setNegativeButton(R.string.no, (dialog, i) -> dialog.dismiss());
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+ else
+ {
+ showSavePrompt();
+ }
+ }
+
+ private void showSavePrompt()
+ {
+ String originalPath = gameFile.getPath();
+
+ StringBuilder path = new StringBuilder(new File(originalPath).getName());
+ int dotIndex = path.lastIndexOf(".");
+ if (dotIndex != -1)
+ path.setLength(dotIndex);
+ switch (mFormat.getValue(requireContext()))
+ {
+ case BLOB_TYPE_PLAIN:
+ path.append(".iso");
+ break;
+ case BLOB_TYPE_GCZ:
+ path.append(".gcz");
+ break;
+ case BLOB_TYPE_WIA:
+ path.append(".wia");
+ break;
+ case BLOB_TYPE_RVZ:
+ path.append(".rvz");
+ break;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("application/octet-stream");
+ intent.putExtra(Intent.EXTRA_TITLE, path.toString());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, originalPath);
+ startActivityForResult(intent, REQUEST_CODE_SAVE_FILE);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data)
+ {
+ if (requestCode == REQUEST_CODE_SAVE_FILE && resultCode == Activity.RESULT_OK)
+ {
+ convert(data.getData().toString());
+ }
+ }
+
+ private void convert(String outPath)
+ {
+ final int PROGRESS_RESOLUTION = 1000;
+
+ Context context = requireContext();
+
+ joinThread();
+
+ mCanceled = false;
+
+ // For some reason, setting R.style.DolphinDialogBase as the theme here gives us white text
+ // on a white background when the device is set to dark mode, so let's not set a theme.
+ ProgressDialog progressDialog = new ProgressDialog(context);
+
+ progressDialog.setTitle(R.string.convert_converting);
+
+ progressDialog.setIndeterminate(false);
+ progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ progressDialog.setMax(PROGRESS_RESOLUTION);
+
+ progressDialog.setCancelable(true);
+ progressDialog.setOnCancelListener((dialog) -> mCanceled = true);
+
+ progressDialog.show();
+
+ mThread = new Thread(() ->
+ {
+ boolean success = NativeLibrary.ConvertDiscImage(gameFile.getPath(), outPath,
+ gameFile.getPlatform(), mFormat.getValue(context), mBlockSize.getValueOr(context, 0),
+ mCompression.getValueOr(context, 0), mCompressionLevel.getValueOr(context, 0),
+ getRemoveJunkData(), (text, completion) ->
+ {
+ requireActivity().runOnUiThread(() ->
+ {
+ progressDialog.setMessage(text);
+ progressDialog.setProgress((int) (completion * PROGRESS_RESOLUTION));
+ });
+
+ return !mCanceled;
+ });
+
+ if (!mCanceled)
+ {
+ requireActivity().runOnUiThread(() ->
+ {
+ progressDialog.dismiss();
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.DolphinDialogBase);
+ if (success)
+ {
+ builder.setMessage(R.string.convert_success_message)
+ .setCancelable(false)
+ .setPositiveButton(R.string.ok, (dialog, i) ->
+ {
+ dialog.dismiss();
+ requireActivity().finish();
+ });
+ }
+ else
+ {
+ builder.setMessage(R.string.convert_failure_message)
+ .setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss());
+ }
+ AlertDialog alert = builder.create();
+ alert.show();
+ });
+ }
+ });
+
+ mThread.start();
+ }
+
+ private void joinThread()
+ {
+ if (mThread != null)
+ {
+ try
+ {
+ mThread.join();
+ }
+ catch (InterruptedException ignored)
+ {
+ }
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java
index dd1dd2898e..9ac331c2b2 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java
@@ -38,6 +38,8 @@ public class GameFile
public native int getRevision();
+ public native int getBlobType();
+
public native String getBlobTypeString();
public native long getBlockSize();
@@ -46,8 +48,12 @@ public class GameFile
public native boolean shouldShowFileFormatDetails();
+ public native boolean shouldAllowConversion();
+
public native long getFileSize();
+ public native boolean isDatelDisc();
+
public native int[] getBanner();
public native int getBannerWidth();
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AlertDialogItemsBuilder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AlertDialogItemsBuilder.java
new file mode 100644
index 0000000000..0c4f358069
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AlertDialogItemsBuilder.java
@@ -0,0 +1,40 @@
+package org.dolphinemu.dolphinemu.utils;
+
+import androidx.appcompat.app.AlertDialog;
+
+import android.content.Context;
+import android.content.DialogInterface.OnClickListener;
+
+import java.util.ArrayList;
+
+public class AlertDialogItemsBuilder
+{
+ private Context mContext;
+
+ private ArrayList<CharSequence> mLabels = new ArrayList<>();
+ private ArrayList<OnClickListener> mListeners = new ArrayList<>();
+
+ public AlertDialogItemsBuilder(Context context)
+ {
+ mContext = context;
+ }
+
+ public void add(int stringId, OnClickListener listener)
+ {
+ mLabels.add(mContext.getResources().getString(stringId));
+ mListeners.add(listener);
+ }
+
+ public void add(CharSequence label, OnClickListener listener)
+ {
+ mLabels.add(label);
+ mListeners.add(listener);
+ }
+
+ public void applyToBuilder(AlertDialog.Builder builder)
+ {
+ CharSequence[] labels = new CharSequence[mLabels.size()];
+ labels = mLabels.toArray(labels);
+ builder.setItems(labels, (dialog, i) -> mListeners.get(i).onClick(dialog, i));
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CompressCallback.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CompressCallback.java
new file mode 100644
index 0000000000..83aa8d7919
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CompressCallback.java
@@ -0,0 +1,6 @@
+package org.dolphinemu.dolphinemu.utils;
+
+public interface CompressCallback
+{
+ boolean run(String text, float completion);
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java
new file mode 100644
index 0000000000..83b9c585c9
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java
@@ -0,0 +1,39 @@
+package org.dolphinemu.dolphinemu.utils;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+
+import org.dolphinemu.dolphinemu.DolphinApplication;
+
+import java.io.FileNotFoundException;
+
+public class ContentHandler
+{
+ public static int openFd(String uri, String mode)
+ {
+ try
+ {
+ return DolphinApplication.getAppContext().getContentResolver()
+ .openFileDescriptor(Uri.parse(uri), mode).detachFd();
+ }
+ catch (FileNotFoundException | NullPointerException e)
+ {
+ return -1;
+ }
+ }
+
+ public static boolean delete(String uri)
+ {
+ try
+ {
+ ContentResolver resolver = DolphinApplication.getAppContext().getContentResolver();
+ return DocumentsContract.deleteDocument(resolver, Uri.parse(uri));
+ }
+ catch (FileNotFoundException e)
+ {
+ // Return true because we care about the file not being there, not the actual delete.
+ return true;
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/res/layout-w680dp-land/activity_convert.xml b/Source/Android/app/src/main/res/layout-w680dp-land/activity_convert.xml
new file mode 100644
index 0000000000..bd37c3deb6
--- /dev/null
+++ b/Source/Android/app/src/main/res/layout-w680dp-land/activity_convert.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp">
+
+ <TextView
+ android:id="@+id/label_format_info"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:text="@string/convert_format_info"
+ app:layout_constraintWidth_max="400dp"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/divider" />
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="1dp"
+ android:layout_height="0dp"
+ android:background="#1F000000"
+ android:layout_marginStart="24dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/label_format_info"
+ app:layout_constraintEnd_toStartOf="@id/fragment_convert" />
+
+ <FrameLayout
+ android:id="@+id/fragment_convert"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dp"
+ app:layout_constraintWidth_max="400dp"
+ app:layout_constraintHorizontal_bias="0.0"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/divider"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</ScrollView>
diff --git a/Source/Android/app/src/main/res/layout/activity_convert.xml b/Source/Android/app/src/main/res/layout/activity_convert.xml
new file mode 100644
index 0000000000..b63559e248
--- /dev/null
+++ b/Source/Android/app/src/main/res/layout/activity_convert.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp">
+
+ <FrameLayout
+ android:id="@+id/fragment_convert"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintWidth_max="400dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/divider" />
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:background="#1F000000"
+ android:layout_marginTop="24dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/fragment_convert"
+ app:layout_constraintBottom_toTopOf="@id/label_format_info" />
+
+ <TextView
+ android:id="@+id/label_format_info"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
+ android:text="@string/convert_format_info"
+ app:layout_constraintWidth_max="400dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/divider"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</ScrollView>
diff --git a/Source/Android/app/src/main/res/layout/fragment_convert.xml b/Source/Android/app/src/main/res/layout/fragment_convert.xml
new file mode 100644
index 0000000000..938c3239a5
--- /dev/null
+++ b/Source/Android/app/src/main/res/layout/fragment_convert.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <TextView
+ android:id="@+id/label_format"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:text="@string/convert_format"
+ app:layout_constraintTop_toTopOf="@id/spinner_format"
+ app:layout_constraintBottom_toBottomOf="@id/spinner_format"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/label_block_size"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:text="@string/convert_block_size"
+ app:layout_constraintTop_toTopOf="@id/spinner_block_size"
+ app:layout_constraintBottom_toBottomOf="@id/spinner_block_size"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/label_compression"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:text="@string/convert_compression"
+ app:layout_constraintTop_toTopOf="@id/spinner_compression"
+ app:layout_constraintBottom_toBottomOf="@id/spinner_compression"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/label_compression_level"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:text="@string/convert_compression_level"
+ app:layout_constraintTop_toTopOf="@id/spinner_compression_level"
+ app:layout_constraintBottom_toBottomOf="@id/spinner_compression_level"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/label_remove_junk_data"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:text="@string/convert_remove_junk_data"
+ app:layout_constraintTop_toTopOf="@id/checkbox_remove_junk_data"
+ app:layout_constraintBottom_toBottomOf="@id/checkbox_remove_junk_data"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/label_barrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="label_format,label_block_size,label_compression,label_compression_level" />
+
+ <Spinner
+ android:id="@+id/spinner_format"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="16dp"
+ app:layout_constraintStart_toEndOf="@id/label_barrier"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <Spinner
+ android:id="@+id/spinner_block_size"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="16dp"
+ android:gravity="end"
+ app:layout_constraintStart_toEndOf="@id/label_barrier"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/spinner_format" />
+
+ <Spinner
+ android:id="@+id/spinner_compression"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="16dp"
+ android:gravity="end"
+ app:layout_constraintStart_toEndOf="@id/label_barrier"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/spinner_block_size" />
+
+ <Spinner
+ android:id="@+id/spinner_compression_level"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="16dp"
+ android:gravity="end"
+ app:layout_constraintStart_toEndOf="@id/label_barrier"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/spinner_compression" />
+
+ <CheckBox
+ android:id="@+id/checkbox_remove_junk_data"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginTop="16dp"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintStart_toEndOf="@id/label_remove_junk_data"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/spinner_compression_level" />
+
+ <Button
+ android:id="@+id/button_convert"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:text="@string/convert_convert"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/checkbox_remove_junk_data" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Source/Android/app/src/main/res/values/arrays.xml b/Source/Android/app/src/main/res/values/arrays.xml
index 275f6257a6..2c7655b66b 100644
--- a/Source/Android/app/src/main/res/values/arrays.xml
+++ b/Source/Android/app/src/main/res/values/arrays.xml
@@ -433,24 +433,6 @@
<item>Classic A</item>
</string-array>
- <string-array name="gameSettingsMenusGC">
- <item>Details</item>
- <item>Set as Default ISO</item>
- <item>Core Settings</item>
- <item>GFX Settings</item>
- <item>GameCube Controller Settings</item>
- <item>Clear Game Settings</item>
- </string-array>
- <string-array name="gameSettingsMenusWii">
- <item>Details</item>
- <item>Set as Default ISO</item>
- <item>Core Settings</item>
- <item>GFX Settings</item>
- <item>GameCube Controller Settings</item>
- <item>Wii Controller Settings</item>
- <item>Clear Game Settings</item>
- </string-array>
-
<string-array name="orientationEntries">
<item>Landscape</item>
<item>Portrait</item>
@@ -467,4 +449,147 @@
<item>Use Device Sensors (Without Pointer Emulation)</item>
<item>Don\'t Use Device Sensors</item>
</string-array>
+
+ <string-array name="convertFormatEntries" translatable="false">
+ <item>ISO</item>
+ <item>GCZ</item>
+ <item>WIA</item>
+ <item>RVZ</item>
+ </string-array>
+ <integer-array name="convertFormatValues">
+ <item>0</item>
+ <item>3</item>
+ <item>7</item>
+ <item>8</item>
+ </integer-array>
+
+ <string-array name="convertBlockSizeGczEntries">
+ <item>32 KiB</item>
+ </string-array>
+ <integer-array name="convertBlockSizeGczValues">
+ <item>32768</item>
+ </integer-array>
+
+ <string-array name="convertBlockSizeWiaEntries">
+ <item>2 MiB</item>
+ </string-array>
+ <integer-array name="convertBlockSizeWiaValues">
+ <item>2097152</item>
+ </integer-array>
+
+ <string-array name="convertBlockSizeRvzEntries">
+ <item>32 KiB</item>
+ <item>64 KiB</item>
+ <item>128 KiB</item>
+ <item>256 KiB</item>
+ <item>512 KiB</item>
+ <item>1 MiB</item>
+ <item>2 MiB</item>
+ </string-array>
+ <integer-array name="convertBlockSizeRvzValues">
+ <item>32768</item>
+ <item>65536</item>
+ <item>131072</item>
+ <item>262144</item>
+ <item>524288</item>
+ <item>1048576</item>
+ <item>2097152</item>
+ </integer-array>
+
+ <string-array name="convertCompressionGczEntries" translatable="false">
+ <item>Deflate</item>
+ </string-array>
+ <integer-array name="convertCompressionGczValues">
+ <item>0</item>
+ </integer-array>
+
+ <string-array name="convertCompressionWiaEntries">
+ <item>No Compression</item>
+ <item>Purge</item>
+ <item>bzip2 (slow)</item>
+ <item>LZMA (slow)</item>
+ <item>LZMA2 (slow)</item>
+ </string-array>
+ <integer-array name="convertCompressionWiaValues">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ </integer-array>
+
+ <string-array name="convertCompressionRvzEntries">
+ <item>No Compression</item>
+ <item>bzip2 (slow)</item>
+ <item>LZMA (slow)</item>
+ <item>LZMA2 (slow)</item>
+ <item>Zstandard (recommended)</item>
+ </string-array>
+ <integer-array name="convertCompressionRvzValues">
+ <item>0</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+ <item>6</item>
+ </integer-array>
+
+ <string-array name="convertCompressionLevelEntries">
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+ <item>6</item>
+ <item>7</item>
+ <item>8</item>
+ <item>9</item>
+ </string-array>
+ <integer-array name="convertCompressionLevelValues">
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+ <item>6</item>
+ <item>7</item>
+ <item>8</item>
+ <item>9</item>
+ </integer-array>
+
+ <string-array name="convertCompressionLevelZstdEntries">
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+ <item>6</item>
+ <item>7</item>
+ <item>8</item>
+ <item>9</item>
+ <item>10</item>
+ <item>11</item>
+ <item>12</item>
+ <item>13</item>
+ <item>14</item>
+ <item>15</item>
+ <item>16</item>
+ <item>17</item>
+ <item>18</item>
+ <item>19</item>
+ <item>20</item>
+ <item>21</item>
+ <item>22</item>
+ </string-array>
+ <integer-array name="convertCompressionLevelZstdValues">
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+ <item>6</item>
+ <item>7</item>
+ <item>8</item>
+ <item>9</item>
+ </integer-array>
</resources>
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index a600725e6e..ce43f7e576 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -324,7 +324,15 @@
<string name="wad_install_success">Successfully installed this title to the NAND.</string>
<string name="wad_install_failure">Failed to install this title to the NAND.</string>
- <!-- Preferences Screen -->
+ <!-- Game Properties Screen -->
+ <string name="properties_details">Details</string>
+ <string name="properties_convert">Convert File</string>
+ <string name="properties_set_default_iso">Set as Default ISO</string>
+ <string name="properties_core_settings">Core Settings</string>
+ <string name="properties_gfx_settings">GFX Settings</string>
+ <string name="properties_gc_controller">GameCube Controller Settings</string>
+ <string name="properties_wii_controller">Wii Controller Settings</string>
+ <string name="properties_clear_game_settings">Clear Game Settings</string>
<string name="preferences_save_exit">Save and Exit</string>
<string name="preferences_settings">Settings</string>
<string name="preferences_game_properties">Game Properties</string>
@@ -341,6 +349,30 @@
<string name="game_details_block_size">Block Size</string>
<string name="game_details_no_compression">No Compression</string>
+ <!-- Convert Screen -->
+ <string name="convert_format">Format</string>
+ <string name="convert_block_size">Block Size</string>
+ <string name="convert_compression">Compression</string>
+ <string name="convert_compression_level">Compression Level</string>
+ <string name="convert_remove_junk_data">Remove Junk Data (Irreversible)</string>
+ <string name="convert_convert">Convert</string>
+ <string name="convert_converting">Converting</string>
+ <string name="convert_warning_iso">Removing junk data does not save any space when converting to ISO (unless you package the ISO file in a compressed file format such as ZIP afterwards). Do you want to continue anyway?</string>
+ <string name="convert_warning_gcz">Converting Wii disc images to GCZ without removing junk data does not save any noticeable amount of space compared to converting to ISO. Do you want to continue anyway?</string>
+ <string name="convert_success_message">The disc image was successfully converted.</string>
+ <string name="convert_failure_message">Dolphin failed to complete the requested action.</string>
+ <string name="convert_format_info">
+ISO: A simple and robust format which is supported by many programs. It takes up more space
+than any other format.
+\n\nGCZ: A basic compressed format which is compatible with most versions of Dolphin and some
+other programs. It can\'t efficiently compress junk data (unless removed) or encrypted Wii data.
+\n\nWIA: An advanced compressed format which is compatible with Dolphin 5.0-12188 and later,
+and a few other programs. It can efficiently compress encrypted Wii data, but not junk data
+(unless removed).
+\n\nRVZ: An advanced compressed format which is compatible with Dolphin 5.0-12188 and later.
+It can efficiently compress both junk data and encrypted Wii data.
+ </string>
+
<!-- Emulation Menu -->
<string name="pause_emulation">Pause Emulation</string>
<string name="unpause_emulation">Unpause Emulation</string>