diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index ef7c756d9..ab986b717 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -1662,19 +1662,28 @@ DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_importBIOSImage, jobject ob return env->NewStringUTF(hash.ToString().c_str()); } -DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getMediaPlaylistPaths, jobject obj) + +DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_hasMediaSubImages, jobject obj) +{ + if (!System::IsValid()) + return false; + + return System::HasMediaSubImages(); +} + +DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getMediaSubImageTitles, jobject obj) { if (!System::IsValid()) return nullptr; - const u32 count = System::GetMediaPlaylistCount(); + const u32 count = System::GetMediaSubImageCount(); if (count == 0) return nullptr; jobjectArray arr = env->NewObjectArray(static_cast(count), s_String_class, nullptr); for (u32 i = 0; i < count; i++) { - jstring str = env->NewStringUTF(System::GetMediaPlaylistPath(i).c_str()); + jstring str = env->NewStringUTF(System::GetMediaSubImageTitle(i).c_str()); env->SetObjectArrayElement(arr, static_cast(i), str); env->DeleteLocalRef(str); } @@ -1682,24 +1691,24 @@ DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getMediaPlaylistPaths, return arr; } -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getMediaPlaylistIndex, jobject obj) +DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getMediaSubImageIndex, jobject obj) { if (!System::IsValid()) return -1; - return System::GetMediaPlaylistIndex(); + return System::GetMediaSubImageIndex(); } -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_setMediaPlaylistIndex, jobject obj, jint index) +DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_switchMediaSubImage, jobject obj, jint index) { - if (!System::IsValid() || index < 0 || static_cast(index) >= System::GetMediaPlaylistCount()) + if (!System::IsValid() || index < 0 || static_cast(index) >= System::GetMediaSubImageCount()) return false; AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); hi->RunOnEmulationThread([index, hi]() { if (System::IsValid()) { - if (!System::SwitchMediaFromPlaylist(index)) + if (!System::SwitchMediaSubImage(static_cast(index))) hi->AddOSDMessage("Disc switch failed. Please make sure the file exists."); } }); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java index 927552eb9..86c39aeb9 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java @@ -139,11 +139,13 @@ public class AndroidHostInterface { public native void setFastForwardEnabled(boolean enabled); - public native String[] getMediaPlaylistPaths(); + public native boolean hasMediaSubImages(); - public native int getMediaPlaylistIndex(); + public native String[] getMediaSubImageTitles(); - public native boolean setMediaPlaylistIndex(int index); + public native int getMediaSubImageIndex(); + + public native boolean switchMediaSubImage(int index); public native boolean setMediaFilename(String filename); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java index 04b88fb61..43960e0eb 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java @@ -690,9 +690,23 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde } } + private void startDiscChangeFromFile() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + startActivityForResult(Intent.createChooser(intent, getString(R.string.main_activity_choose_disc_image)), REQUEST_CHANGE_DISC_FILE); + } + private void showDiscChangeMenu() { - final String[] paths = AndroidHostInterface.getInstance().getMediaPlaylistPaths(); - final int currentPath = AndroidHostInterface.getInstance().getMediaPlaylistIndex(); + final AndroidHostInterface hi = AndroidHostInterface.getInstance(); + + if (!hi.hasMediaSubImages()) { + startDiscChangeFromFile(); + return; + } + + final String[] paths = AndroidHostInterface.getInstance().getMediaSubImageTitles(); + final int currentPath = AndroidHostInterface.getInstance().getMediaSubImageIndex(); final int numPaths = (paths != null) ? paths.length : 0; AlertDialog.Builder builder = new AlertDialog.Builder(this); @@ -707,12 +721,9 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde onMenuClosed(); if (i < numPaths) { - AndroidHostInterface.getInstance().setMediaPlaylistIndex(i); + AndroidHostInterface.getInstance().switchMediaSubImage(i); } else { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(intent, getString(R.string.main_activity_choose_disc_image)), REQUEST_CHANGE_DISC_FILE); + startDiscChangeFromFile(); } }); builder.setOnCancelListener(dialogInterface -> onMenuClosed()); diff --git a/src/common/cd_image.cpp b/src/common/cd_image.cpp index d0ec5b876..658d3b6d6 100644 --- a/src/common/cd_image.cpp +++ b/src/common/cd_image.cpp @@ -307,6 +307,17 @@ std::string CDImage::GetSubImageMetadata(u32 index, const std::string_view& type return {}; } +void CDImage::ClearTOC() +{ + m_lba_count = 0; + m_indices.clear(); + m_tracks.clear(); + m_current_index = nullptr; + m_position_in_index = 0; + m_position_in_track = 0; + m_position_on_disc = 0; +} + void CDImage::CopyTOC(const CDImage* image) { m_lba_count = image->m_lba_count; diff --git a/src/common/cd_image.h b/src/common/cd_image.h index bc8e7bfd2..d5fa87d02 100644 --- a/src/common/cd_image.h +++ b/src/common/cd_image.h @@ -280,6 +280,7 @@ public: virtual std::string GetSubImageMetadata(u32 index, const std::string_view& type) const; protected: + void ClearTOC(); void CopyTOC(const CDImage* image); const Index* GetIndexForDiscPosition(LBA pos); diff --git a/src/common/cd_image_pbp.cpp b/src/common/cd_image_pbp.cpp index 98bd7643e..e0d2f0caf 100644 --- a/src/common/cd_image_pbp.cpp +++ b/src/common/cd_image_pbp.cpp @@ -1,3 +1,4 @@ +#include "assert.h" #include "cd_image.h" #include "cd_subchannel_replacement.h" #include "error.h" @@ -25,6 +26,13 @@ public: bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; + bool HasSubImages() const override; + u32 GetSubImageCount() const override; + u32 GetCurrentSubImage() const override; + bool SwitchSubImage(u32 index, Common::Error* error) override; + std::string GetMetadata(const std::string_view& type) const override; + std::string GetSubImageMetadata(u32 index, const std::string_view& type) const override; + protected: bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; @@ -54,6 +62,8 @@ private: bool OpenDisc(u32 index, Common::Error* error); + static const std::string* LookupStringSFOTableEntry(const char* key, const SFOTable& table); + FILE* m_file = nullptr; PBPHeader m_pbp_header; @@ -63,6 +73,7 @@ private: // Absolute offsets to ISO headers, size is the number of discs in the file std::vector m_disc_offsets; + u32 m_current_disc = 0; // Absolute offsets and sizes of blocks in m_file std::array m_blockinfo_table; @@ -537,6 +548,7 @@ bool CDImagePBP::OpenDisc(u32 index, Common::Error* error) // that isn't 2 seconds long. We don't have a good way to validate this, and have to assume the TOC is giving us // correct pregap lengths... + ClearTOC(); m_lba_count = sectors_on_file; LBA track1_pregap_frames = 0; for (u32 curr_track = 1; curr_track <= last_track; curr_track++) @@ -654,9 +666,23 @@ bool CDImagePBP::OpenDisc(u32 index, Common::Error* error) else m_sbi.LoadSBI(FileSystem::ReplaceExtension(m_filename, "sbi").c_str()); + m_current_disc = index; return Seek(1, Position{0, 0, 0}); } +const std::string* CDImagePBP::LookupStringSFOTableEntry(const char* key, const SFOTable& table) +{ + auto iter = table.find(key); + if (iter == table.end()) + return nullptr; + + const SFOTableDataValue& data_value = iter->second; + if (!std::holds_alternative(data_value)) + return nullptr; + + return &std::get(data_value); +} + bool CDImagePBP::InitDecompressionStream() { m_inflate_stream = {}; @@ -788,6 +814,61 @@ void CDImagePBP::PrintSFOTable(const SFOTable& sfo_table) } #endif +bool CDImagePBP::HasSubImages() const +{ + return m_disc_offsets.size() > 1; +} + +std::string CDImagePBP::GetMetadata(const std::string_view& type) const +{ + if (type == "title") + { + const std::string* title = LookupStringSFOTableEntry("TITLE", m_sfo_table); + if (title && !title->empty()) + return *title; + } + + return CDImage::GetMetadata(type); +} + +u32 CDImagePBP::GetSubImageCount() const +{ + return static_cast(m_disc_offsets.size()); +} + +u32 CDImagePBP::GetCurrentSubImage() const +{ + return m_current_disc; +} + +bool CDImagePBP::SwitchSubImage(u32 index, Common::Error* error) +{ + if (index >= m_disc_offsets.size()) + return false; + + const u32 old_disc = m_current_disc; + if (!OpenDisc(index, error)) + { + // return to old disc, this should never fail... in theory. + if (!OpenDisc(old_disc, nullptr)) + Panic("Failed to reopen old disc after switch."); + } + + return true; +} + +std::string CDImagePBP::GetSubImageMetadata(u32 index, const std::string_view& type) const +{ + if (type == "title") + { + const std::string* title = LookupStringSFOTableEntry("TITLE", m_sfo_table); + if (title && !title->empty()) + return StringUtil::StdStringFromFormat("%s (Disc %u)", title->c_str(), index + 1); + } + + return CDImage::GetSubImageMetadata(index, type); +} + std::unique_ptr CDImage::OpenPBPImage(const char* filename, Common::Error* error) { std::unique_ptr image = std::make_unique();