mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-04 13:14:31 +00:00
Tests: Add for CueParser and ElfFile
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
add_executable(util-tests
|
||||
animated_image_tests.cpp
|
||||
elf_parser_tests.cpp
|
||||
cue_parser_tests.cpp
|
||||
image_tests.cpp
|
||||
)
|
||||
|
||||
|
||||
372
src/util-tests/cue_parser_tests.cpp
Normal file
372
src/util-tests/cue_parser_tests.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "util/cue_parser.h"
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
// Test basic valid CUE sheet
|
||||
TEST(CueParser, BasicValidCue)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 MODE2/2352\n"
|
||||
"INDEX 01 00:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
|
||||
ASSERT_TRUE(cue_file.Parse(cue, &error));
|
||||
|
||||
const CueParser::Track* track = cue_file.GetTrack(1);
|
||||
ASSERT_NE(track, nullptr);
|
||||
EXPECT_EQ(track->number, 1);
|
||||
EXPECT_EQ(track->mode, CueParser::TrackMode::Mode2Raw);
|
||||
EXPECT_EQ(track->file, "game.bin");
|
||||
EXPECT_EQ(track->file_format, CueParser::FileFormat::Binary);
|
||||
|
||||
const CueParser::MSF* index1 = track->GetIndex(1);
|
||||
ASSERT_NE(index1, nullptr);
|
||||
EXPECT_EQ(index1->minute, 0);
|
||||
EXPECT_EQ(index1->second, 0);
|
||||
EXPECT_EQ(index1->frame, 0);
|
||||
}
|
||||
|
||||
// Test multiple tracks
|
||||
TEST(CueParser, MultipleTracksCue)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 MODE1/2352\n"
|
||||
"INDEX 01 00:00:00\n"
|
||||
"TRACK 02 AUDIO\n"
|
||||
"INDEX 00 02:30:65\n"
|
||||
"INDEX 01 02:31:45\n"
|
||||
"TRACK 03 MODE2/2336\n"
|
||||
"INDEX 01 05:45:20\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
|
||||
ASSERT_TRUE(cue_file.Parse(cue, &error));
|
||||
|
||||
// Test track 1
|
||||
const CueParser::Track* track1 = cue_file.GetTrack(1);
|
||||
ASSERT_NE(track1, nullptr);
|
||||
EXPECT_EQ(track1->number, 1);
|
||||
EXPECT_EQ(track1->mode, CueParser::TrackMode::Mode1Raw);
|
||||
|
||||
// Test track 2
|
||||
const CueParser::Track* track2 = cue_file.GetTrack(2);
|
||||
ASSERT_NE(track2, nullptr);
|
||||
EXPECT_EQ(track2->number, 2);
|
||||
EXPECT_EQ(track2->mode, CueParser::TrackMode::Audio);
|
||||
|
||||
// Check track 2 indices
|
||||
const CueParser::MSF* index0 = track2->GetIndex(0);
|
||||
ASSERT_NE(index0, nullptr);
|
||||
EXPECT_EQ(index0->minute, 2);
|
||||
EXPECT_EQ(index0->second, 30);
|
||||
EXPECT_EQ(index0->frame, 65);
|
||||
|
||||
const CueParser::MSF* index1 = track2->GetIndex(1);
|
||||
ASSERT_NE(index1, nullptr);
|
||||
EXPECT_EQ(index1->minute, 2);
|
||||
EXPECT_EQ(index1->second, 31);
|
||||
EXPECT_EQ(index1->frame, 45);
|
||||
|
||||
// Test track 3
|
||||
const CueParser::Track* track3 = cue_file.GetTrack(3);
|
||||
ASSERT_NE(track3, nullptr);
|
||||
EXPECT_EQ(track3->number, 3);
|
||||
EXPECT_EQ(track3->mode, CueParser::TrackMode::Mode2);
|
||||
}
|
||||
|
||||
// Test different track modes
|
||||
TEST(CueParser, DifferentTrackModes)
|
||||
{
|
||||
const std::string cue = "FILE \"audio.wav\" WAVE\n"
|
||||
"TRACK 01 AUDIO\n"
|
||||
"INDEX 01 00:00:00\n"
|
||||
"FILE \"data.bin\" BINARY\n"
|
||||
"TRACK 02 MODE1/2048\n"
|
||||
"INDEX 01 00:00:00\n"
|
||||
"TRACK 03 MODE1/2352\n"
|
||||
"INDEX 01 15:34:50\n"
|
||||
"TRACK 04 MODE2/2048\n"
|
||||
"INDEX 01 30:10:00\n"
|
||||
"TRACK 05 MODE2/2352\n"
|
||||
"INDEX 01 45:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
|
||||
ASSERT_TRUE(cue_file.Parse(cue, &error));
|
||||
|
||||
// Verify track modes
|
||||
const CueParser::Track* track1 = cue_file.GetTrack(1);
|
||||
ASSERT_NE(track1, nullptr);
|
||||
EXPECT_EQ(track1->mode, CueParser::TrackMode::Audio);
|
||||
EXPECT_EQ(track1->file_format, CueParser::FileFormat::Wave);
|
||||
|
||||
const CueParser::Track* track2 = cue_file.GetTrack(2);
|
||||
ASSERT_NE(track2, nullptr);
|
||||
EXPECT_EQ(track2->mode, CueParser::TrackMode::Mode1);
|
||||
EXPECT_EQ(track2->file_format, CueParser::FileFormat::Binary);
|
||||
|
||||
const CueParser::Track* track3 = cue_file.GetTrack(3);
|
||||
ASSERT_NE(track3, nullptr);
|
||||
EXPECT_EQ(track3->mode, CueParser::TrackMode::Mode1Raw);
|
||||
|
||||
const CueParser::Track* track4 = cue_file.GetTrack(4);
|
||||
ASSERT_NE(track4, nullptr);
|
||||
EXPECT_EQ(track4->mode, CueParser::TrackMode::Mode2Form1);
|
||||
|
||||
const CueParser::Track* track5 = cue_file.GetTrack(5);
|
||||
ASSERT_NE(track5, nullptr);
|
||||
EXPECT_EQ(track5->mode, CueParser::TrackMode::Mode2Raw);
|
||||
}
|
||||
|
||||
// Test track flags
|
||||
TEST(CueParser, TrackFlags)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 AUDIO\n"
|
||||
"FLAGS PRE DCP 4CH\n"
|
||||
"INDEX 01 00:00:00\n"
|
||||
"TRACK 02 AUDIO\n"
|
||||
"FLAGS SCMS\n"
|
||||
"INDEX 01 03:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
|
||||
ASSERT_TRUE(cue_file.Parse(cue, &error));
|
||||
|
||||
const CueParser::Track* track1 = cue_file.GetTrack(1);
|
||||
ASSERT_NE(track1, nullptr);
|
||||
EXPECT_TRUE(track1->HasFlag(CueParser::TrackFlag::PreEmphasis));
|
||||
EXPECT_TRUE(track1->HasFlag(CueParser::TrackFlag::CopyPermitted));
|
||||
EXPECT_TRUE(track1->HasFlag(CueParser::TrackFlag::FourChannelAudio));
|
||||
EXPECT_FALSE(track1->HasFlag(CueParser::TrackFlag::SerialCopyManagement));
|
||||
|
||||
const CueParser::Track* track2 = cue_file.GetTrack(2);
|
||||
ASSERT_NE(track2, nullptr);
|
||||
EXPECT_FALSE(track2->HasFlag(CueParser::TrackFlag::PreEmphasis));
|
||||
EXPECT_FALSE(track2->HasFlag(CueParser::TrackFlag::CopyPermitted));
|
||||
EXPECT_FALSE(track2->HasFlag(CueParser::TrackFlag::FourChannelAudio));
|
||||
EXPECT_TRUE(track2->HasFlag(CueParser::TrackFlag::SerialCopyManagement));
|
||||
}
|
||||
|
||||
// Test PREGAP handling
|
||||
TEST(CueParser, PregapHandling)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 AUDIO\n"
|
||||
"INDEX 01 00:00:00\n"
|
||||
"TRACK 02 AUDIO\n"
|
||||
"PREGAP 00:02:00\n"
|
||||
"INDEX 01 03:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
|
||||
ASSERT_TRUE(cue_file.Parse(cue, &error));
|
||||
|
||||
const CueParser::Track* track1 = cue_file.GetTrack(1);
|
||||
ASSERT_NE(track1, nullptr);
|
||||
EXPECT_FALSE(track1->zero_pregap.has_value());
|
||||
|
||||
const CueParser::Track* track2 = cue_file.GetTrack(2);
|
||||
ASSERT_NE(track2, nullptr);
|
||||
ASSERT_TRUE(track2->zero_pregap.has_value());
|
||||
EXPECT_EQ(track2->zero_pregap->minute, 0);
|
||||
EXPECT_EQ(track2->zero_pregap->second, 2);
|
||||
EXPECT_EQ(track2->zero_pregap->frame, 0);
|
||||
}
|
||||
|
||||
// Test track lengths are correctly calculated
|
||||
TEST(CueParser, TrackLengthCalculation)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 AUDIO\n"
|
||||
"INDEX 01 00:00:00\n"
|
||||
"TRACK 02 AUDIO\n"
|
||||
"INDEX 00 02:30:00\n"
|
||||
"INDEX 01 02:32:00\n"
|
||||
"TRACK 03 AUDIO\n"
|
||||
"INDEX 01 05:45:20\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
|
||||
ASSERT_TRUE(cue_file.Parse(cue, &error));
|
||||
|
||||
const CueParser::Track* track1 = cue_file.GetTrack(1);
|
||||
ASSERT_NE(track1, nullptr);
|
||||
ASSERT_TRUE(track1->length.has_value());
|
||||
// Track 1 length should be calculated to end at track 2's index 0
|
||||
EXPECT_EQ(track1->length->minute, 2);
|
||||
EXPECT_EQ(track1->length->second, 30);
|
||||
EXPECT_EQ(track1->length->frame, 0);
|
||||
|
||||
const CueParser::Track* track2 = cue_file.GetTrack(2);
|
||||
ASSERT_NE(track2, nullptr);
|
||||
ASSERT_TRUE(track2->length.has_value());
|
||||
// Track 2 length should be calculated to end at track 3's index 1
|
||||
EXPECT_EQ(track2->length->minute, 3);
|
||||
EXPECT_EQ(track2->length->second, 13);
|
||||
EXPECT_EQ(track2->length->frame, 20);
|
||||
}
|
||||
|
||||
// Test missing FILE command
|
||||
TEST(CueParser, MissingFileCommand)
|
||||
{
|
||||
const std::string cue = "TRACK 01 AUDIO\n"
|
||||
"INDEX 01 00:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
ASSERT_FALSE(cue_file.Parse(cue, &error));
|
||||
}
|
||||
|
||||
// Test invalid track number
|
||||
TEST(CueParser, InvalidTrackNumber)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 100 AUDIO\n"
|
||||
"INDEX 01 00:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
ASSERT_FALSE(cue_file.Parse(cue, &error));
|
||||
}
|
||||
|
||||
// Test missing index 1
|
||||
TEST(CueParser, MissingIndex1)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 AUDIO\n"
|
||||
"INDEX 00 00:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
ASSERT_FALSE(cue_file.Parse(cue, &error));
|
||||
}
|
||||
|
||||
// Test invalid index number
|
||||
TEST(CueParser, InvalidIndexNumber)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 AUDIO\n"
|
||||
"INDEX 100 00:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
ASSERT_FALSE(cue_file.Parse(cue, &error));
|
||||
}
|
||||
|
||||
// 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";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
ASSERT_FALSE(cue_file.Parse(cue, &error));
|
||||
}
|
||||
|
||||
// Test duplicate index
|
||||
TEST(CueParser, DuplicateIndex)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 AUDIO\n"
|
||||
"INDEX 01 00:00:00\n"
|
||||
"INDEX 01 00:01:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
ASSERT_FALSE(cue_file.Parse(cue, &error));
|
||||
}
|
||||
|
||||
// Test reverse index order
|
||||
TEST(CueParser, ReverseIndexOrder)
|
||||
{
|
||||
const std::string cue = "FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 AUDIO\n"
|
||||
"INDEX 02 00:01:00\n"
|
||||
"INDEX 01 00:02:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
ASSERT_FALSE(cue_file.Parse(cue, &error));
|
||||
}
|
||||
|
||||
// Test handling of comments and whitespace
|
||||
TEST(CueParser, CommentsAndWhitespace)
|
||||
{
|
||||
const std::string cue = "REM This is a comment\n"
|
||||
" FILE \"game.bin\" BINARY \n"
|
||||
"\n"
|
||||
"REM Another comment\n"
|
||||
" TRACK 01 MODE2/2352 \n"
|
||||
" INDEX 01 00:00:00 \n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
|
||||
ASSERT_TRUE(cue_file.Parse(cue, &error));
|
||||
|
||||
const CueParser::Track* track = cue_file.GetTrack(1);
|
||||
ASSERT_NE(track, nullptr);
|
||||
EXPECT_EQ(track->mode, CueParser::TrackMode::Mode2Raw);
|
||||
}
|
||||
|
||||
// Test handling of multiple files
|
||||
TEST(CueParser, MultipleFiles)
|
||||
{
|
||||
const std::string cue = "FILE \"track1.bin\" BINARY\n"
|
||||
"TRACK 01 MODE1/2352\n"
|
||||
"INDEX 01 00:00:00\n"
|
||||
"FILE \"track2.wav\" WAVE\n"
|
||||
"TRACK 02 AUDIO\n"
|
||||
"INDEX 01 00:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
|
||||
ASSERT_TRUE(cue_file.Parse(cue, &error));
|
||||
|
||||
const CueParser::Track* track1 = cue_file.GetTrack(1);
|
||||
ASSERT_NE(track1, nullptr);
|
||||
EXPECT_EQ(track1->file, "track1.bin");
|
||||
EXPECT_EQ(track1->file_format, CueParser::FileFormat::Binary);
|
||||
|
||||
const CueParser::Track* track2 = cue_file.GetTrack(2);
|
||||
ASSERT_NE(track2, nullptr);
|
||||
EXPECT_EQ(track2->file, "track2.wav");
|
||||
EXPECT_EQ(track2->file_format, CueParser::FileFormat::Wave);
|
||||
}
|
||||
|
||||
// Test ignoring unsupported metadata
|
||||
TEST(CueParser, IgnoreUnsupportedMetadata)
|
||||
{
|
||||
const std::string cue = "CATALOG 1234567890123\n"
|
||||
"TITLE \"Game Title\"\n"
|
||||
"PERFORMER \"Developer\"\n"
|
||||
"FILE \"game.bin\" BINARY\n"
|
||||
"TRACK 01 MODE2/2352\n"
|
||||
"TITLE \"Track Title\"\n"
|
||||
"INDEX 01 00:00:00\n";
|
||||
|
||||
CueParser::File cue_file;
|
||||
Error error;
|
||||
|
||||
ASSERT_TRUE(cue_file.Parse(cue, &error));
|
||||
|
||||
const CueParser::Track* track = cue_file.GetTrack(1);
|
||||
ASSERT_NE(track, nullptr);
|
||||
EXPECT_EQ(track->number, 1);
|
||||
}
|
||||
392
src/util-tests/elf_parser_tests.cpp
Normal file
392
src/util-tests/elf_parser_tests.cpp
Normal file
@@ -0,0 +1,392 @@
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "util/elf_file.h"
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper to create minimal valid ELF file data
|
||||
ELFFile::DataArray CreateValidELFData()
|
||||
{
|
||||
// Create a minimal valid ELF file (32-bit MIPS)
|
||||
constexpr size_t elf_size = 768;
|
||||
ELFFile::DataArray data(elf_size);
|
||||
std::memset(data.data(), 0, data.size());
|
||||
|
||||
// ELF Header
|
||||
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
|
||||
ehdr->e_ident[0] = 0x7F; // Magic bytes
|
||||
ehdr->e_ident[1] = 'E';
|
||||
ehdr->e_ident[2] = 'L';
|
||||
ehdr->e_ident[3] = 'F';
|
||||
ehdr->e_type = ELFFile::ET_EXEC;
|
||||
ehdr->e_machine = ELFFile::EM_MIPS;
|
||||
ehdr->e_version = 1;
|
||||
ehdr->e_entry = 0x80010000; // Entry point
|
||||
ehdr->e_phoff = sizeof(ELFFile::Elf32_Ehdr); // Program header table right after ELF header
|
||||
ehdr->e_shoff =
|
||||
sizeof(ELFFile::Elf32_Ehdr) + 2 * sizeof(ELFFile::Elf32_Phdr); // Section header table after program headers
|
||||
ehdr->e_flags = 0;
|
||||
ehdr->e_ehsize = sizeof(ELFFile::Elf32_Ehdr);
|
||||
ehdr->e_phentsize = sizeof(ELFFile::Elf32_Phdr);
|
||||
ehdr->e_phnum = 2; // Two program headers
|
||||
ehdr->e_shentsize = sizeof(ELFFile::Elf32_Shdr);
|
||||
ehdr->e_shnum = 3; // Three section headers (null + .text + .shstrtab)
|
||||
ehdr->e_shstrndx = 2; // String table is section 2
|
||||
|
||||
// Program Headers
|
||||
auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
|
||||
|
||||
// First program header - loadable segment with code
|
||||
phdr[0].p_type = ELFFile::PT_LOAD;
|
||||
phdr[0].p_offset = 0x100; // Start of section data
|
||||
phdr[0].p_vaddr = 0x80010000; // Virtual address matching entry point
|
||||
phdr[0].p_filesz = 0x100; // Size in file
|
||||
phdr[0].p_memsz = 0x100; // Size in memory
|
||||
phdr[0].p_flags = 5; // Read + execute
|
||||
phdr[0].p_align = 0x1000; // Page alignment
|
||||
|
||||
// Second program header - non-loadable segment
|
||||
phdr[1].p_type = ELFFile::PT_NOTE;
|
||||
|
||||
// Section Headers
|
||||
auto* shdr = reinterpret_cast<ELFFile::Elf32_Shdr*>(data.data() + ehdr->e_shoff);
|
||||
|
||||
// Section 0 - NULL section
|
||||
shdr[0].sh_name = 0;
|
||||
shdr[0].sh_type = ELFFile::SHT_NULL;
|
||||
|
||||
// Section 1 - .text
|
||||
shdr[1].sh_name = 1; // Offset in string table
|
||||
shdr[1].sh_type = ELFFile::SHT_PROGBITS;
|
||||
shdr[1].sh_flags = 6; // Executable, allocated
|
||||
shdr[1].sh_addr = 0x80010000;
|
||||
shdr[1].sh_offset = 0x100; // Start of section data
|
||||
shdr[1].sh_size = 0x100; // Size
|
||||
shdr[1].sh_link = 0;
|
||||
shdr[1].sh_info = 0;
|
||||
shdr[1].sh_addralign = 4;
|
||||
shdr[1].sh_entsize = 0;
|
||||
|
||||
// Section 2 - .shstrtab (string table)
|
||||
shdr[2].sh_name = 7; // Offset in string table
|
||||
shdr[2].sh_type = ELFFile::SHT_STRTAB;
|
||||
shdr[2].sh_flags = 0;
|
||||
shdr[2].sh_addr = 0;
|
||||
shdr[2].sh_offset = 0x200; // String table location
|
||||
shdr[2].sh_size = 0x20; // String table size
|
||||
shdr[2].sh_link = 0;
|
||||
shdr[2].sh_info = 0;
|
||||
shdr[2].sh_addralign = 1;
|
||||
shdr[2].sh_entsize = 0;
|
||||
|
||||
// String table section
|
||||
char* strtab = reinterpret_cast<char*>(data.data() + shdr[2].sh_offset);
|
||||
strtab[0] = '\0'; // Empty string at index 0
|
||||
strcpy(strtab + 1, ".text"); // Section name at index 1
|
||||
strcpy(strtab + 7, ".shstrtab"); // Section name at index 7
|
||||
|
||||
// Add some fake code to the .text section
|
||||
u8* text = data.data() + shdr[1].sh_offset;
|
||||
memset(text, 0xAA, shdr[1].sh_size);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Helper to create invalid ELF data (wrong magic)
|
||||
ELFFile::DataArray CreateInvalidELFData()
|
||||
{
|
||||
auto data = CreateValidELFData();
|
||||
data[1] = 'X'; // Corrupt magic number
|
||||
return data;
|
||||
}
|
||||
|
||||
// Helper to create ELF with wrong machine type
|
||||
ELFFile::DataArray CreateWrongMachineELFData()
|
||||
{
|
||||
auto data = CreateValidELFData();
|
||||
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
|
||||
ehdr->e_machine = 0x42; // Not MIPS
|
||||
return data;
|
||||
}
|
||||
|
||||
// Helper to create ELF with missing entry point
|
||||
ELFFile::DataArray CreateMissingEntryELFData()
|
||||
{
|
||||
auto data = CreateValidELFData();
|
||||
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
|
||||
// auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
|
||||
|
||||
// Set entry point outside of loadable segment
|
||||
ehdr->e_entry = 0x90000000;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Helper to create ELF with out-of-range program header
|
||||
ELFFile::DataArray CreateOutOfRangeProgramHeaderELFData()
|
||||
{
|
||||
auto data = CreateValidELFData();
|
||||
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
|
||||
auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
|
||||
|
||||
// Set offset and size to be out of file range
|
||||
phdr[0].p_offset = static_cast<u32>(data.size()) - 10;
|
||||
phdr[0].p_filesz = 100;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
class ELFParserTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
valid_elf_data = CreateValidELFData();
|
||||
invalid_elf_data = CreateInvalidELFData();
|
||||
wrong_machine_elf_data = CreateWrongMachineELFData();
|
||||
missing_entry_elf_data = CreateMissingEntryELFData();
|
||||
out_of_range_program_header_elf_data = CreateOutOfRangeProgramHeaderELFData();
|
||||
}
|
||||
|
||||
ELFFile::DataArray valid_elf_data;
|
||||
ELFFile::DataArray invalid_elf_data;
|
||||
ELFFile::DataArray wrong_machine_elf_data;
|
||||
ELFFile::DataArray missing_entry_elf_data;
|
||||
ELFFile::DataArray out_of_range_program_header_elf_data;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(ELFParserTest, ValidELFHeader)
|
||||
{
|
||||
Error error;
|
||||
EXPECT_TRUE(ELFFile::IsValidElfHeader(valid_elf_data.cspan(), &error));
|
||||
EXPECT_FALSE(error.IsValid());
|
||||
|
||||
// Test the static header checking method
|
||||
const auto& header = *reinterpret_cast<const ELFFile::Elf32_Ehdr*>(valid_elf_data.data());
|
||||
EXPECT_TRUE(ELFFile::IsValidElfHeader(header, &error));
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, InvalidELFHeader)
|
||||
{
|
||||
Error error;
|
||||
EXPECT_FALSE(ELFFile::IsValidElfHeader(invalid_elf_data.cspan(), &error));
|
||||
EXPECT_TRUE(error.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, WrongMachineType)
|
||||
{
|
||||
Error error;
|
||||
EXPECT_FALSE(ELFFile::IsValidElfHeader(wrong_machine_elf_data.cspan(), &error));
|
||||
EXPECT_TRUE(error.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, TooSmallBuffer)
|
||||
{
|
||||
Error error;
|
||||
std::span<const u8> small_span = valid_elf_data.cspan(10); // Too small for header
|
||||
EXPECT_FALSE(ELFFile::IsValidElfHeader(small_span, &error));
|
||||
EXPECT_TRUE(error.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, OpenValidELF)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
EXPECT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
EXPECT_FALSE(error.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, OpenInvalidELF)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
EXPECT_FALSE(elf.Open(invalid_elf_data, &error));
|
||||
EXPECT_TRUE(error.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, OpenWrongMachineELF)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
EXPECT_FALSE(elf.Open(wrong_machine_elf_data, &error));
|
||||
EXPECT_TRUE(error.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, GetELFHeader)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
const auto& header = elf.GetELFHeader();
|
||||
EXPECT_EQ(header.e_type, ELFFile::ET_EXEC);
|
||||
EXPECT_EQ(header.e_machine, ELFFile::EM_MIPS);
|
||||
EXPECT_EQ(header.e_entry, 0x80010000);
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, GetEntryPoint)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
EXPECT_EQ(elf.GetEntryPoint(), 0x80010000);
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, GetSectionCount)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
EXPECT_EQ(elf.GetSectionCount(), 3u);
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, GetValidSectionHeader)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
// Get .text section
|
||||
const auto* section = elf.GetSectionHeader(1);
|
||||
ASSERT_NE(section, nullptr);
|
||||
EXPECT_EQ(section->sh_type, ELFFile::SHT_PROGBITS);
|
||||
EXPECT_EQ(section->sh_addr, 0x80010000);
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, GetInvalidSectionHeader)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
// Try to get a section with an out-of-bounds index
|
||||
EXPECT_EQ(elf.GetSectionHeader(99), nullptr);
|
||||
|
||||
// Try the special "undefined" section
|
||||
EXPECT_EQ(elf.GetSectionHeader(ELFFile::SHN_UNDEF), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, GetSectionName)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
// Get .text section name
|
||||
const auto* section = elf.GetSectionHeader(1);
|
||||
ASSERT_NE(section, nullptr);
|
||||
EXPECT_EQ(elf.GetSectionName(*section), ".text");
|
||||
|
||||
// Get .shstrtab section name
|
||||
const auto* strtab_section = elf.GetSectionHeader(2);
|
||||
ASSERT_NE(strtab_section, nullptr);
|
||||
EXPECT_EQ(elf.GetSectionName(*strtab_section), ".shstrtab");
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, GetProgramHeaderCount)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
EXPECT_EQ(elf.GetProgramHeaderCount(), 2u);
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, GetValidProgramHeader)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
// Get loadable segment
|
||||
const auto* phdr = elf.GetProgramHeader(0);
|
||||
ASSERT_NE(phdr, nullptr);
|
||||
EXPECT_EQ(phdr->p_type, ELFFile::PT_LOAD);
|
||||
EXPECT_EQ(phdr->p_vaddr, 0x80010000u);
|
||||
|
||||
// Get note segment
|
||||
const auto* phdr2 = elf.GetProgramHeader(1);
|
||||
ASSERT_NE(phdr2, nullptr);
|
||||
EXPECT_EQ(phdr2->p_type, ELFFile::PT_NOTE);
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, GetInvalidProgramHeader)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
// Try to get a program header with an out-of-bounds index
|
||||
EXPECT_EQ(elf.GetProgramHeader(99), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, LoadExecutableSections)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
|
||||
|
||||
// Track loaded sections
|
||||
struct LoadedSection
|
||||
{
|
||||
std::vector<u8> data;
|
||||
u32 vaddr;
|
||||
u32 size;
|
||||
};
|
||||
std::vector<LoadedSection> loaded_sections;
|
||||
|
||||
bool result = elf.LoadExecutableSections(
|
||||
[&loaded_sections](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) {
|
||||
LoadedSection section;
|
||||
section.data.assign(data.begin(), data.end());
|
||||
section.vaddr = dest_vaddr;
|
||||
section.size = dest_size;
|
||||
loaded_sections.push_back(std::move(section));
|
||||
return true;
|
||||
},
|
||||
&error);
|
||||
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_FALSE(error.IsValid());
|
||||
|
||||
// We should have loaded one section (the loadable segment)
|
||||
ASSERT_EQ(loaded_sections.size(), 1u);
|
||||
EXPECT_EQ(loaded_sections[0].vaddr, 0x80010000u);
|
||||
EXPECT_EQ(loaded_sections[0].size, 0x100u);
|
||||
EXPECT_EQ(loaded_sections[0].data[0], 0xAAu); // Check first byte of our fake code
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, MissingEntryPoint)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(missing_entry_elf_data), &error));
|
||||
|
||||
bool result = elf.LoadExecutableSections(
|
||||
[](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) { return true; }, &error);
|
||||
|
||||
EXPECT_FALSE(result);
|
||||
EXPECT_TRUE(error.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(ELFParserTest, OutOfRangeProgramHeader)
|
||||
{
|
||||
ELFFile elf;
|
||||
Error error;
|
||||
ASSERT_TRUE(elf.Open(std::move(out_of_range_program_header_elf_data), &error));
|
||||
|
||||
bool result = elf.LoadExecutableSections(
|
||||
[](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) { return true; }, &error);
|
||||
|
||||
EXPECT_FALSE(result);
|
||||
EXPECT_TRUE(error.IsValid());
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
|
||||
<ClCompile Include="animated_image_tests.cpp" />
|
||||
<ClCompile Include="cue_parser_tests.cpp" />
|
||||
<ClCompile Include="elf_parser_tests.cpp" />
|
||||
<ClCompile Include="image_tests.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <sstream>
|
||||
|
||||
LOG_CHANNEL(CueParser);
|
||||
|
||||
@@ -71,6 +72,26 @@ bool CueParser::File::Parse(std::FILE* fp, Error* error)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CueParser::File::Parse(const std::string& buffer, Error* error)
|
||||
{
|
||||
u32 line_number = 1;
|
||||
std::istringstream ss(buffer);
|
||||
for (std::string line; std::getline(ss, line);)
|
||||
{
|
||||
if (!ParseLine(line.c_str(), line_number, error))
|
||||
return false;
|
||||
line_number++;
|
||||
}
|
||||
|
||||
if (!CompleteLastTrack(line_number, error))
|
||||
return false;
|
||||
|
||||
if (!SetTrackLengths(line_number, error))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CueParser::File::SetError(u32 line_number, Error* error, const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cd_image.h"
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -69,6 +73,7 @@ public:
|
||||
const Track* GetTrack(u32 n) const;
|
||||
|
||||
bool Parse(std::FILE* fp, Error* error);
|
||||
bool Parse(const std::string& buffer, Error* error);
|
||||
|
||||
private:
|
||||
Track* GetMutableTrack(u32 n);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/types.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
class Error;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user