diff options
Diffstat (limited to 'Source/Android/app/src')
11 files changed, 820 insertions, 5 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 86e75d55ec..238fb2a769 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 @@ -429,6 +429,9 @@ 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); + public static native String FormatSize(long bytes, int decimals); private static boolean alertResult = false; 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/dialogs/GamePropertiesDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java index 15521e6043..44642d30b8 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,6 +9,7 @@ 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; @@ -67,6 +68,9 @@ public class GamePropertiesDialog extends DialogFragment .getSupportFragmentManager(), "game_details"); break; case 1: + ConvertActivity.launch(getContext(), path); + break; + case 2: try (Settings settings = new Settings()) { settings.loadSettings(null); @@ -74,23 +78,23 @@ public class GamePropertiesDialog extends DialogFragment settings.saveSettings(null, getContext()); } break; - case 2: + case 3: SettingsActivity.launch(getContext(), MenuTag.CONFIG, gameId, revision); break; - case 3: + case 4: SettingsActivity.launch(getContext(), MenuTag.GRAPHICS, gameId, revision); break; - case 4: + case 5: SettingsActivity.launch(getContext(), MenuTag.GCPAD_TYPE, gameId, revision); break; - case 5: + case 6: // 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: + case 7: clearGameSettings(gameId); break; } 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..a37a9d3874 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java @@ -0,0 +1,371 @@ +package org.dolphinemu.dolphinemu.fragments; + +import android.content.Context; +import android.os.Bundle; +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.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 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; + + 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); + } + + 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(); + convert(); + }) + .setNegativeButton(R.string.no, (dialog, i) -> dialog.dismiss()); + AlertDialog alert = builder.create(); + alert.show(); + } + else + { + convert(); + } + } + + private void convert() + { + Context context = requireContext(); + + // TODO: Let the user select a path + String outPath = gameFile.getPath() + ".converted"; + + 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()); + + 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(); + } +} 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..c0b55f5da7 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(); @@ -48,6 +50,8 @@ public class GameFile public native long getFileSize(); + public native boolean isDatelDisc(); + public native int[] getBanner(); public native int getBannerWidth(); 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 5adc40229d..724ac6d8c4 100644 --- a/Source/Android/app/src/main/res/values/arrays.xml +++ b/Source/Android/app/src/main/res/values/arrays.xml @@ -387,6 +387,7 @@ <string-array name="gameSettingsMenusGC"> <item>Details</item> + <item>Convert File</item> <item>Set as Default ISO</item> <item>Core Settings</item> <item>GFX Settings</item> @@ -395,6 +396,7 @@ </string-array> <string-array name="gameSettingsMenusWii"> <item>Details</item> + <item>Convert File</item> <item>Set as Default ISO</item> <item>Core Settings</item> <item>GFX Settings</item> @@ -419,4 +421,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 7ced055909..977bd56bd4 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -330,6 +330,29 @@ <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_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> |
