Merge pull request #8902 from JosJuice/android-convert
Android: Add disc image conversion
This commit is contained in:
commit
54e570a95f
|
@ -96,6 +96,10 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.ConvertActivity"
|
||||||
|
android:theme="@style/DolphinBase" />
|
||||||
|
|
||||||
<service android:name=".utils.DirectoryInitialization"/>
|
<service android:name=".utils.DirectoryInitialization"/>
|
||||||
<service android:name=".services.GameFileCacheService"/>
|
<service android:name=".services.GameFileCacheService"/>
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -12,6 +12,7 @@ import android.view.Surface;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.CompressCallback;
|
||||||
import org.dolphinemu.dolphinemu.utils.Log;
|
import org.dolphinemu.dolphinemu.utils.Log;
|
||||||
import org.dolphinemu.dolphinemu.utils.Rumble;
|
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 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 String FormatSize(long bytes, int decimals);
|
||||||
|
|
||||||
public static native void SetObscuredPixelsLeft(int width);
|
public static native void SetObscuredPixelsLeft(int width);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -156,9 +156,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile.getPath(),
|
GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile);
|
||||||
gameId, holder.gameFile.getRevision(), holder.gameFile.getPlatform());
|
|
||||||
|
|
||||||
((FragmentActivity) view.getContext()).getSupportFragmentManager().beginTransaction()
|
((FragmentActivity) view.getContext()).getSupportFragmentManager().beginTransaction()
|
||||||
.add(fragment, GamePropertiesDialog.TAG).commit();
|
.add(fragment, GamePropertiesDialog.TAG).commit();
|
||||||
|
|
||||||
|
|
|
@ -100,9 +100,7 @@ public final class GameRowPresenter extends Presenter
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile.getPath(),
|
GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile);
|
||||||
gameId, holder.gameFile.getRevision(), holder.gameFile.getPlatform());
|
|
||||||
|
|
||||||
((FragmentActivity) view.getContext()).getSupportFragmentManager().beginTransaction()
|
((FragmentActivity) view.getContext()).getSupportFragmentManager().beginTransaction()
|
||||||
.add(fragment, GamePropertiesDialog.TAG).commit();
|
.add(fragment, GamePropertiesDialog.TAG).commit();
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,14 @@ import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
import org.dolphinemu.dolphinemu.R;
|
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.Settings;
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting;
|
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting;
|
||||||
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
|
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
|
||||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity;
|
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.ui.platform.Platform;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.AlertDialogItemsBuilder;
|
||||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
|
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
|
||||||
import org.dolphinemu.dolphinemu.utils.Log;
|
import org.dolphinemu.dolphinemu.utils.Log;
|
||||||
|
|
||||||
|
@ -22,21 +25,22 @@ import java.io.File;
|
||||||
public class GamePropertiesDialog extends DialogFragment
|
public class GamePropertiesDialog extends DialogFragment
|
||||||
{
|
{
|
||||||
public static final String TAG = "GamePropertiesDialog";
|
public static final String TAG = "GamePropertiesDialog";
|
||||||
public static final String ARG_PATH = "path";
|
private static final String ARG_PATH = "path";
|
||||||
public static final String ARG_GAMEID = "game_id";
|
private static final String ARG_GAMEID = "game_id";
|
||||||
public static final String ARG_REVISION = "revision";
|
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,
|
public static GamePropertiesDialog newInstance(GameFile gameFile)
|
||||||
int platform)
|
|
||||||
{
|
{
|
||||||
GamePropertiesDialog fragment = new GamePropertiesDialog();
|
GamePropertiesDialog fragment = new GamePropertiesDialog();
|
||||||
|
|
||||||
Bundle arguments = new Bundle();
|
Bundle arguments = new Bundle();
|
||||||
arguments.putString(ARG_PATH, path);
|
arguments.putString(ARG_PATH, gameFile.getPath());
|
||||||
arguments.putString(ARG_GAMEID, gameId);
|
arguments.putString(ARG_GAMEID, gameFile.getGameId());
|
||||||
arguments.putInt(ARG_REVISION, revision);
|
arguments.putInt(ARG_REVISION, gameFile.getRevision());
|
||||||
arguments.putInt(ARG_PLATFORM, platform);
|
arguments.putInt(ARG_PLATFORM, gameFile.getPlatform());
|
||||||
|
arguments.putBoolean(ARG_SHOULD_ALLOW_CONVERSION, gameFile.shouldAllowConversion());
|
||||||
fragment.setArguments(arguments);
|
fragment.setArguments(arguments);
|
||||||
|
|
||||||
return fragment;
|
return fragment;
|
||||||
|
@ -46,58 +50,59 @@ public class GamePropertiesDialog extends DialogFragment
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(),
|
|
||||||
R.style.DolphinDialogBase);
|
|
||||||
|
|
||||||
String path = requireArguments().getString(ARG_PATH);
|
String path = requireArguments().getString(ARG_PATH);
|
||||||
String gameId = requireArguments().getString(ARG_GAMEID);
|
String gameId = requireArguments().getString(ARG_GAMEID);
|
||||||
int revision = requireArguments().getInt(ARG_REVISION);
|
int revision = requireArguments().getInt(ARG_REVISION);
|
||||||
int platform = requireArguments().getInt(ARG_PLATFORM);
|
int platform = requireArguments().getInt(ARG_PLATFORM);
|
||||||
|
boolean shouldAllowConversion = requireArguments().getBoolean(ARG_SHOULD_ALLOW_CONVERSION);
|
||||||
|
|
||||||
builder.setTitle(requireContext()
|
AlertDialogItemsBuilder itemsBuilder = new AlertDialogItemsBuilder(requireContext());
|
||||||
.getString(R.string.preferences_game_properties) + ": " + gameId)
|
|
||||||
.setItems(platform == Platform.GAMECUBE.toInt() ?
|
itemsBuilder.add(R.string.properties_details, (dialog, i) ->
|
||||||
R.array.gameSettingsMenusGC :
|
GameDetailsDialog.newInstance(path).show(requireActivity()
|
||||||
R.array.gameSettingsMenusWii, (dialog, which) ->
|
.getSupportFragmentManager(), "game_details"));
|
||||||
|
|
||||||
|
if (shouldAllowConversion)
|
||||||
{
|
{
|
||||||
switch (which)
|
itemsBuilder.add(R.string.properties_convert, (dialog, i) ->
|
||||||
|
ConvertActivity.launch(getContext(), path));
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsBuilder.add(R.string.properties_set_default_iso, (dialog, i) ->
|
||||||
{
|
{
|
||||||
case 0:
|
|
||||||
GameDetailsDialog.newInstance(path).show((requireActivity())
|
|
||||||
.getSupportFragmentManager(), "game_details");
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
try (Settings settings = new Settings())
|
try (Settings settings = new Settings())
|
||||||
{
|
{
|
||||||
settings.loadSettings(null);
|
settings.loadSettings(null);
|
||||||
StringSetting.MAIN_DEFAULT_ISO.setString(settings, path);
|
StringSetting.MAIN_DEFAULT_ISO.setString(settings, path);
|
||||||
settings.saveSettings(null, getContext());
|
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return builder.create();
|
|
||||||
|
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);
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
private void clearGameSettings(String gameId)
|
private void clearGameSettings(String gameId)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,8 @@ public class GameFile
|
||||||
|
|
||||||
public native int getRevision();
|
public native int getRevision();
|
||||||
|
|
||||||
|
public native int getBlobType();
|
||||||
|
|
||||||
public native String getBlobTypeString();
|
public native String getBlobTypeString();
|
||||||
|
|
||||||
public native long getBlockSize();
|
public native long getBlockSize();
|
||||||
|
@ -46,8 +48,12 @@ public class GameFile
|
||||||
|
|
||||||
public native boolean shouldShowFileFormatDetails();
|
public native boolean shouldShowFileFormatDetails();
|
||||||
|
|
||||||
|
public native boolean shouldAllowConversion();
|
||||||
|
|
||||||
public native long getFileSize();
|
public native long getFileSize();
|
||||||
|
|
||||||
|
public native boolean isDatelDisc();
|
||||||
|
|
||||||
public native int[] getBanner();
|
public native int[] getBanner();
|
||||||
|
|
||||||
public native int getBannerWidth();
|
public native int getBannerWidth();
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.dolphinemu.dolphinemu.utils;
|
||||||
|
|
||||||
|
public interface CompressCallback
|
||||||
|
{
|
||||||
|
boolean run(String text, float completion);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -433,24 +433,6 @@
|
||||||
<item>Classic A</item>
|
<item>Classic A</item>
|
||||||
</string-array>
|
</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">
|
<string-array name="orientationEntries">
|
||||||
<item>Landscape</item>
|
<item>Landscape</item>
|
||||||
<item>Portrait</item>
|
<item>Portrait</item>
|
||||||
|
@ -467,4 +449,147 @@
|
||||||
<item>Use Device Sensors (Without Pointer Emulation)</item>
|
<item>Use Device Sensors (Without Pointer Emulation)</item>
|
||||||
<item>Don\'t Use Device Sensors</item>
|
<item>Don\'t Use Device Sensors</item>
|
||||||
</string-array>
|
</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>
|
</resources>
|
||||||
|
|
|
@ -324,7 +324,15 @@
|
||||||
<string name="wad_install_success">Successfully installed this title to the NAND.</string>
|
<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>
|
<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_save_exit">Save and Exit</string>
|
||||||
<string name="preferences_settings">Settings</string>
|
<string name="preferences_settings">Settings</string>
|
||||||
<string name="preferences_game_properties">Game Properties</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_block_size">Block Size</string>
|
||||||
<string name="game_details_no_compression">No Compression</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 -->
|
<!-- Emulation Menu -->
|
||||||
<string name="pause_emulation">Pause Emulation</string>
|
<string name="pause_emulation">Pause Emulation</string>
|
||||||
<string name="unpause_emulation">Unpause Emulation</string>
|
<string name="unpause_emulation">Unpause Emulation</string>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
#include "jni/AndroidCommon/IDCache.h"
|
||||||
|
|
||||||
std::string GetJString(JNIEnv* env, jstring jstr)
|
std::string GetJString(JNIEnv* env, jstring jstr)
|
||||||
{
|
{
|
||||||
|
@ -40,3 +41,27 @@ std::vector<std::string> JStringArrayToVector(JNIEnv* env, jobjectArray array)
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int OpenAndroidContent(const std::string& uri, const std::string& mode)
|
||||||
|
{
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
const jint fd = env->CallStaticIntMethod(IDCache::GetContentHandlerClass(),
|
||||||
|
IDCache::GetContentHandlerOpenFd(), ToJString(env, uri),
|
||||||
|
ToJString(env, mode));
|
||||||
|
|
||||||
|
// We can get an IllegalArgumentException when passing an invalid mode
|
||||||
|
if (env->ExceptionCheck())
|
||||||
|
{
|
||||||
|
env->ExceptionDescribe();
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeleteAndroidContent(const std::string& uri)
|
||||||
|
{
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
return env->CallStaticBooleanMethod(IDCache::GetContentHandlerClass(),
|
||||||
|
IDCache::GetContentHandlerDelete(), ToJString(env, uri));
|
||||||
|
}
|
||||||
|
|
|
@ -11,3 +11,6 @@
|
||||||
std::string GetJString(JNIEnv* env, jstring jstr);
|
std::string GetJString(JNIEnv* env, jstring jstr);
|
||||||
jstring ToJString(JNIEnv* env, const std::string& str);
|
jstring ToJString(JNIEnv* env, const std::string& str);
|
||||||
std::vector<std::string> JStringArrayToVector(JNIEnv* env, jobjectArray array);
|
std::vector<std::string> JStringArrayToVector(JNIEnv* env, jobjectArray array);
|
||||||
|
|
||||||
|
int OpenAndroidContent(const std::string& uri, const std::string& mode);
|
||||||
|
bool DeleteAndroidContent(const std::string& uri);
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
add_library(androidcommon STATIC
|
||||||
|
AndroidCommon.cpp
|
||||||
|
AndroidCommon.h
|
||||||
|
IDCache.cpp
|
||||||
|
IDCache.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(androidcommon
|
||||||
|
PRIVATE
|
||||||
|
android
|
||||||
|
log
|
||||||
|
"-Wl,--no-warn-mismatch"
|
||||||
|
"-Wl,--whole-archive"
|
||||||
|
"-Wl,--no-whole-archive"
|
||||||
|
)
|
|
@ -36,6 +36,13 @@ static jclass s_ini_file_section_class;
|
||||||
static jfieldID s_ini_file_section_pointer;
|
static jfieldID s_ini_file_section_pointer;
|
||||||
static jmethodID s_ini_file_section_constructor;
|
static jmethodID s_ini_file_section_constructor;
|
||||||
|
|
||||||
|
static jclass s_compress_cb_class;
|
||||||
|
static jmethodID s_compress_cb_run;
|
||||||
|
|
||||||
|
static jclass s_content_handler_class;
|
||||||
|
static jmethodID s_content_handler_open_fd;
|
||||||
|
static jmethodID s_content_handler_delete;
|
||||||
|
|
||||||
namespace IDCache
|
namespace IDCache
|
||||||
{
|
{
|
||||||
JNIEnv* GetEnvForThread()
|
JNIEnv* GetEnvForThread()
|
||||||
|
@ -161,6 +168,31 @@ jmethodID GetIniFileSectionConstructor()
|
||||||
return s_ini_file_section_constructor;
|
return s_ini_file_section_constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jclass GetCompressCallbackClass()
|
||||||
|
{
|
||||||
|
return s_compress_cb_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID GetCompressCallbackRun()
|
||||||
|
{
|
||||||
|
return s_compress_cb_run;
|
||||||
|
}
|
||||||
|
|
||||||
|
jclass GetContentHandlerClass()
|
||||||
|
{
|
||||||
|
return s_content_handler_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID GetContentHandlerOpenFd()
|
||||||
|
{
|
||||||
|
return s_content_handler_open_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID GetContentHandlerDelete()
|
||||||
|
{
|
||||||
|
return s_content_handler_delete;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace IDCache
|
} // namespace IDCache
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -223,6 +255,19 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||||
s_linked_hash_map_put = env->GetMethodID(
|
s_linked_hash_map_put = env->GetMethodID(
|
||||||
s_linked_hash_map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
s_linked_hash_map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
||||||
|
|
||||||
|
const jclass compress_cb_class =
|
||||||
|
env->FindClass("org/dolphinemu/dolphinemu/utils/CompressCallback");
|
||||||
|
s_compress_cb_class = reinterpret_cast<jclass>(env->NewGlobalRef(compress_cb_class));
|
||||||
|
s_compress_cb_run = env->GetMethodID(s_compress_cb_class, "run", "(Ljava/lang/String;F)Z");
|
||||||
|
|
||||||
|
const jclass content_handler_class =
|
||||||
|
env->FindClass("org/dolphinemu/dolphinemu/utils/ContentHandler");
|
||||||
|
s_content_handler_class = reinterpret_cast<jclass>(env->NewGlobalRef(content_handler_class));
|
||||||
|
s_content_handler_open_fd = env->GetStaticMethodID(s_content_handler_class, "openFd",
|
||||||
|
"(Ljava/lang/String;Ljava/lang/String;)I");
|
||||||
|
s_content_handler_delete =
|
||||||
|
env->GetStaticMethodID(s_content_handler_class, "delete", "(Ljava/lang/String;)Z");
|
||||||
|
|
||||||
return JNI_VERSION;
|
return JNI_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,6 +284,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved)
|
||||||
env->DeleteGlobalRef(s_linked_hash_map_class);
|
env->DeleteGlobalRef(s_linked_hash_map_class);
|
||||||
env->DeleteGlobalRef(s_ini_file_class);
|
env->DeleteGlobalRef(s_ini_file_class);
|
||||||
env->DeleteGlobalRef(s_ini_file_section_class);
|
env->DeleteGlobalRef(s_ini_file_section_class);
|
||||||
|
env->DeleteGlobalRef(s_compress_cb_class);
|
||||||
|
env->DeleteGlobalRef(s_content_handler_class);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
@ -38,4 +38,11 @@ jclass GetIniFileSectionClass();
|
||||||
jfieldID GetIniFileSectionPointer();
|
jfieldID GetIniFileSectionPointer();
|
||||||
jmethodID GetIniFileSectionConstructor();
|
jmethodID GetIniFileSectionConstructor();
|
||||||
|
|
||||||
|
jclass GetCompressCallbackClass();
|
||||||
|
jmethodID GetCompressCallbackRun();
|
||||||
|
|
||||||
|
jclass GetContentHandlerClass();
|
||||||
|
jmethodID GetContentHandlerOpenFd();
|
||||||
|
jmethodID GetContentHandlerDelete();
|
||||||
|
|
||||||
} // namespace IDCache
|
} // namespace IDCache
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
add_library(main SHARED
|
add_library(main SHARED
|
||||||
AndroidCommon/AndroidCommon.cpp
|
|
||||||
AndroidCommon/IDCache.cpp
|
|
||||||
GameList/GameFile.cpp
|
GameList/GameFile.cpp
|
||||||
|
GameList/GameFile.h
|
||||||
GameList/GameFileCache.cpp
|
GameList/GameFileCache.cpp
|
||||||
IniFile.cpp
|
IniFile.cpp
|
||||||
MainAndroid.cpp
|
MainAndroid.cpp
|
||||||
|
@ -10,6 +9,7 @@ add_library(main SHARED
|
||||||
|
|
||||||
target_link_libraries(main
|
target_link_libraries(main
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
androidcommon
|
||||||
core
|
core
|
||||||
uicommon
|
uicommon
|
||||||
)
|
)
|
||||||
|
@ -32,3 +32,5 @@ file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/Sys/R
|
||||||
file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/Sys/Themes/)
|
file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/Sys/Themes/)
|
||||||
|
|
||||||
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} main)
|
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} main)
|
||||||
|
|
||||||
|
add_subdirectory(AndroidCommon)
|
||||||
|
|
|
@ -63,6 +63,8 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getDiscNumb
|
||||||
jobject obj);
|
jobject obj);
|
||||||
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRevision(JNIEnv* env,
|
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRevision(JNIEnv* env,
|
||||||
jobject obj);
|
jobject obj);
|
||||||
|
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBlobType(JNIEnv* env,
|
||||||
|
jobject obj);
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_org_dolphinemu_dolphinemu_model_GameFile_getBlobTypeString(JNIEnv* env, jobject obj);
|
Java_org_dolphinemu_dolphinemu_model_GameFile_getBlobTypeString(JNIEnv* env, jobject obj);
|
||||||
JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBlockSize(JNIEnv* env,
|
JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBlockSize(JNIEnv* env,
|
||||||
|
@ -71,8 +73,12 @@ JNIEXPORT jstring JNICALL
|
||||||
Java_org_dolphinemu_dolphinemu_model_GameFile_getCompressionMethod(JNIEnv* env, jobject obj);
|
Java_org_dolphinemu_dolphinemu_model_GameFile_getCompressionMethod(JNIEnv* env, jobject obj);
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_org_dolphinemu_dolphinemu_model_GameFile_shouldShowFileFormatDetails(JNIEnv* env, jobject obj);
|
Java_org_dolphinemu_dolphinemu_model_GameFile_shouldShowFileFormatDetails(JNIEnv* env, jobject obj);
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_org_dolphinemu_dolphinemu_model_GameFile_shouldAllowConversion(JNIEnv* env, jobject obj);
|
||||||
JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getFileSize(JNIEnv* env,
|
JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getFileSize(JNIEnv* env,
|
||||||
jobject obj);
|
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,
|
JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBanner(JNIEnv* env,
|
||||||
jobject obj);
|
jobject obj);
|
||||||
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBannerWidth(JNIEnv* env,
|
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBannerWidth(JNIEnv* env,
|
||||||
|
@ -154,6 +160,12 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRevision
|
||||||
return GetRef(env, obj)->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
|
JNIEXPORT jstring JNICALL
|
||||||
Java_org_dolphinemu_dolphinemu_model_GameFile_getBlobTypeString(JNIEnv* env, jobject obj)
|
Java_org_dolphinemu_dolphinemu_model_GameFile_getBlobTypeString(JNIEnv* env, jobject obj)
|
||||||
{
|
{
|
||||||
|
@ -175,7 +187,13 @@ Java_org_dolphinemu_dolphinemu_model_GameFile_getCompressionMethod(JNIEnv* env,
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_org_dolphinemu_dolphinemu_model_GameFile_shouldShowFileFormatDetails(JNIEnv* env, jobject obj)
|
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 jboolean JNICALL
|
||||||
|
Java_org_dolphinemu_dolphinemu_model_GameFile_shouldAllowConversion(JNIEnv* env, jobject obj)
|
||||||
|
{
|
||||||
|
return static_cast<jboolean>(GetRef(env, obj)->ShouldAllowConversion());
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getFileSize(JNIEnv* env,
|
JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getFileSize(JNIEnv* env,
|
||||||
|
@ -184,6 +202,12 @@ JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getFileSiz
|
||||||
return GetRef(env, obj)->GetFileSize();
|
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,
|
JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBanner(JNIEnv* env,
|
||||||
jobject obj)
|
jobject obj)
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "Common/AndroidAnalytics.h"
|
#include "Common/AndroidAnalytics.h"
|
||||||
|
#include "Common/Assert.h"
|
||||||
#include "Common/CPUDetect.h"
|
#include "Common/CPUDetect.h"
|
||||||
#include "Common/CommonPaths.h"
|
#include "Common/CommonPaths.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
#include "Common/IniFile.h"
|
#include "Common/IniFile.h"
|
||||||
#include "Common/Logging/LogManager.h"
|
#include "Common/Logging/LogManager.h"
|
||||||
#include "Common/MsgHandler.h"
|
#include "Common/MsgHandler.h"
|
||||||
|
#include "Common/ScopeGuard.h"
|
||||||
#include "Common/Version.h"
|
#include "Common/Version.h"
|
||||||
#include "Common/WindowSystemInfo.h"
|
#include "Common/WindowSystemInfo.h"
|
||||||
|
|
||||||
|
@ -45,7 +47,9 @@
|
||||||
#include "Core/State.h"
|
#include "Core/State.h"
|
||||||
#include "Core/WiiUtils.h"
|
#include "Core/WiiUtils.h"
|
||||||
|
|
||||||
|
#include "DiscIO/Blob.h"
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
|
#include "DiscIO/ScrubbedBlob.h"
|
||||||
#include "DiscIO/Volume.h"
|
#include "DiscIO/Volume.h"
|
||||||
|
|
||||||
#include "InputCommon/ControllerInterface/Android/Android.h"
|
#include "InputCommon/ControllerInterface/Android/Android.h"
|
||||||
|
@ -663,6 +667,65 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_InstallW
|
||||||
return static_cast<jboolean>(WiiUtils::InstallWAD(path));
|
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, jobject jCallback)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
jobject jCallbackGlobal = env->NewGlobalRef(jCallback);
|
||||||
|
Common::ScopeGuard scope_guard([jCallbackGlobal, env] { env->DeleteGlobalRef(jCallbackGlobal); });
|
||||||
|
|
||||||
|
const auto callback = [&jCallbackGlobal](const std::string& text, float completion) {
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
return static_cast<bool>(env->CallBooleanMethod(
|
||||||
|
jCallbackGlobal, IDCache::GetCompressCallbackRun(), ToJString(env, text), completion));
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_FormatSize(JNIEnv* env,
|
||||||
jobject obj,
|
jobject obj,
|
||||||
jlong bytes,
|
jlong bytes,
|
||||||
|
|
|
@ -164,6 +164,11 @@ elseif(WIN32)
|
||||||
if (_M_X86_64)
|
if (_M_X86_64)
|
||||||
target_link_libraries(common PRIVATE opengl32.lib)
|
target_link_libraries(common PRIVATE opengl32.lib)
|
||||||
endif()
|
endif()
|
||||||
|
elseif (ANDROID)
|
||||||
|
target_link_libraries(common
|
||||||
|
PRIVATE
|
||||||
|
androidcommon
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
|
|
|
@ -15,6 +15,13 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
|
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/File.h"
|
#include "Common/File.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
|
@ -62,7 +69,21 @@ bool IOFile::Open(const std::string& filename, const char openmode[])
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
m_good = _tfopen_s(&m_file, UTF8ToTStr(filename).c_str(), UTF8ToTStr(openmode).c_str()) == 0;
|
m_good = _tfopen_s(&m_file, UTF8ToTStr(filename).c_str(), UTF8ToTStr(openmode).c_str()) == 0;
|
||||||
#else
|
#else
|
||||||
|
#ifdef ANDROID
|
||||||
|
if (StringBeginsWith(filename, "content://"))
|
||||||
|
{
|
||||||
|
// The Java method which OpenAndroidContent passes the mode to does not support the b specifier.
|
||||||
|
// Since we're on POSIX, it's fine to just remove the b.
|
||||||
|
std::string mode_without_b(openmode);
|
||||||
|
mode_without_b.erase(std::remove(mode_without_b.begin(), mode_without_b.end(), 'b'),
|
||||||
|
mode_without_b.end());
|
||||||
|
m_file = fdopen(OpenAndroidContent(filename, mode_without_b), mode_without_b.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
m_file = std::fopen(filename.c_str(), openmode);
|
m_file = std::fopen(filename.c_str(), openmode);
|
||||||
|
}
|
||||||
m_good = m_file != nullptr;
|
m_good = m_file != nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,11 @@
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
|
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef S_ISDIR
|
#ifndef S_ISDIR
|
||||||
#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
|
#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
|
||||||
#endif
|
#endif
|
||||||
|
@ -132,10 +137,19 @@ bool Delete(const std::string& filename)
|
||||||
{
|
{
|
||||||
INFO_LOG(COMMON, "Delete: file %s", filename.c_str());
|
INFO_LOG(COMMON, "Delete: file %s", filename.c_str());
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
if (StringBeginsWith(filename, "content://"))
|
||||||
|
{
|
||||||
|
const bool success = DeleteAndroidContent(filename);
|
||||||
|
if (!success)
|
||||||
|
WARN_LOG(COMMON, "Delete failed on %s", filename.c_str());
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
const FileInfo file_info(filename);
|
const FileInfo file_info(filename);
|
||||||
|
|
||||||
// Return true because we care about the file no
|
// Return true because we care about the file not being there, not the actual delete.
|
||||||
// being there, not the actual delete.
|
|
||||||
if (!file_info.Exists())
|
if (!file_info.Exists())
|
||||||
{
|
{
|
||||||
WARN_LOG(COMMON, "Delete: %s does not exist", filename.c_str());
|
WARN_LOG(COMMON, "Delete: %s does not exist", filename.c_str());
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
// automatically do the right thing.
|
// automatically do the right thing.
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -175,17 +176,16 @@ private:
|
||||||
// Factory function - examines the path to choose the right type of BlobReader, and returns one.
|
// Factory function - examines the path to choose the right type of BlobReader, and returns one.
|
||||||
std::unique_ptr<BlobReader> CreateBlobReader(const std::string& filename);
|
std::unique_ptr<BlobReader> CreateBlobReader(const std::string& filename);
|
||||||
|
|
||||||
typedef bool (*CompressCB)(const std::string& text, float percent, void* arg);
|
using CompressCB = std::function<bool(const std::string& text, float percent)>;
|
||||||
|
|
||||||
bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
||||||
const std::string& outfile_path, u32 sub_type, int sector_size = 16384,
|
const std::string& outfile_path, u32 sub_type, int sector_size,
|
||||||
CompressCB callback = nullptr, void* arg = nullptr);
|
CompressCB callback);
|
||||||
bool ConvertToPlain(BlobReader* infile, const std::string& infile_path,
|
bool ConvertToPlain(BlobReader* infile, const std::string& infile_path,
|
||||||
const std::string& outfile_path, CompressCB callback = nullptr,
|
const std::string& outfile_path, CompressCB callback);
|
||||||
void* arg = nullptr);
|
|
||||||
bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path,
|
bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path,
|
||||||
const std::string& outfile_path, bool rvz,
|
const std::string& outfile_path, bool rvz,
|
||||||
WIARVZCompressionType compression_type, int compression_level,
|
WIARVZCompressionType compression_type, int compression_level,
|
||||||
int chunk_size, CompressCB callback = nullptr, void* arg = nullptr);
|
int chunk_size, CompressCB callback);
|
||||||
|
|
||||||
} // namespace DiscIO
|
} // namespace DiscIO
|
||||||
|
|
|
@ -238,7 +238,7 @@ static ConversionResult<OutputParameters> Compress(CompressThreadState* state,
|
||||||
|
|
||||||
static ConversionResultCode Output(OutputParameters parameters, File::IOFile* outfile,
|
static ConversionResultCode Output(OutputParameters parameters, File::IOFile* outfile,
|
||||||
u64* position, std::vector<u64>* offsets, int progress_monitor,
|
u64* position, std::vector<u64>* offsets, int progress_monitor,
|
||||||
u32 num_blocks, CompressCB callback, void* arg)
|
u32 num_blocks, CompressCB callback)
|
||||||
{
|
{
|
||||||
u64 offset = *position;
|
u64 offset = *position;
|
||||||
if (!parameters.compressed)
|
if (!parameters.compressed)
|
||||||
|
@ -261,7 +261,7 @@ static ConversionResultCode Output(OutputParameters parameters, File::IOFile* ou
|
||||||
|
|
||||||
const float completion = static_cast<float>(parameters.block_number) / num_blocks;
|
const float completion = static_cast<float>(parameters.block_number) / num_blocks;
|
||||||
|
|
||||||
if (!callback(text, completion, arg))
|
if (!callback(text, completion))
|
||||||
return ConversionResultCode::Canceled;
|
return ConversionResultCode::Canceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +270,7 @@ static ConversionResultCode Output(OutputParameters parameters, File::IOFile* ou
|
||||||
|
|
||||||
bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
||||||
const std::string& outfile_path, u32 sub_type, int block_size,
|
const std::string& outfile_path, u32 sub_type, int block_size,
|
||||||
CompressCB callback, void* arg)
|
CompressCB callback)
|
||||||
{
|
{
|
||||||
ASSERT(infile->IsDataSizeAccurate());
|
ASSERT(infile->IsDataSizeAccurate());
|
||||||
|
|
||||||
|
@ -284,7 +284,7 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(Common::GetStringT("Files opened, ready to compress."), 0, arg);
|
callback(Common::GetStringT("Files opened, ready to compress."), 0);
|
||||||
|
|
||||||
CompressedBlobHeader header;
|
CompressedBlobHeader header;
|
||||||
header.magic_cookie = GCZ_MAGIC;
|
header.magic_cookie = GCZ_MAGIC;
|
||||||
|
@ -317,7 +317,7 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
||||||
|
|
||||||
const auto output = [&](OutputParameters parameters) {
|
const auto output = [&](OutputParameters parameters) {
|
||||||
return Output(std::move(parameters), &outfile, &position, &offsets, progress_monitor,
|
return Output(std::move(parameters), &outfile, &position, &offsets, progress_monitor,
|
||||||
header.num_blocks, callback, arg);
|
header.num_blocks, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
MultithreadedCompressor<CompressThreadState, CompressParameters, OutputParameters> compressor(
|
MultithreadedCompressor<CompressThreadState, CompressParameters, OutputParameters> compressor(
|
||||||
|
@ -364,7 +364,7 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
||||||
outfile.WriteArray(offsets.data(), header.num_blocks);
|
outfile.WriteArray(offsets.data(), header.num_blocks);
|
||||||
outfile.WriteArray(hashes.data(), header.num_blocks);
|
outfile.WriteArray(hashes.data(), header.num_blocks);
|
||||||
|
|
||||||
callback(Common::GetStringT("Done compressing disc image."), 1.0f, arg);
|
callback(Common::GetStringT("Done compressing disc image."), 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == ConversionResultCode::ReadFailed)
|
if (result == ConversionResultCode::ReadFailed)
|
||||||
|
|
|
@ -42,7 +42,7 @@ bool PlainFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConvertToPlain(BlobReader* infile, const std::string& infile_path,
|
bool ConvertToPlain(BlobReader* infile, const std::string& infile_path,
|
||||||
const std::string& outfile_path, CompressCB callback, void* arg)
|
const std::string& outfile_path, CompressCB callback)
|
||||||
{
|
{
|
||||||
ASSERT(infile->IsDataSizeAccurate());
|
ASSERT(infile->IsDataSizeAccurate());
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ bool ConvertToPlain(BlobReader* infile, const std::string& infile_path,
|
||||||
if (i % progress_monitor == 0)
|
if (i % progress_monitor == 0)
|
||||||
{
|
{
|
||||||
const bool was_cancelled =
|
const bool was_cancelled =
|
||||||
!callback(Common::GetStringT("Unpacking"), (float)i / (float)num_buffers, arg);
|
!callback(Common::GetStringT("Unpacking"), (float)i / (float)num_buffers);
|
||||||
if (was_cancelled)
|
if (was_cancelled)
|
||||||
{
|
{
|
||||||
success = false;
|
success = false;
|
||||||
|
|
|
@ -1686,9 +1686,9 @@ ConversionResultCode WIARVZFileReader<RVZ>::Output(std::vector<OutputParametersE
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool RVZ>
|
template <bool RVZ>
|
||||||
ConversionResultCode
|
ConversionResultCode WIARVZFileReader<RVZ>::RunCallback(size_t groups_written, u64 bytes_read,
|
||||||
WIARVZFileReader<RVZ>::RunCallback(size_t groups_written, u64 bytes_read, u64 bytes_written,
|
u64 bytes_written, u32 total_groups,
|
||||||
u32 total_groups, u64 iso_size, CompressCB callback, void* arg)
|
u64 iso_size, CompressCB callback)
|
||||||
{
|
{
|
||||||
int ratio = 0;
|
int ratio = 0;
|
||||||
if (bytes_read != 0)
|
if (bytes_read != 0)
|
||||||
|
@ -1700,7 +1700,7 @@ WIARVZFileReader<RVZ>::RunCallback(size_t groups_written, u64 bytes_read, u64 by
|
||||||
|
|
||||||
const float completion = static_cast<float>(bytes_read) / iso_size;
|
const float completion = static_cast<float>(bytes_read) / iso_size;
|
||||||
|
|
||||||
return callback(text, completion, arg) ? ConversionResultCode::Success :
|
return callback(text, completion) ? ConversionResultCode::Success :
|
||||||
ConversionResultCode::Canceled;
|
ConversionResultCode::Canceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1729,8 +1729,7 @@ template <bool RVZ>
|
||||||
ConversionResultCode
|
ConversionResultCode
|
||||||
WIARVZFileReader<RVZ>::Convert(BlobReader* infile, const VolumeDisc* infile_volume,
|
WIARVZFileReader<RVZ>::Convert(BlobReader* infile, const VolumeDisc* infile_volume,
|
||||||
File::IOFile* outfile, WIARVZCompressionType compression_type,
|
File::IOFile* outfile, WIARVZCompressionType compression_type,
|
||||||
int compression_level, int chunk_size, CompressCB callback,
|
int compression_level, int chunk_size, CompressCB callback)
|
||||||
void* arg)
|
|
||||||
{
|
{
|
||||||
ASSERT(infile->IsDataSizeAccurate());
|
ASSERT(infile->IsDataSizeAccurate());
|
||||||
ASSERT(chunk_size > 0);
|
ASSERT(chunk_size > 0);
|
||||||
|
@ -1832,7 +1831,7 @@ WIARVZFileReader<RVZ>::Convert(BlobReader* infile, const VolumeDisc* infile_volu
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
return RunCallback(parameters.group_index + parameters.entries.size(), parameters.bytes_read,
|
return RunCallback(parameters.group_index + parameters.entries.size(), parameters.bytes_read,
|
||||||
bytes_written, total_groups, iso_size, callback, arg);
|
bytes_written, total_groups, iso_size, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
MultithreadedCompressor<CompressThreadState, CompressParameters, OutputParameters> mt_compressor(
|
MultithreadedCompressor<CompressThreadState, CompressParameters, OutputParameters> mt_compressor(
|
||||||
|
@ -2030,7 +2029,7 @@ WIARVZFileReader<RVZ>::Convert(BlobReader* infile, const VolumeDisc* infile_volu
|
||||||
bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path,
|
bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path,
|
||||||
const std::string& outfile_path, bool rvz,
|
const std::string& outfile_path, bool rvz,
|
||||||
WIARVZCompressionType compression_type, int compression_level,
|
WIARVZCompressionType compression_type, int compression_level,
|
||||||
int chunk_size, CompressCB callback, void* arg)
|
int chunk_size, CompressCB callback)
|
||||||
{
|
{
|
||||||
File::IOFile outfile(outfile_path, "wb");
|
File::IOFile outfile(outfile_path, "wb");
|
||||||
if (!outfile)
|
if (!outfile)
|
||||||
|
@ -2047,7 +2046,7 @@ bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path,
|
||||||
const auto convert = rvz ? RVZFileReader::Convert : WIAFileReader::Convert;
|
const auto convert = rvz ? RVZFileReader::Convert : WIAFileReader::Convert;
|
||||||
const ConversionResultCode result =
|
const ConversionResultCode result =
|
||||||
convert(infile, infile_volume.get(), &outfile, compression_type, compression_level,
|
convert(infile, infile_volume.get(), &outfile, compression_type, compression_level,
|
||||||
chunk_size, callback, arg);
|
chunk_size, callback);
|
||||||
|
|
||||||
if (result == ConversionResultCode::ReadFailed)
|
if (result == ConversionResultCode::ReadFailed)
|
||||||
PanicAlertT("Failed to read from the input file \"%s\".", infile_path.c_str());
|
PanicAlertT("Failed to read from the input file \"%s\".", infile_path.c_str());
|
||||||
|
|
|
@ -64,8 +64,7 @@ public:
|
||||||
|
|
||||||
static ConversionResultCode Convert(BlobReader* infile, const VolumeDisc* infile_volume,
|
static ConversionResultCode Convert(BlobReader* infile, const VolumeDisc* infile_volume,
|
||||||
File::IOFile* outfile, WIARVZCompressionType compression_type,
|
File::IOFile* outfile, WIARVZCompressionType compression_type,
|
||||||
int compression_level, int chunk_size, CompressCB callback,
|
int compression_level, int chunk_size, CompressCB callback);
|
||||||
void* arg);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using SHA1 = std::array<u8, 20>;
|
using SHA1 = std::array<u8, 20>;
|
||||||
|
@ -351,8 +350,7 @@ private:
|
||||||
std::mutex* reusable_groups_mutex, GroupEntry* group_entry,
|
std::mutex* reusable_groups_mutex, GroupEntry* group_entry,
|
||||||
u64* bytes_written);
|
u64* bytes_written);
|
||||||
static ConversionResultCode RunCallback(size_t groups_written, u64 bytes_read, u64 bytes_written,
|
static ConversionResultCode RunCallback(size_t groups_written, u64 bytes_read, u64 bytes_written,
|
||||||
u32 total_groups, u64 iso_size, CompressCB callback,
|
u32 total_groups, u64 iso_size, CompressCB callback);
|
||||||
void* arg);
|
|
||||||
|
|
||||||
bool m_valid;
|
bool m_valid;
|
||||||
WIARVZCompressionType m_compression_type;
|
WIARVZCompressionType m_compression_type;
|
||||||
|
|
|
@ -32,17 +32,6 @@
|
||||||
#include "UICommon/GameFile.h"
|
#include "UICommon/GameFile.h"
|
||||||
#include "UICommon/UICommon.h"
|
#include "UICommon/UICommon.h"
|
||||||
|
|
||||||
static bool CompressCB(const std::string& text, float percent, void* ptr)
|
|
||||||
{
|
|
||||||
if (ptr == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto* progress_dialog = static_cast<ParallelProgressDialog*>(ptr);
|
|
||||||
|
|
||||||
progress_dialog->SetValue(percent * 100);
|
|
||||||
return !progress_dialog->WasCanceled();
|
|
||||||
}
|
|
||||||
|
|
||||||
ConvertDialog::ConvertDialog(QList<std::shared_ptr<const UICommon::GameFile>> files,
|
ConvertDialog::ConvertDialog(QList<std::shared_ptr<const UICommon::GameFile>> files,
|
||||||
QWidget* parent)
|
QWidget* parent)
|
||||||
: QDialog(parent), m_files(std::move(files))
|
: QDialog(parent), m_files(std::move(files))
|
||||||
|
@ -463,15 +452,19 @@ void ConvertDialog::Convert()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
const auto callback = [&progress_dialog](const std::string& text, float percent) {
|
||||||
|
progress_dialog.SetValue(percent * 100);
|
||||||
|
return !progress_dialog.WasCanceled();
|
||||||
|
};
|
||||||
|
|
||||||
std::future<bool> success;
|
std::future<bool> success;
|
||||||
|
|
||||||
switch (format)
|
switch (format)
|
||||||
{
|
{
|
||||||
case DiscIO::BlobType::PLAIN:
|
case DiscIO::BlobType::PLAIN:
|
||||||
success = std::async(std::launch::async, [&] {
|
success = std::async(std::launch::async, [&] {
|
||||||
const bool good =
|
const bool good = DiscIO::ConvertToPlain(blob_reader.get(), original_path,
|
||||||
DiscIO::ConvertToPlain(blob_reader.get(), original_path, dst_path.toStdString(),
|
dst_path.toStdString(), callback);
|
||||||
&CompressCB, &progress_dialog);
|
|
||||||
progress_dialog.Reset();
|
progress_dialog.Reset();
|
||||||
return good;
|
return good;
|
||||||
});
|
});
|
||||||
|
@ -479,10 +472,9 @@ void ConvertDialog::Convert()
|
||||||
|
|
||||||
case DiscIO::BlobType::GCZ:
|
case DiscIO::BlobType::GCZ:
|
||||||
success = std::async(std::launch::async, [&] {
|
success = std::async(std::launch::async, [&] {
|
||||||
const bool good =
|
const bool good = DiscIO::ConvertToGCZ(
|
||||||
DiscIO::ConvertToGCZ(blob_reader.get(), original_path, dst_path.toStdString(),
|
blob_reader.get(), original_path, dst_path.toStdString(),
|
||||||
file->GetPlatform() == DiscIO::Platform::WiiDisc ? 1 : 0,
|
file->GetPlatform() == DiscIO::Platform::WiiDisc ? 1 : 0, block_size, callback);
|
||||||
block_size, &CompressCB, &progress_dialog);
|
|
||||||
progress_dialog.Reset();
|
progress_dialog.Reset();
|
||||||
return good;
|
return good;
|
||||||
});
|
});
|
||||||
|
@ -491,10 +483,10 @@ void ConvertDialog::Convert()
|
||||||
case DiscIO::BlobType::WIA:
|
case DiscIO::BlobType::WIA:
|
||||||
case DiscIO::BlobType::RVZ:
|
case DiscIO::BlobType::RVZ:
|
||||||
success = std::async(std::launch::async, [&] {
|
success = std::async(std::launch::async, [&] {
|
||||||
const bool good = DiscIO::ConvertToWIAOrRVZ(
|
const bool good =
|
||||||
blob_reader.get(), original_path, dst_path.toStdString(),
|
DiscIO::ConvertToWIAOrRVZ(blob_reader.get(), original_path, dst_path.toStdString(),
|
||||||
format == DiscIO::BlobType::RVZ, compression, compression_level, block_size,
|
format == DiscIO::BlobType::RVZ, compression,
|
||||||
&CompressCB, &progress_dialog);
|
compression_level, block_size, callback);
|
||||||
progress_dialog.Reset();
|
progress_dialog.Reset();
|
||||||
return good;
|
return good;
|
||||||
});
|
});
|
||||||
|
|
|
@ -267,9 +267,8 @@ void GameList::ShowContextMenu(const QPoint&)
|
||||||
{
|
{
|
||||||
const auto selected_games = GetSelectedGames();
|
const auto selected_games = GetSelectedGames();
|
||||||
|
|
||||||
if (std::all_of(selected_games.begin(), selected_games.end(), [](const auto& game) {
|
if (std::all_of(selected_games.begin(), selected_games.end(),
|
||||||
return DiscIO::IsDisc(game->GetPlatform()) && game->IsVolumeSizeAccurate();
|
[](const auto& game) { return game->ShouldAllowConversion(); }))
|
||||||
}))
|
|
||||||
{
|
{
|
||||||
menu->addAction(tr("Convert Selected Files..."), this, &GameList::ConvertFile);
|
menu->addAction(tr("Convert Selected Files..."), this, &GameList::ConvertFile);
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
@ -301,7 +300,7 @@ void GameList::ShowContextMenu(const QPoint&)
|
||||||
{
|
{
|
||||||
menu->addAction(tr("Set as &Default ISO"), this, &GameList::SetDefaultISO);
|
menu->addAction(tr("Set as &Default ISO"), this, &GameList::SetDefaultISO);
|
||||||
|
|
||||||
if (game->IsVolumeSizeAccurate())
|
if (game->ShouldAllowConversion())
|
||||||
menu->addAction(tr("Convert File..."), this, &GameList::ConvertFile);
|
menu->addAction(tr("Convert File..."), this, &GameList::ConvertFile);
|
||||||
|
|
||||||
QAction* change_disc = menu->addAction(tr("Change &Disc"), this, &GameList::ChangeDisc);
|
QAction* change_disc = menu->addAction(tr("Change &Disc"), this, &GameList::ChangeDisc);
|
||||||
|
|
|
@ -672,6 +672,11 @@ std::string GameFile::GetFileFormatName() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GameFile::ShouldAllowConversion() const
|
||||||
|
{
|
||||||
|
return DiscIO::IsDisc(m_platform) && m_volume_size_is_accurate;
|
||||||
|
}
|
||||||
|
|
||||||
const GameBanner& GameFile::GetBannerImage() const
|
const GameBanner& GameFile::GetBannerImage() const
|
||||||
{
|
{
|
||||||
return m_custom_banner.empty() ? m_volume_banner : m_custom_banner;
|
return m_custom_banner.empty() ? m_volume_banner : m_custom_banner;
|
||||||
|
|
|
@ -101,6 +101,7 @@ public:
|
||||||
const std::string& GetCompressionMethod() const { return m_compression_method; }
|
const std::string& GetCompressionMethod() const { return m_compression_method; }
|
||||||
bool ShouldShowFileFormatDetails() const;
|
bool ShouldShowFileFormatDetails() const;
|
||||||
std::string GetFileFormatName() const;
|
std::string GetFileFormatName() const;
|
||||||
|
bool ShouldAllowConversion() const;
|
||||||
const std::string& GetApploaderDate() const { return m_apploader_date; }
|
const std::string& GetApploaderDate() const { return m_apploader_date; }
|
||||||
u64 GetFileSize() const { return m_file_size; }
|
u64 GetFileSize() const { return m_file_size; }
|
||||||
u64 GetVolumeSize() const { return m_volume_size; }
|
u64 GetVolumeSize() const { return m_volume_size; }
|
||||||
|
|
Loading…
Reference in New Issue