From 30dfad096a511ca8b295e2e6870fdb125cc72578 Mon Sep 17 00:00:00 2001 From: retropieuser <73472027+retropieuser@users.noreply.github.com> Date: Tue, 16 Jun 2026 07:50:57 +0100 Subject: [PATCH 1/7] Create m3u.h --- Source/UI/m3u.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Source/UI/m3u.h diff --git a/Source/UI/m3u.h b/Source/UI/m3u.h new file mode 100644 index 0000000..b6b31ec --- /dev/null +++ b/Source/UI/m3u.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +struct M3UResult { + bool success; + std::string error; + std::vector entries; +}; + +M3UResult LoadM3U(const std::string& path); From ed2671c2f9dbcd4b209ebe3e5714f799341c916d Mon Sep 17 00:00:00 2001 From: retropieuser <73472027+retropieuser@users.noreply.github.com> Date: Tue, 16 Jun 2026 07:55:11 +0100 Subject: [PATCH 2/7] Create m3u.cpp Implement M3U file loading with entry trimming --- Source/UI/m3u.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Source/UI/m3u.cpp diff --git a/Source/UI/m3u.cpp b/Source/UI/m3u.cpp new file mode 100644 index 0000000..ec33584 --- /dev/null +++ b/Source/UI/m3u.cpp @@ -0,0 +1,49 @@ +#include "m3u.h" + +#include +#include + +namespace { + +std::string Trim(const std::string& value) +{ + const auto first = value.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) { + return ""; + } + + const auto last = value.find_last_not_of(" \t\r\n"); + return value.substr(first, last - first + 1); +} + +} + +M3UResult LoadM3U(const std::string& path) +{ + M3UResult result; + result.success = false; + + std::ifstream file(path); + if (!file.is_open()) { + result.error = "unable to open m3u: " + path; + return result; + } + + std::string line; + while (std::getline(file, line)) { + line = Trim(line); + + if (line.empty()) { + continue; + } + + if (line[0] == '#') { + continue; + } + + result.entries.push_back(line); + } + + result.success = true; + return result; +} From f42bb78594411117cd7f1bd5ef108dd056a83cbf Mon Sep 17 00:00:00 2001 From: retropieuser <73472027+retropieuser@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:02:08 +0100 Subject: [PATCH 3/7] Implement M3U playlist support in clidisk --- Source/UI/clidisk.cpp | 53 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/Source/UI/clidisk.cpp b/Source/UI/clidisk.cpp index 6a802df..e1befc8 100644 --- a/Source/UI/clidisk.cpp +++ b/Source/UI/clidisk.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "m3u.h" namespace { @@ -119,6 +120,16 @@ bool ParseClock(const std::string& value, CliClockMode *clock) return true; } +bool IsM3U(const std::string& path) +{ + if (path.length() < 4) { + return false; + } + + std::string ext = ToUpper(path.substr(path.length() - 4)); + return ext == ".M3U"; +} + } // namespace CliOptions ParseCommandLine(int argc, char *argv[]) @@ -187,13 +198,43 @@ CliOptions ParseCommandLine(int argc, char *argv[]) return Error("too many disk images; maximum is 2"); } - DiskSpec spec; - std::string error; - if (!ParseDiskSpec(argument, static_cast(options.disks.size()), - &spec, &error)) { - return Error(error); + if (IsM3U(argument)) { + + M3UResult playlist = LoadM3U(argument); + + if (!playlist.success) { + return Error(playlist.error); + } + + for (const auto& entry : playlist.entries) { + + DiskSpec spec; + std::string error; + + if (!ParseDiskSpec(entry, + static_cast(options.disks.size()), + &spec, + &error)) { + return Error(error); + } + + options.disks.push_back(spec); + } + + } else { + + DiskSpec spec; + std::string error; + + if (!ParseDiskSpec(argument, + static_cast(options.disks.size()), + &spec, + &error)) { + return Error(error); + } + + options.disks.push_back(spec); } - options.disks.push_back(spec); } if (action_seen && (system_seen || clock_seen || !options.disks.empty())) { From baaee044d2975bcc32bdce11211def365e495a86 Mon Sep 17 00:00:00 2001 From: retropieuser <73472027+retropieuser@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:12:16 +0100 Subject: [PATCH 4/7] Update CMakeLists.txt --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 65bcc01..dd49292 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,7 @@ set(SRCS Source/UI/fileio.cpp Source/UI/font.cpp Source/UI/input.cpp + Source/UI/m3u.cpp Source/UI/main.cpp Source/UI/menu.cpp Source/UI/menuitem.cpp From 2d3de796f68b91eed1ebd326a83c3b85747158d0 Mon Sep 17 00:00:00 2001 From: retropieuser <73472027+retropieuser@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:29:45 +0100 Subject: [PATCH 5/7] Update CMakeLists.txt --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd49292..461d291 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,6 +192,7 @@ if(BUILD_TESTING) add_executable(clidisk_test Tests/clidisk_test.cpp Source/UI/clidisk.cpp + Source/UI/m3u.cpp ) target_include_directories(clidisk_test PRIVATE Source/UI) add_test(NAME clidisk_test COMMAND clidisk_test) From c79dabf9870223403525dbc942e6db1636c30f58 Mon Sep 17 00:00:00 2001 From: retropieuser <73472027+retropieuser@users.noreply.github.com> Date: Tue, 16 Jun 2026 09:15:12 +0100 Subject: [PATCH 6/7] adding m3u support --- CMakeLists.txt | 2 +- Source/UI/clidisk.cpp | 4 ++++ Source/UI/m3u.cpp | 12 ++++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 461d291..ef8a837 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED OFF) # Dependencies must be included after Platforms. -set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13") +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15") if(APPLE) # macOS: Build as bundle and use bundled SDL2 source by default set(XM8_SYSTEM_SDL2 OFF CACHE BOOL "Use system-installed SDL2" FORCE) diff --git a/Source/UI/clidisk.cpp b/Source/UI/clidisk.cpp index e1befc8..23f6513 100644 --- a/Source/UI/clidisk.cpp +++ b/Source/UI/clidisk.cpp @@ -207,6 +207,10 @@ CliOptions ParseCommandLine(int argc, char *argv[]) } for (const auto& entry : playlist.entries) { + + if (options.disks.size() >=2) { + break; + } DiskSpec spec; std::string error; diff --git a/Source/UI/m3u.cpp b/Source/UI/m3u.cpp index ec33584..993cf56 100644 --- a/Source/UI/m3u.cpp +++ b/Source/UI/m3u.cpp @@ -2,6 +2,7 @@ #include #include +#include namespace { @@ -16,7 +17,7 @@ std::string Trim(const std::string& value) return value.substr(first, last - first + 1); } -} +} // namespace M3UResult LoadM3U(const std::string& path) { @@ -29,6 +30,10 @@ M3UResult LoadM3U(const std::string& path) return result; } + // Base directory of the M3U file + std::filesystem::path baseDir = + std::filesystem::path(path).parent_path(); + std::string line; while (std::getline(file, line)) { line = Trim(line); @@ -41,7 +46,10 @@ M3UResult LoadM3U(const std::string& path) continue; } - result.entries.push_back(line); + // Resolve relative paths against M3U location + std::filesystem::path resolved = baseDir / line; + + result.entries.push_back(resolved.lexically_normal().string()); } result.success = true; From da2c796f43364c9211c08c1c4ac30c2cf8b33b44 Mon Sep 17 00:00:00 2001 From: retropieuser <73472027+retropieuser@users.noreply.github.com> Date: Tue, 16 Jun 2026 11:55:45 +0100 Subject: [PATCH 7/7] Stay on OSX 10.13 avoid using std::filename for m3u support --- CMakeLists.txt | 2 +- Source/UI/m3u.cpp | 43 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef8a837..461d291 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED OFF) # Dependencies must be included after Platforms. -set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15") +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13") if(APPLE) # macOS: Build as bundle and use bundled SDL2 source by default set(XM8_SYSTEM_SDL2 OFF CACHE BOOL "Use system-installed SDL2" FORCE) diff --git a/Source/UI/m3u.cpp b/Source/UI/m3u.cpp index 993cf56..78b58e3 100644 --- a/Source/UI/m3u.cpp +++ b/Source/UI/m3u.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace { @@ -17,8 +17,38 @@ std::string Trim(const std::string& value) return value.substr(first, last - first + 1); } +std::string ParentDirectory(const std::string& path) +{ + const std::string::size_type slash = path.find_last_of("/\\"); + if (slash == std::string::npos) { + return ""; + } + return path.substr(0, slash + 1); +} + +std::string ResolveM3UEntry(const std::string& baseDir, + const std::string& entry) +{ + if (entry.empty()) { + return entry; + } + + if (entry[0] == '/') { + return entry; + } + + if (entry.size() >= 2 && + std::isalpha(static_cast(entry[0])) && + entry[1] == ':') { + return entry; + } + + return baseDir + entry; +} + } // namespace + M3UResult LoadM3U(const std::string& path) { M3UResult result; @@ -30,9 +60,8 @@ M3UResult LoadM3U(const std::string& path) return result; } - // Base directory of the M3U file - std::filesystem::path baseDir = - std::filesystem::path(path).parent_path(); + // 👇 get base directory ONCE + const std::string baseDir = ParentDirectory(path); std::string line; while (std::getline(file, line)) { @@ -46,10 +75,10 @@ M3UResult LoadM3U(const std::string& path) continue; } - // Resolve relative paths against M3U location - std::filesystem::path resolved = baseDir / line; + // 👇 resolve relative paths properly + std::string resolved = ResolveM3UEntry(baseDir, line); - result.entries.push_back(resolved.lexically_normal().string()); + result.entries.push_back(resolved); } result.success = true;