Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util/cd_image_m3u.cpp
4212 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "cd_image.h"
5
6
#include "common/assert.h"
7
#include "common/error.h"
8
#include "common/file_system.h"
9
#include "common/log.h"
10
#include "common/path.h"
11
#include "common/string_util.h"
12
13
#include <algorithm>
14
#include <cerrno>
15
#include <map>
16
#include <sstream>
17
18
LOG_CHANNEL(CDImage);
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 HasSubchannelData() 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() && StringUtil::IsWhitespace(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 (StringUtil::IsWhitespace(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::HasSubchannelData() const
119
{
120
return m_current_image->HasSubchannelData();
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* path, bool apply_patches, Error* error)
186
{
187
std::unique_ptr<CDImageM3u> image = std::make_unique<CDImageM3u>();
188
if (!image->Open(path, apply_patches, error))
189
return {};
190
191
return image;
192
}
193
194