Android: Add disc image conversion
This commit is contained in:
parent
ca46028cde
commit
7d6debb907
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -63,6 +63,8 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getDiscNumb
|
|||
jobject obj);
|
||||
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRevision(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBlobType(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_model_GameFile_getBlobTypeString(JNIEnv* env, jobject obj);
|
||||
JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBlockSize(JNIEnv* env,
|
||||
|
@ -73,6 +75,8 @@ JNIEXPORT jboolean JNICALL
|
|||
Java_org_dolphinemu_dolphinemu_model_GameFile_shouldShowFileFormatDetails(JNIEnv* env, jobject obj);
|
||||
JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getFileSize(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_isDatelDisc(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBanner(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBannerWidth(JNIEnv* env,
|
||||
|
@ -154,6 +158,12 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRevision
|
|||
return GetRef(env, obj)->GetRevision();
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBlobType(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
return static_cast<jint>(GetRef(env, obj)->GetBlobType());
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_model_GameFile_getBlobTypeString(JNIEnv* env, jobject obj)
|
||||
{
|
||||
|
@ -175,7 +185,7 @@ Java_org_dolphinemu_dolphinemu_model_GameFile_getCompressionMethod(JNIEnv* env,
|
|||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_model_GameFile_shouldShowFileFormatDetails(JNIEnv* env, jobject obj)
|
||||
{
|
||||
return GetRef(env, obj)->ShouldShowFileFormatDetails();
|
||||
return static_cast<jboolean>(GetRef(env, obj)->ShouldShowFileFormatDetails());
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getFileSize(JNIEnv* env,
|
||||
|
@ -184,6 +194,12 @@ JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getFileSiz
|
|||
return GetRef(env, obj)->GetFileSize();
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_isDatelDisc(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
return static_cast<jboolean>(GetRef(env, obj)->IsDatelDisc());
|
||||
}
|
||||
|
||||
JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBanner(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <utility>
|
||||
|
||||
#include "Common/AndroidAnalytics.h"
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
@ -45,7 +46,9 @@
|
|||
#include "Core/State.h"
|
||||
#include "Core/WiiUtils.h"
|
||||
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/ScrubbedBlob.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
#include "InputCommon/ControllerInterface/Android/Android.h"
|
||||
|
@ -663,6 +666,58 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_InstallW
|
|||
return static_cast<jboolean>(WiiUtils::InstallWAD(path));
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ConvertDiscImage(
|
||||
JNIEnv* env, jobject obj, jstring jInPath, jstring jOutPath, jint jPlatform, jint jFormat,
|
||||
jint jBlockSize, jint jCompression, jint jCompressionLevel, jboolean jScrub)
|
||||
{
|
||||
const std::string in_path = GetJString(env, jInPath);
|
||||
const std::string out_path = GetJString(env, jOutPath);
|
||||
const DiscIO::Platform platform = static_cast<DiscIO::Platform>(jPlatform);
|
||||
const DiscIO::BlobType format = static_cast<DiscIO::BlobType>(jFormat);
|
||||
const DiscIO::WIARVZCompressionType compression =
|
||||
static_cast<DiscIO::WIARVZCompressionType>(jCompression);
|
||||
const bool scrub = static_cast<bool>(jScrub);
|
||||
|
||||
std::unique_ptr<DiscIO::BlobReader> blob_reader;
|
||||
if (scrub)
|
||||
blob_reader = DiscIO::ScrubbedBlob::Create(in_path);
|
||||
else
|
||||
blob_reader = DiscIO::CreateBlobReader(in_path);
|
||||
|
||||
if (!blob_reader)
|
||||
return static_cast<jboolean>(false);
|
||||
|
||||
const auto callback = [](const std::string& text, float percent) { return true; };
|
||||
|
||||
bool success = false;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case DiscIO::BlobType::PLAIN:
|
||||
success = DiscIO::ConvertToPlain(blob_reader.get(), in_path, out_path, callback);
|
||||
break;
|
||||
|
||||
case DiscIO::BlobType::GCZ:
|
||||
success =
|
||||
DiscIO::ConvertToGCZ(blob_reader.get(), in_path, out_path,
|
||||
platform == DiscIO::Platform::WiiDisc ? 1 : 0, jBlockSize, callback);
|
||||
break;
|
||||
|
||||
case DiscIO::BlobType::WIA:
|
||||
case DiscIO::BlobType::RVZ:
|
||||
success = DiscIO::ConvertToWIAOrRVZ(blob_reader.get(), in_path, out_path,
|
||||
format == DiscIO::BlobType::RVZ, compression,
|
||||
jCompressionLevel, jBlockSize, callback);
|
||||
break;
|
||||
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return static_cast<jboolean>(success);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_FormatSize(JNIEnv* env,
|
||||
jobject obj,
|
||||
jlong bytes,
|
||||
|
|
Loading…
Reference in New Issue