From ca46028cdebdd379528245c6551f3ca83a1b9c55 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 26 Jun 2020 18:35:09 +0200 Subject: [PATCH 01/10] DiscIO: Use std::function for compression callback --- Source/Core/DiscIO/Blob.h | 12 ++++----- Source/Core/DiscIO/CompressedBlob.cpp | 12 ++++----- Source/Core/DiscIO/FileBlob.cpp | 4 +-- Source/Core/DiscIO/WIABlob.cpp | 19 +++++++------ Source/Core/DiscIO/WIABlob.h | 6 ++--- Source/Core/DolphinQt/ConvertDialog.cpp | 36 ++++++++++--------------- 6 files changed, 39 insertions(+), 50 deletions(-) diff --git a/Source/Core/DiscIO/Blob.h b/Source/Core/DiscIO/Blob.h index 473aa9bfce..2166d8c677 100644 --- a/Source/Core/DiscIO/Blob.h +++ b/Source/Core/DiscIO/Blob.h @@ -15,6 +15,7 @@ // automatically do the right thing. #include +#include #include #include #include @@ -175,17 +176,16 @@ private: // Factory function - examines the path to choose the right type of BlobReader, and returns one. std::unique_ptr CreateBlobReader(const std::string& filename); -typedef bool (*CompressCB)(const std::string& text, float percent, void* arg); +using CompressCB = std::function; bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path, - const std::string& outfile_path, u32 sub_type, int sector_size = 16384, - CompressCB callback = nullptr, void* arg = nullptr); + const std::string& outfile_path, u32 sub_type, int sector_size, + CompressCB callback); bool ConvertToPlain(BlobReader* infile, const std::string& infile_path, - const std::string& outfile_path, CompressCB callback = nullptr, - void* arg = nullptr); + const std::string& outfile_path, CompressCB callback); bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path, const std::string& outfile_path, bool rvz, WIARVZCompressionType compression_type, int compression_level, - int chunk_size, CompressCB callback = nullptr, void* arg = nullptr); + int chunk_size, CompressCB callback); } // namespace DiscIO diff --git a/Source/Core/DiscIO/CompressedBlob.cpp b/Source/Core/DiscIO/CompressedBlob.cpp index fdfd65baf8..4486b54827 100644 --- a/Source/Core/DiscIO/CompressedBlob.cpp +++ b/Source/Core/DiscIO/CompressedBlob.cpp @@ -238,7 +238,7 @@ static ConversionResult Compress(CompressThreadState* state, static ConversionResultCode Output(OutputParameters parameters, File::IOFile* outfile, u64* position, std::vector* offsets, int progress_monitor, - u32 num_blocks, CompressCB callback, void* arg) + u32 num_blocks, CompressCB callback) { u64 offset = *position; if (!parameters.compressed) @@ -261,7 +261,7 @@ static ConversionResultCode Output(OutputParameters parameters, File::IOFile* ou const float completion = static_cast(parameters.block_number) / num_blocks; - if (!callback(text, completion, arg)) + if (!callback(text, completion)) return ConversionResultCode::Canceled; } @@ -270,7 +270,7 @@ static ConversionResultCode Output(OutputParameters parameters, File::IOFile* ou bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path, const std::string& outfile_path, u32 sub_type, int block_size, - CompressCB callback, void* arg) + CompressCB callback) { ASSERT(infile->IsDataSizeAccurate()); @@ -284,7 +284,7 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path, return false; } - callback(Common::GetStringT("Files opened, ready to compress."), 0, arg); + callback(Common::GetStringT("Files opened, ready to compress."), 0); CompressedBlobHeader header; header.magic_cookie = GCZ_MAGIC; @@ -317,7 +317,7 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path, const auto output = [&](OutputParameters parameters) { return Output(std::move(parameters), &outfile, &position, &offsets, progress_monitor, - header.num_blocks, callback, arg); + header.num_blocks, callback); }; MultithreadedCompressor compressor( @@ -364,7 +364,7 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path, outfile.WriteArray(offsets.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) diff --git a/Source/Core/DiscIO/FileBlob.cpp b/Source/Core/DiscIO/FileBlob.cpp index ca0aa2cfd0..980b158dc9 100644 --- a/Source/Core/DiscIO/FileBlob.cpp +++ b/Source/Core/DiscIO/FileBlob.cpp @@ -42,7 +42,7 @@ bool PlainFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr) } 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()); @@ -78,7 +78,7 @@ bool ConvertToPlain(BlobReader* infile, const std::string& infile_path, if (i % progress_monitor == 0) { 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) { success = false; diff --git a/Source/Core/DiscIO/WIABlob.cpp b/Source/Core/DiscIO/WIABlob.cpp index 51a9c65694..4d4b104242 100644 --- a/Source/Core/DiscIO/WIABlob.cpp +++ b/Source/Core/DiscIO/WIABlob.cpp @@ -1686,9 +1686,9 @@ ConversionResultCode WIARVZFileReader::Output(std::vector -ConversionResultCode -WIARVZFileReader::RunCallback(size_t groups_written, u64 bytes_read, u64 bytes_written, - u32 total_groups, u64 iso_size, CompressCB callback, void* arg) +ConversionResultCode WIARVZFileReader::RunCallback(size_t groups_written, u64 bytes_read, + u64 bytes_written, u32 total_groups, + u64 iso_size, CompressCB callback) { int ratio = 0; if (bytes_read != 0) @@ -1700,8 +1700,8 @@ WIARVZFileReader::RunCallback(size_t groups_written, u64 bytes_read, u64 by const float completion = static_cast(bytes_read) / iso_size; - return callback(text, completion, arg) ? ConversionResultCode::Success : - ConversionResultCode::Canceled; + return callback(text, completion) ? ConversionResultCode::Success : + ConversionResultCode::Canceled; } template @@ -1729,8 +1729,7 @@ template ConversionResultCode WIARVZFileReader::Convert(BlobReader* infile, const VolumeDisc* infile_volume, File::IOFile* outfile, WIARVZCompressionType compression_type, - int compression_level, int chunk_size, CompressCB callback, - void* arg) + int compression_level, int chunk_size, CompressCB callback) { ASSERT(infile->IsDataSizeAccurate()); ASSERT(chunk_size > 0); @@ -1832,7 +1831,7 @@ WIARVZFileReader::Convert(BlobReader* infile, const VolumeDisc* infile_volu return result; 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 mt_compressor( @@ -2030,7 +2029,7 @@ WIARVZFileReader::Convert(BlobReader* infile, const VolumeDisc* infile_volu bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path, const std::string& outfile_path, bool rvz, WIARVZCompressionType compression_type, int compression_level, - int chunk_size, CompressCB callback, void* arg) + int chunk_size, CompressCB callback) { File::IOFile outfile(outfile_path, "wb"); if (!outfile) @@ -2047,7 +2046,7 @@ bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path, const auto convert = rvz ? RVZFileReader::Convert : WIAFileReader::Convert; const ConversionResultCode result = convert(infile, infile_volume.get(), &outfile, compression_type, compression_level, - chunk_size, callback, arg); + chunk_size, callback); if (result == ConversionResultCode::ReadFailed) PanicAlertT("Failed to read from the input file \"%s\".", infile_path.c_str()); diff --git a/Source/Core/DiscIO/WIABlob.h b/Source/Core/DiscIO/WIABlob.h index c76af169f2..5b08dd227c 100644 --- a/Source/Core/DiscIO/WIABlob.h +++ b/Source/Core/DiscIO/WIABlob.h @@ -64,8 +64,7 @@ public: static ConversionResultCode Convert(BlobReader* infile, const VolumeDisc* infile_volume, File::IOFile* outfile, WIARVZCompressionType compression_type, - int compression_level, int chunk_size, CompressCB callback, - void* arg); + int compression_level, int chunk_size, CompressCB callback); private: using SHA1 = std::array; @@ -351,8 +350,7 @@ private: std::mutex* reusable_groups_mutex, GroupEntry* group_entry, u64* bytes_written); static ConversionResultCode RunCallback(size_t groups_written, u64 bytes_read, u64 bytes_written, - u32 total_groups, u64 iso_size, CompressCB callback, - void* arg); + u32 total_groups, u64 iso_size, CompressCB callback); bool m_valid; WIARVZCompressionType m_compression_type; diff --git a/Source/Core/DolphinQt/ConvertDialog.cpp b/Source/Core/DolphinQt/ConvertDialog.cpp index 5a41ccea36..a14e728c40 100644 --- a/Source/Core/DolphinQt/ConvertDialog.cpp +++ b/Source/Core/DolphinQt/ConvertDialog.cpp @@ -32,17 +32,6 @@ #include "UICommon/GameFile.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(ptr); - - progress_dialog->SetValue(percent * 100); - return !progress_dialog->WasCanceled(); -} - ConvertDialog::ConvertDialog(QList> files, QWidget* parent) : QDialog(parent), m_files(std::move(files)) @@ -463,15 +452,19 @@ void ConvertDialog::Convert() } else { + const auto callback = [&progress_dialog](const std::string& text, float percent) { + progress_dialog.SetValue(percent * 100); + return !progress_dialog.WasCanceled(); + }; + std::future success; switch (format) { case DiscIO::BlobType::PLAIN: success = std::async(std::launch::async, [&] { - const bool good = - DiscIO::ConvertToPlain(blob_reader.get(), original_path, dst_path.toStdString(), - &CompressCB, &progress_dialog); + const bool good = DiscIO::ConvertToPlain(blob_reader.get(), original_path, + dst_path.toStdString(), callback); progress_dialog.Reset(); return good; }); @@ -479,10 +472,9 @@ void ConvertDialog::Convert() case DiscIO::BlobType::GCZ: success = std::async(std::launch::async, [&] { - const bool good = - DiscIO::ConvertToGCZ(blob_reader.get(), original_path, dst_path.toStdString(), - file->GetPlatform() == DiscIO::Platform::WiiDisc ? 1 : 0, - block_size, &CompressCB, &progress_dialog); + const bool good = DiscIO::ConvertToGCZ( + blob_reader.get(), original_path, dst_path.toStdString(), + file->GetPlatform() == DiscIO::Platform::WiiDisc ? 1 : 0, block_size, callback); progress_dialog.Reset(); return good; }); @@ -491,10 +483,10 @@ void ConvertDialog::Convert() case DiscIO::BlobType::WIA: case DiscIO::BlobType::RVZ: success = std::async(std::launch::async, [&] { - const bool good = DiscIO::ConvertToWIAOrRVZ( - blob_reader.get(), original_path, dst_path.toStdString(), - format == DiscIO::BlobType::RVZ, compression, compression_level, block_size, - &CompressCB, &progress_dialog); + const bool good = + DiscIO::ConvertToWIAOrRVZ(blob_reader.get(), original_path, dst_path.toStdString(), + format == DiscIO::BlobType::RVZ, compression, + compression_level, block_size, callback); progress_dialog.Reset(); return good; }); From 7d6debb90793dbd34df49d779ea2c34e432094d8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 25 Jun 2020 00:02:02 +0200 Subject: [PATCH 02/10] Android: Add disc image conversion --- .../Android/app/src/main/AndroidManifest.xml | 4 + .../dolphinemu/dolphinemu/NativeLibrary.java | 3 + .../activities/ConvertActivity.java | 40 ++ .../dialogs/GamePropertiesDialog.java | 14 +- .../dolphinemu/fragments/ConvertFragment.java | 371 ++++++++++++++++++ .../dolphinemu/dolphinemu/model/GameFile.java | 4 + .../layout-w680dp-land/activity_convert.xml | 50 +++ .../src/main/res/layout/activity_convert.xml | 48 +++ .../src/main/res/layout/fragment_convert.xml | 123 ++++++ .../app/src/main/res/values/arrays.xml | 145 +++++++ .../app/src/main/res/values/strings.xml | 23 ++ Source/Android/jni/GameList/GameFile.cpp | 18 +- Source/Android/jni/MainAndroid.cpp | 55 +++ 13 files changed, 892 insertions(+), 6 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java create mode 100644 Source/Android/app/src/main/res/layout-w680dp-land/activity_convert.xml create mode 100644 Source/Android/app/src/main/res/layout/activity_convert.xml create mode 100644 Source/Android/app/src/main/res/layout/fragment_convert.xml diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml index 7298c10137..56d6eadfbf 100644 --- a/Source/Android/app/src/main/AndroidManifest.xml +++ b/Source/Android/app/src/main/AndroidManifest.xml @@ -96,6 +96,10 @@ + + mCallbacks = new ArrayList<>(); + + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long id) + { + if (mCurrentPosition != position) + setPosition(position); + } + + @Override + public void onNothingSelected(AdapterView adapterView) + { + mCurrentPosition = -1; + } + + int getPosition() + { + return mCurrentPosition; + } + + void setPosition(int position) + { + mCurrentPosition = position; + for (Runnable callback : mCallbacks) + callback.run(); + } + + void populate(int valuesId) + { + mValuesId = valuesId; + } + + boolean hasValues() + { + return mValuesId != -1; + } + + int getValue(Context context) + { + return context.getResources().getIntArray(mValuesId)[mCurrentPosition]; + } + + int getValueOr(Context context, int defaultValue) + { + return hasValues() ? getValue(context) : defaultValue; + } + + void addCallback(Runnable callback) + { + mCallbacks.add(callback); + } + } + + private static final String ARG_GAME_PATH = "game_path"; + + private static final String KEY_FORMAT = "convert_format"; + private static final String KEY_BLOCK_SIZE = "convert_block_size"; + private static final String KEY_COMPRESSION = "convert_compression"; + private static final String KEY_COMPRESSION_LEVEL = "convert_compression_level"; + private static final String KEY_REMOVE_JUNK_DATA = "remove_junk_data"; + + private static final int BLOB_TYPE_PLAIN = 0; + private static final int BLOB_TYPE_GCZ = 3; + private static final int BLOB_TYPE_WIA = 7; + private static final int BLOB_TYPE_RVZ = 8; + + private static final int COMPRESSION_NONE = 0; + private static final int COMPRESSION_PURGE = 1; + private static final int COMPRESSION_BZIP2 = 2; + private static final int COMPRESSION_LZMA = 3; + private static final int COMPRESSION_LZMA2 = 4; + private static final int COMPRESSION_ZSTD = 5; + + private SpinnerValue mFormat = new SpinnerValue(); + private SpinnerValue mBlockSize = new SpinnerValue(); + private SpinnerValue mCompression = new SpinnerValue(); + private SpinnerValue mCompressionLevel = new SpinnerValue(); + + private GameFile gameFile; + + public static ConvertFragment newInstance(String gamePath) + { + Bundle args = new Bundle(); + args.putString(ARG_GAME_PATH, gamePath); + + ConvertFragment fragment = new ConvertFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + gameFile = GameFileCacheService.addOrGet(requireArguments().getString(ARG_GAME_PATH)); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_convert, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) + { + populateFormats(); + populateBlockSize(); + populateCompression(); + populateCompressionLevel(); + populateRemoveJunkData(); + + mFormat.addCallback(this::populateBlockSize); + mFormat.addCallback(this::populateCompression); + mCompression.addCallback(this::populateCompressionLevel); + mFormat.addCallback(this::populateRemoveJunkData); + + view.findViewById(R.id.button_convert).setOnClickListener(this); + + if (savedInstanceState != null) + { + setSpinnerSelection(R.id.spinner_format, mFormat, savedInstanceState.getInt(KEY_FORMAT)); + setSpinnerSelection(R.id.spinner_block_size, mBlockSize, + savedInstanceState.getInt(KEY_BLOCK_SIZE)); + setSpinnerSelection(R.id.spinner_compression, mCompression, + savedInstanceState.getInt(KEY_COMPRESSION)); + setSpinnerSelection(R.id.spinner_compression_level, mCompressionLevel, + savedInstanceState.getInt(KEY_COMPRESSION_LEVEL)); + + CheckBox removeJunkData = requireView().findViewById(R.id.checkbox_remove_junk_data); + removeJunkData.setChecked(savedInstanceState.getBoolean(KEY_REMOVE_JUNK_DATA)); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) + { + outState.putInt(KEY_FORMAT, mFormat.getPosition()); + outState.putInt(KEY_BLOCK_SIZE, mBlockSize.getPosition()); + outState.putInt(KEY_COMPRESSION, mCompression.getPosition()); + outState.putInt(KEY_COMPRESSION_LEVEL, mCompressionLevel.getPosition()); + + CheckBox removeJunkData = requireView().findViewById(R.id.checkbox_remove_junk_data); + outState.putBoolean(KEY_REMOVE_JUNK_DATA, removeJunkData.isChecked()); + } + + private void setSpinnerSelection(int id, SpinnerValue valueWrapper, int i) + { + ((Spinner) requireView().findViewById(id)).setSelection(i); + valueWrapper.setPosition(i); + } + + private Spinner populateSpinner(int spinnerId, int entriesId, int valuesId, + SpinnerValue valueWrapper) + { + Spinner spinner = requireView().findViewById(spinnerId); + + ArrayAdapter adapter = ArrayAdapter.createFromResource(requireContext(), + entriesId, android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + + spinner.setEnabled(spinner.getCount() > 1); + + valueWrapper.populate(valuesId); + valueWrapper.setPosition(spinner.getSelectedItemPosition()); + spinner.setOnItemSelectedListener(valueWrapper); + + return spinner; + } + + private Spinner clearSpinner(int spinnerId, SpinnerValue valueWrapper) + { + Spinner spinner = requireView().findViewById(spinnerId); + + spinner.setAdapter(null); + + spinner.setEnabled(false); + + valueWrapper.populate(-1); + valueWrapper.setPosition(-1); + spinner.setOnItemSelectedListener(valueWrapper); + + return spinner; + } + + private void populateFormats() + { + Spinner spinner = populateSpinner(R.id.spinner_format, R.array.convertFormatEntries, + R.array.convertFormatValues, mFormat); + + if (gameFile.getBlobType() == BLOB_TYPE_PLAIN) + spinner.setSelection(spinner.getCount() - 1); + } + + private void populateBlockSize() + { + switch (mFormat.getValue(requireContext())) + { + case BLOB_TYPE_GCZ: + // In the equivalent DolphinQt code, we have some logic for avoiding block sizes that can + // trigger bugs in Dolphin versions older than 5.0-11893, but it was too annoying to port. + // TODO: Port it? + populateSpinner(R.id.spinner_block_size, R.array.convertBlockSizeGczEntries, + R.array.convertBlockSizeGczValues, mBlockSize); + break; + case BLOB_TYPE_WIA: + populateSpinner(R.id.spinner_block_size, R.array.convertBlockSizeWiaEntries, + R.array.convertBlockSizeWiaValues, mBlockSize); + break; + case BLOB_TYPE_RVZ: + populateSpinner(R.id.spinner_block_size, R.array.convertBlockSizeRvzEntries, + R.array.convertBlockSizeRvzValues, mBlockSize).setSelection(2); + break; + default: + clearSpinner(R.id.spinner_block_size, mBlockSize); + } + } + + private void populateCompression() + { + switch (mFormat.getValue(requireContext())) + { + case BLOB_TYPE_GCZ: + populateSpinner(R.id.spinner_compression, R.array.convertCompressionGczEntries, + R.array.convertCompressionGczValues, mCompression); + break; + case BLOB_TYPE_WIA: + populateSpinner(R.id.spinner_compression, R.array.convertCompressionWiaEntries, + R.array.convertCompressionWiaValues, mCompression); + break; + case BLOB_TYPE_RVZ: + populateSpinner(R.id.spinner_compression, R.array.convertCompressionRvzEntries, + R.array.convertCompressionRvzValues, mCompression).setSelection(4); + break; + default: + clearSpinner(R.id.spinner_compression, mCompression); + } + } + + private void populateCompressionLevel() + { + switch (mCompression.getValueOr(requireContext(), COMPRESSION_NONE)) + { + case COMPRESSION_BZIP2: + case COMPRESSION_LZMA: + case COMPRESSION_LZMA2: + populateSpinner(R.id.spinner_compression_level, R.array.convertCompressionLevelEntries, + R.array.convertCompressionLevelValues, mCompressionLevel).setSelection(4); + break; + case COMPRESSION_ZSTD: + // TODO: Query DiscIO for the supported compression levels, like we do in DolphinQt? + populateSpinner(R.id.spinner_compression_level, R.array.convertCompressionLevelZstdEntries, + R.array.convertCompressionLevelZstdValues, mCompressionLevel).setSelection(4); + break; + default: + clearSpinner(R.id.spinner_compression_level, mCompressionLevel); + } + } + + private void populateRemoveJunkData() + { + boolean scrubbingAllowed = mFormat.getValue(requireContext()) != BLOB_TYPE_RVZ && + !gameFile.isDatelDisc(); + + CheckBox removeJunkData = requireView().findViewById(R.id.checkbox_remove_junk_data); + removeJunkData.setEnabled(scrubbingAllowed); + if (!scrubbingAllowed) + removeJunkData.setChecked(false); + } + + private boolean getRemoveJunkData() + { + CheckBox checkBoxScrub = requireView().findViewById(R.id.checkbox_remove_junk_data); + return checkBoxScrub.isChecked(); + } + + @Override + public void onClick(View view) + { + Context context = requireContext(); + + boolean scrub = getRemoveJunkData(); + int format = mFormat.getValue(context); + + boolean iso_warning = scrub && format == BLOB_TYPE_PLAIN; + boolean gcz_warning = !scrub && format == BLOB_TYPE_GCZ && !gameFile.isDatelDisc() && + gameFile.getPlatform() == Platform.WII.toInt(); + + if (iso_warning || gcz_warning) + { + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.DolphinDialogBase); + builder.setMessage(iso_warning ? R.string.convert_warning_iso : R.string.convert_warning_gcz) + .setPositiveButton(R.string.yes, (dialog, i) -> + { + dialog.dismiss(); + convert(); + }) + .setNegativeButton(R.string.no, (dialog, i) -> dialog.dismiss()); + AlertDialog alert = builder.create(); + alert.show(); + } + else + { + convert(); + } + } + + private void convert() + { + Context context = requireContext(); + + // TODO: Let the user select a path + String outPath = gameFile.getPath() + ".converted"; + + boolean success = NativeLibrary.ConvertDiscImage(gameFile.getPath(), outPath, + gameFile.getPlatform(), mFormat.getValue(context), mBlockSize.getValueOr(context,0), + mCompression.getValueOr(context,0), mCompressionLevel.getValueOr(context,0), + getRemoveJunkData()); + + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.DolphinDialogBase); + if (success) + { + builder.setMessage(R.string.convert_success_message) + .setCancelable(false) + .setPositiveButton(R.string.ok, (dialog, i) -> + { + dialog.dismiss(); + requireActivity().finish(); + }); + } + else + { + builder.setMessage(R.string.convert_failure_message) + .setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss()); + } + AlertDialog alert = builder.create(); + alert.show(); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java index dd1dd2898e..c0b55f5da7 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java @@ -38,6 +38,8 @@ public class GameFile public native int getRevision(); + public native int getBlobType(); + public native String getBlobTypeString(); public native long getBlockSize(); @@ -48,6 +50,8 @@ public class GameFile public native long getFileSize(); + public native boolean isDatelDisc(); + public native int[] getBanner(); public native int getBannerWidth(); diff --git a/Source/Android/app/src/main/res/layout-w680dp-land/activity_convert.xml b/Source/Android/app/src/main/res/layout-w680dp-land/activity_convert.xml new file mode 100644 index 0000000000..bd37c3deb6 --- /dev/null +++ b/Source/Android/app/src/main/res/layout-w680dp-land/activity_convert.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + diff --git a/Source/Android/app/src/main/res/layout/activity_convert.xml b/Source/Android/app/src/main/res/layout/activity_convert.xml new file mode 100644 index 0000000000..b63559e248 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/activity_convert.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + diff --git a/Source/Android/app/src/main/res/layout/fragment_convert.xml b/Source/Android/app/src/main/res/layout/fragment_convert.xml new file mode 100644 index 0000000000..938c3239a5 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/fragment_convert.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +