CueParser: Handle rarely-used commands

This commit is contained in:
Stenzek
2026-07-08 19:00:26 +10:00
parent e7f2f101c2
commit 9c79341cf4
4 changed files with 185 additions and 18 deletions
+73 -6
View File
@@ -96,7 +96,13 @@ TEST(CueParser, DifferentTrackModes)
"TRACK 04 MODE2/2048\n"
"INDEX 01 30:10:00\n"
"TRACK 05 MODE2/2352\n"
"INDEX 01 45:00:00\n";
"INDEX 01 45:00:00\n"
"TRACK 06 MODE2/2324\n"
"INDEX 01 60:00:00\n"
"TRACK 07 MODE1_RAW\n"
"INDEX 01 75:00:00\n"
"TRACK 08 MODE2_RAW\n"
"INDEX 01 90:00:00\n";
CueParser::File cue_file;
Error error;
@@ -125,6 +131,18 @@ TEST(CueParser, DifferentTrackModes)
const CueParser::Track* track5 = cue_file.GetTrack(5);
ASSERT_NE(track5, nullptr);
EXPECT_EQ(track5->mode, CueParser::TrackMode::Mode2Raw);
const CueParser::Track* track6 = cue_file.GetTrack(6);
ASSERT_NE(track6, nullptr);
EXPECT_EQ(track6->mode, CueParser::TrackMode::Mode2Form2);
const CueParser::Track* track7 = cue_file.GetTrack(7);
ASSERT_NE(track7, nullptr);
EXPECT_EQ(track7->mode, CueParser::TrackMode::Mode1Raw);
const CueParser::Track* track8 = cue_file.GetTrack(8);
ASSERT_NE(track8, nullptr);
EXPECT_EQ(track8->mode, CueParser::TrackMode::Mode2Raw);
}
// Test track flags
@@ -269,13 +287,19 @@ TEST(CueParser, InvalidIndexNumber)
// Test invalid MSF format
TEST(CueParser, InvalidMSFFormat)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 01 00:99:00\n";
const std::string invalid_second = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 01 00:60:00\n";
const std::string invalid_frame = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 01 00:00:75\n";
CueParser::File cue_file;
Error error;
ASSERT_FALSE(cue_file.Parse(cue, &error));
CueParser::File cue_file;
ASSERT_FALSE(cue_file.Parse(invalid_second, &error));
cue_file = CueParser::File();
ASSERT_FALSE(cue_file.Parse(invalid_frame, &error));
}
// Test duplicate index
@@ -308,6 +332,9 @@ TEST(CueParser, ReverseIndexOrder)
TEST(CueParser, CommentsAndWhitespace)
{
const std::string cue = "REM This is a comment\n"
"; This is also a comment\n"
"// cdrdao comment\n"
"CD_ROM_XA\n"
" FILE \"game.bin\" BINARY \n"
"\n"
"REM Another comment\n"
@@ -324,6 +351,46 @@ TEST(CueParser, CommentsAndWhitespace)
EXPECT_EQ(track->mode, CueParser::TrackMode::Mode2Raw);
}
TEST(CueParser, CdrdaoTrackWithoutNumber)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK MODE1_RAW\n"
"INDEX 01 00:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error)) << error.GetDescription();
const CueParser::Track* track = cue_file.GetTrack(1);
ASSERT_NE(track, nullptr);
EXPECT_EQ(track->mode, CueParser::TrackMode::Mode1Raw);
}
TEST(CueParser, StandaloneFlagCommands)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"COPY\n"
"PRE_EMPHASIS\n"
"FOUR_CHANNEL_AUDIO\n"
"NO COPY\n"
"NO PRE_EMPHASIS\n"
"TWO_CHANNEL_AUDIO\n"
"INDEX 01 00:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error)) << error.GetDescription();
const CueParser::Track* track = cue_file.GetTrack(1);
ASSERT_NE(track, nullptr);
EXPECT_FALSE(track->HasFlag(CueParser::TrackFlag::CopyPermitted));
EXPECT_FALSE(track->HasFlag(CueParser::TrackFlag::PreEmphasis));
EXPECT_FALSE(track->HasFlag(CueParser::TrackFlag::FourChannelAudio));
}
// Test handling of multiple files
TEST(CueParser, MultipleFiles)
{
+9
View File
@@ -9,6 +9,15 @@ bool Core::GetBaseBoolSettingValue(const char* section, const char* key, bool de
return default_value;
}
SmallString Host::TranslatePluralToSmallString(const char* context, const char* msg, const char* disambiguation,
int count)
{
TinyString count_string = TinyString::from_format("{}", count);
SmallString ret(msg);
ret.replace("%n", count_string.c_str());
return ret;
}
s32 Host::Internal::GetTranslatedStringImpl(std::string_view context, std::string_view msg,
std::string_view disambiguation, char* tbuf, size_t tbuf_space)
{
+100 -12
View File
@@ -148,7 +148,7 @@ std::string_view CueParser::File::GetToken(const char*& line)
std::optional<CueParser::MSF> CueParser::File::GetMSF(std::string_view token)
{
static const s32 max_values[] = {std::numeric_limits<s32>::max(), 60, 75};
static const s32 max_values[] = {std::numeric_limits<s32>::max(), 59, 74};
u32 parts[3] = {};
u32 part = 0;
@@ -197,7 +197,7 @@ bool CueParser::File::ParseLine(const char* line, u32 line_number, Error* error)
if (command.empty())
return true;
if (TokenMatch(command, "REM"))
if (command[0] == ';' || command.starts_with("//") || TokenMatch(command, "REM"))
{
// comment, eat it
return true;
@@ -213,6 +213,22 @@ bool CueParser::File::ParseLine(const char* line, u32 line_number, Error* error)
return HandlePregapCommand(line, line_number, error);
else if (TokenMatch(command, "FLAGS"))
return HandleFlagCommand(line, line_number, error);
else if (TokenMatch(command, "COPY"))
return HandleCopyCommand(line_number, true, error);
else if (TokenMatch(command, "PRE_EMPHASIS"))
return HandlePreEmphasisCommand(line_number, true, error);
else if (TokenMatch(command, "FOUR_CHANNEL_AUDIO"))
return HandleFourChannelAudioCommand(line_number, true, error);
else if (TokenMatch(command, "TWO_CHANNEL_AUDIO"))
return HandleFourChannelAudioCommand(line_number, false, error);
else if (TokenMatch(command, "NO"))
{
const std::string_view subcommand(GetToken(line));
if (TokenMatch(subcommand, "COPY"))
return HandleCopyCommand(line_number, false, error);
else if (TokenMatch(subcommand, "PRE_EMPHASIS"))
return HandlePreEmphasisCommand(line_number, false, error);
}
if (TokenMatch(command, "POSTGAP"))
{
@@ -221,12 +237,15 @@ bool CueParser::File::ParseLine(const char* line, u32 line_number, Error* error)
}
// stuff we definitely ignore
if (TokenMatch(command, "CATALOG") || TokenMatch(command, "CDTEXTFILE") || TokenMatch(command, "ISRC") ||
TokenMatch(command, "TRACK_ISRC") || TokenMatch(command, "TITLE") || TokenMatch(command, "PERFORMER") ||
TokenMatch(command, "SONGWRITER") || TokenMatch(command, "COMPOSER") || TokenMatch(command, "ARRANGER") ||
TokenMatch(command, "MESSAGE") || TokenMatch(command, "DISC_ID") || TokenMatch(command, "GENRE") ||
TokenMatch(command, "TOC_INFO1") || TokenMatch(command, "TOC_INFO2") || TokenMatch(command, "UPC_EAN") ||
TokenMatch(command, "SIZE_INFO"))
if (TokenMatch(command, "CATALOG") || TokenMatch(command, "CD_DA") || TokenMatch(command, "CD_ROM") ||
TokenMatch(command, "CD_ROM_XA") || TokenMatch(command, "CD_TEXT") || TokenMatch(command, "CDTEXTFILE") ||
TokenMatch(command, "ISRC") || TokenMatch(command, "TRACK_ISRC") || TokenMatch(command, "TITLE") ||
TokenMatch(command, "PERFORMER") || TokenMatch(command, "SONGWRITER") || TokenMatch(command, "COMPOSER") ||
TokenMatch(command, "ARRANGER") || TokenMatch(command, "MESSAGE") || TokenMatch(command, "DISC_ID") ||
TokenMatch(command, "GENRE") || TokenMatch(command, "TOC_INFO1") || TokenMatch(command, "TOC_INFO2") ||
TokenMatch(command, "UPC_EAN") || TokenMatch(command, "SIZE_INFO") || TokenMatch(command, "DATAFILE") ||
TokenMatch(command, "FIFO") || TokenMatch(command, "SILENCE") || TokenMatch(command, "START") ||
TokenMatch(command, "ZERO"))
{
return true;
}
@@ -278,33 +297,54 @@ bool CueParser::File::HandleTrackCommand(const char* line, u32 line_number, Erro
return false;
}
const std::string_view track_number_str(GetToken(line));
std::string_view track_number_str(GetToken(line));
if (track_number_str.empty())
{
SetError(line_number, error, "Missing track number");
return false;
}
const std::optional<s32> track_number = StringUtil::FromChars<s32>(track_number_str);
std::optional<s32> track_number = StringUtil::FromChars<s32>(track_number_str);
std::string_view mode_str;
if (track_number.has_value())
{
mode_str = GetToken(line);
}
else
{
// cdrdao can omit the track number for single-track CD_ROM CUE sheets.
if (!m_tracks.empty())
{
SetError(line_number, error, "Missing track number");
return false;
}
track_number = 1;
mode_str = track_number_str;
}
if (track_number.value_or(0) < MIN_TRACK_NUMBER || track_number.value_or(0) > MAX_TRACK_NUMBER)
{
SetError(line_number, error, "Invalid track number {}", track_number.value_or(0));
return false;
}
const std::string_view mode_str = GetToken(line);
TrackMode mode;
if (TokenMatch(mode_str, "AUDIO"))
mode = TrackMode::Audio;
else if (TokenMatch(mode_str, "MODE1_RAW"))
mode = TrackMode::Mode1Raw;
else if (TokenMatch(mode_str, "MODE1/2048"))
mode = TrackMode::Mode1;
else if (TokenMatch(mode_str, "MODE1/2352"))
mode = TrackMode::Mode1Raw;
else if (TokenMatch(mode_str, "MODE2_RAW"))
mode = TrackMode::Mode2Raw;
else if (TokenMatch(mode_str, "MODE2/2336"))
mode = TrackMode::Mode2;
else if (TokenMatch(mode_str, "MODE2/2048"))
mode = TrackMode::Mode2Form1;
else if (TokenMatch(mode_str, "MODE2/2342"))
else if (TokenMatch(mode_str, "MODE2/2324") || TokenMatch(mode_str, "MODE2/2342"))
mode = TrackMode::Mode2Form2;
else if (TokenMatch(mode_str, "MODE2/2332"))
mode = TrackMode::Mode2FormMix;
@@ -324,6 +364,54 @@ bool CueParser::File::HandleTrackCommand(const char* line, u32 line_number, Erro
return true;
}
bool CueParser::File::HandleCopyCommand(u32 line_number, bool enabled, Error* error)
{
if (!m_current_track.has_value())
{
SetError(line_number, error, "Copy command outside of track");
return false;
}
if (enabled)
m_current_track->SetFlag(TrackFlag::CopyPermitted);
else
m_current_track->RemoveFlag(TrackFlag::CopyPermitted);
return true;
}
bool CueParser::File::HandlePreEmphasisCommand(u32 line_number, bool enabled, Error* error)
{
if (!m_current_track.has_value())
{
SetError(line_number, error, "Pre-emphasis command outside of track");
return false;
}
if (enabled)
m_current_track->SetFlag(TrackFlag::PreEmphasis);
else
m_current_track->RemoveFlag(TrackFlag::PreEmphasis);
return true;
}
bool CueParser::File::HandleFourChannelAudioCommand(u32 line_number, bool enabled, Error* error)
{
if (!m_current_track.has_value())
{
SetError(line_number, error, "Channel audio command outside of track");
return false;
}
if (enabled)
m_current_track->SetFlag(TrackFlag::FourChannelAudio);
else
m_current_track->RemoveFlag(TrackFlag::FourChannelAudio);
return true;
}
bool CueParser::File::HandleIndexCommand(const char* line, u32 line_number, Error* error)
{
if (!m_current_track.has_value())
+3
View File
@@ -88,6 +88,9 @@ private:
bool HandleIndexCommand(const char* line, u32 line_number, Error* error);
bool HandlePregapCommand(const char* line, u32 line_number, Error* error);
bool HandleFlagCommand(const char* line, u32 line_number, Error* error);
bool HandleCopyCommand(u32 line_number, bool enabled, Error* error);
bool HandlePreEmphasisCommand(u32 line_number, bool enabled, Error* error);
bool HandleFourChannelAudioCommand(u32 line_number, bool enabled, Error* error);
bool CompleteLastTrack(u32 line_number, Error* error);
bool SetTrackLengths(u32 line_number, Error* error);