|
46 | 46 | #include "common/Console.h" |
47 | 47 | #include "common/Error.h" |
48 | 48 | #include "common/FileSystem.h" |
| 49 | +#include "common/Path.h" |
49 | 50 | #include "common/FPControl.h" |
50 | 51 | #include "common/ScopedGuard.h" |
51 | 52 | #include "common/SettingsWrapper.h" |
@@ -1235,6 +1236,41 @@ bool VMManager::HasBootedELF() |
1235 | 1236 | return s_current_crc != 0 && s_elf_executed; |
1236 | 1237 | } |
1237 | 1238 |
|
| 1239 | +static std::vector<std::string> ParseM3UPlaylist(const std::string& m3u_path) |
| 1240 | +{ |
| 1241 | + std::vector<std::string> disc_paths; |
| 1242 | + |
| 1243 | + const std::optional<std::string> content = FileSystem::ReadFileToString(m3u_path.c_str()); |
| 1244 | + if (!content.has_value()) |
| 1245 | + return disc_paths; |
| 1246 | + |
| 1247 | + const std::string_view m3u_dir = Path::GetDirectory(m3u_path); |
| 1248 | + |
| 1249 | + const std::vector<std::string_view> lines = StringUtil::SplitString(*content, '\n', false); |
| 1250 | + for (const std::string_view line : lines) |
| 1251 | + { |
| 1252 | + // Skip empty lines and comments |
| 1253 | + const std::string_view trimmed = StringUtil::StripWhitespace(line); |
| 1254 | + if (trimmed.empty() || trimmed[0] == '#') |
| 1255 | + continue; |
| 1256 | + |
| 1257 | + // Resolve relative paths |
| 1258 | + std::string disc_path; |
| 1259 | + if (Path::IsAbsolute(trimmed)) |
| 1260 | + { |
| 1261 | + disc_path = std::string(trimmed); |
| 1262 | + } |
| 1263 | + else |
| 1264 | + { |
| 1265 | + disc_path = Path::Combine(m3u_dir, trimmed); |
| 1266 | + } |
| 1267 | + |
| 1268 | + disc_paths.push_back(std::move(disc_path)); |
| 1269 | + } |
| 1270 | + |
| 1271 | + return disc_paths; |
| 1272 | +} |
| 1273 | + |
1238 | 1274 | bool VMManager::AutoDetectSource(const std::string& filename, Error* error) |
1239 | 1275 | { |
1240 | 1276 | if (!filename.empty()) |
@@ -1268,6 +1304,20 @@ bool VMManager::AutoDetectSource(const std::string& filename, Error* error) |
1268 | 1304 | s_elf_override = filename; |
1269 | 1305 | return true; |
1270 | 1306 | } |
| 1307 | + else if (StringUtil::EndsWithNoCase(filename, ".m3u")) |
| 1308 | + { |
| 1309 | + const std::vector<std::string> disc_paths = ParseM3UPlaylist(filename); |
| 1310 | + if (disc_paths.empty()) |
| 1311 | + { |
| 1312 | + Error::SetStringFmt(error, TRANSLATE_FS("VMManager", "M3U playlist '{}' does not contain any valid disc paths."), filename); |
| 1313 | + return false; |
| 1314 | + } |
| 1315 | + |
| 1316 | + // For now, just load the first disc |
| 1317 | + CDVDsys_SetFile(CDVD_SourceType::Iso, disc_paths[0]); |
| 1318 | + CDVDsys_ChangeSource(CDVD_SourceType::Iso); |
| 1319 | + return true; |
| 1320 | + } |
1271 | 1321 | else |
1272 | 1322 | { |
1273 | 1323 | // TODO: Maybe we should check if it's a valid iso here... |
@@ -2424,7 +2474,7 @@ bool VMManager::IsSaveStateFileName(const std::string_view path) |
2424 | 2474 |
|
2425 | 2475 | bool VMManager::IsDiscFileName(const std::string_view path) |
2426 | 2476 | { |
2427 | | - static const char* extensions[] = {".iso", ".bin", ".img", ".mdf", ".gz", ".cso", ".zso", ".chd"}; |
| 2477 | + static const char* extensions[] = {".iso", ".bin", ".img", ".mdf", ".gz", ".cso", ".zso", ".chd", ".m3u"}; |
2428 | 2478 |
|
2429 | 2479 | for (const char* test_extension : extensions) |
2430 | 2480 | { |
|
0 commit comments