cd_image_m3u.cpp (5150B)
1 // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "cd_image.h" 5 #include "cd_subchannel_replacement.h" 6 7 #include "common/assert.h" 8 #include "common/error.h" 9 #include "common/file_system.h" 10 #include "common/log.h" 11 #include "common/path.h" 12 13 #include <algorithm> 14 #include <cerrno> 15 #include <map> 16 #include <sstream> 17 18 Log_SetChannel(CDImageMemory); 19 20 namespace { 21 22 class CDImageM3u : public CDImage 23 { 24 public: 25 CDImageM3u(); 26 ~CDImageM3u() override; 27 28 bool Open(const char* path, bool apply_patches, Error* Error); 29 30 bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; 31 bool HasNonStandardSubchannel() const override; 32 33 bool HasSubImages() const override; 34 u32 GetSubImageCount() const override; 35 u32 GetCurrentSubImage() const override; 36 std::string GetSubImageMetadata(u32 index, std::string_view type) const override; 37 bool SwitchSubImage(u32 index, Error* error) override; 38 39 protected: 40 bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; 41 42 private: 43 struct Entry 44 { 45 // TODO: Worth storing any other data? 46 std::string filename; 47 std::string title; 48 }; 49 50 std::vector<Entry> m_entries; 51 std::unique_ptr<CDImage> m_current_image; 52 u32 m_current_image_index = UINT32_C(0xFFFFFFFF); 53 bool m_apply_patches = false; 54 }; 55 56 } // namespace 57 58 CDImageM3u::CDImageM3u() = default; 59 60 CDImageM3u::~CDImageM3u() = default; 61 62 bool CDImageM3u::Open(const char* path, bool apply_patches, Error* error) 63 { 64 std::FILE* fp = FileSystem::OpenSharedCFile(path, "rb", FileSystem::FileShareMode::DenyWrite, error); 65 if (!fp) 66 return false; 67 68 std::optional<std::string> m3u_file(FileSystem::ReadFileToString(fp)); 69 std::fclose(fp); 70 if (!m3u_file.has_value() || m3u_file->empty()) 71 { 72 Error::SetString(error, "Failed to read M3u file"); 73 return false; 74 } 75 76 std::istringstream ifs(m3u_file.value()); 77 m_filename = path; 78 m_apply_patches = apply_patches; 79 80 std::vector<std::string> entries; 81 std::string line; 82 while (std::getline(ifs, line)) 83 { 84 u32 start_offset = 0; 85 while (start_offset < line.size() && std::isspace(line[start_offset])) 86 start_offset++; 87 88 // skip comments 89 if (start_offset == line.size() || line[start_offset] == '#') 90 continue; 91 92 // strip ending whitespace 93 u32 end_offset = static_cast<u32>(line.size()) - 1; 94 while (std::isspace(line[end_offset]) && end_offset > start_offset) 95 end_offset--; 96 97 // anything? 98 if (start_offset == end_offset) 99 continue; 100 101 Entry entry; 102 std::string entry_filename = 103 Path::ToNativePath(std::string_view(line.begin() + start_offset, line.begin() + end_offset + 1)); 104 entry.title = Path::GetFileTitle(entry_filename); 105 if (!Path::IsAbsolute(entry_filename)) 106 entry.filename = Path::BuildRelativePath(path, entry_filename); 107 else 108 entry.filename = std::move(entry_filename); 109 110 DEV_LOG("Read path from m3u: '{}'", entry.filename); 111 m_entries.push_back(std::move(entry)); 112 } 113 114 INFO_LOG("Loaded {} paths from m3u '{}'", m_entries.size(), path); 115 return !m_entries.empty() && SwitchSubImage(0, error); 116 } 117 118 bool CDImageM3u::HasNonStandardSubchannel() const 119 { 120 return m_current_image->HasNonStandardSubchannel(); 121 } 122 123 bool CDImageM3u::HasSubImages() const 124 { 125 return true; 126 } 127 128 u32 CDImageM3u::GetSubImageCount() const 129 { 130 return static_cast<u32>(m_entries.size()); 131 } 132 133 u32 CDImageM3u::GetCurrentSubImage() const 134 { 135 return m_current_image_index; 136 } 137 138 bool CDImageM3u::SwitchSubImage(u32 index, Error* error) 139 { 140 if (index >= m_entries.size()) 141 return false; 142 else if (index == m_current_image_index) 143 return true; 144 145 const Entry& entry = m_entries[index]; 146 std::unique_ptr<CDImage> new_image = CDImage::Open(entry.filename.c_str(), m_apply_patches, error); 147 if (!new_image) 148 { 149 ERROR_LOG("Failed to load subimage {} ({})", index, entry.filename); 150 return false; 151 } 152 153 CopyTOC(new_image.get()); 154 m_current_image = std::move(new_image); 155 m_current_image_index = index; 156 if (!Seek(1, Position{0, 0, 0})) 157 Panic("Failed to seek to start after sub-image change."); 158 159 return true; 160 } 161 162 std::string CDImageM3u::GetSubImageMetadata(u32 index, std::string_view type) const 163 { 164 if (index >= m_entries.size()) 165 return {}; 166 167 if (type == "title") 168 return m_entries[index].title; 169 else if (type == "file_title") 170 return std::string(Path::GetFileTitle(m_entries[index].filename)); 171 172 return CDImage::GetSubImageMetadata(index, type); 173 } 174 175 bool CDImageM3u::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) 176 { 177 return m_current_image->ReadSectorFromIndex(buffer, index, lba_in_index); 178 } 179 180 bool CDImageM3u::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) 181 { 182 return m_current_image->ReadSubChannelQ(subq, index, lba_in_index); 183 } 184 185 std::unique_ptr<CDImage> CDImage::OpenM3uImage(const char* filename, bool apply_patches, Error* error) 186 { 187 std::unique_ptr<CDImageM3u> image = std::make_unique<CDImageM3u>(); 188 if (!image->Open(filename, apply_patches, error)) 189 return {}; 190 191 return image; 192 }