/* * Copyright (c) 2024 - 2026 Ember * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include using namespace ember; TEST(MPQ, Locate_FileNotFound) { const auto result = mpq::locate_archive("test_data/mpqs/no_such_file"); ASSERT_EQ(result.error(), mpq::ErrorCode::file_not_found); } TEST(MPQ, Locate_ArchiveNotFound) { const auto result = mpq::locate_archive("test_data/mpqs/not_an_mpq"); ASSERT_EQ(result.error(), mpq::ErrorCode::no_archive_found); } TEST(MPQ, Locate_ArchiveFound) { const auto result = mpq::locate_archive("test_data/mpqs/v0_0.mpq"); ASSERT_EQ(*result, 0); // offset } TEST(MPQ, Locate_ArchiveFoundOffset) { const auto result = mpq::locate_archive("test_data/mpqs/v0_1.mpq"); ASSERT_EQ(*result, 0x200); // offset } TEST(MPQ, Locate_ArchiveNotFoundOffset) { const auto result = mpq::locate_archive("test_data/mpqs/v0_2.mpq"); ASSERT_TRUE(!result); ASSERT_EQ(result.error(), mpq::ErrorCode::no_archive_found); } TEST(MPQ, Locate_BadFile_PartialMagic) { const auto result = mpq::locate_archive("test_data/mpqs/v0_3.mpq"); ASSERT_TRUE(!result); ASSERT_EQ(result.error(), mpq::ErrorCode::no_archive_found); } TEST(MPQ, Locate_BadHeader_Size) { const auto result = mpq::locate_archive("test_data/mpqs/v0_5.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v0_5.mpq", *result), mpq::exception); } TEST(MPQ, Locate_BadHeader_Version) { const auto result = mpq::locate_archive("test_data/mpqs/v0_6.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v0_6.mpq", *result), mpq::exception); } TEST(MPQ, Locate_BadHeader_Truncated) { const auto result = mpq::locate_archive("test_data/mpqs/v0_7.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v0_7.mpq", *result), mpq::exception); } TEST(MPQ, Open_HeaderTruncated) { const auto result = mpq::locate_archive("test_data/mpqs/v0_8.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v0_8.mpq", *result), mpq::exception); } TEST(MPQ, Open_BadArchiveSize) { const auto result = mpq::locate_archive("test_data/mpqs/v0_9.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v0_9.mpq", *result), mpq::exception); } TEST(MPQ, Open_Success) { const auto result = mpq::locate_archive("test_data/mpqs/v0_10.mpq"); ASSERT_TRUE(result); ASSERT_NO_THROW(mpq::open_archive("test_data/mpqs/v0_10.mpq", *result)); } TEST(MPQ, Open_BadHashTableOffset) { const auto result = mpq::locate_archive("test_data/mpqs/v0_11.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v0_11.mpq", *result), mpq::exception); } TEST(MPQ, Open_BadBlockTableOffset) { const auto result = mpq::locate_archive("test_data/mpqs/v0_12.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v0_12.mpq", *result), mpq::exception); } TEST(MPQ, Open_BadHashTableSize) { const auto result = mpq::locate_archive("test_data/mpqs/v0_13.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v0_13.mpq", *result), mpq::exception); } TEST(MPQ, Open_BadBlockTableSize) { const auto result = mpq::locate_archive("test_data/mpqs/v0_14.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v0_14.mpq", *result), mpq::exception); } TEST(MPQ, Open_BadExtendedBlockTableOffset) { const auto result = mpq::locate_archive("test_data/mpqs/v1_15.mpq"); ASSERT_TRUE(result); ASSERT_THROW(mpq::open_archive("test_data/mpqs/v1_15.mpq", *result), mpq::exception); } // ADPCM & Huffman compression, encrypted TEST(MPQ, Extract_WAV) { auto archive = mpq::open_archive("test_data/mpqs/v1_16.mpq", 0); ASSERT_TRUE(archive); const auto index = archive->file_lookup("owl.wav", 0); ASSERT_NE(index, mpq::Archive::npos); const auto& entry = archive->file_entry(index); mpq::DynamicMemorySink sink; ASSERT_NO_THROW(archive->extract_file("owl.wav", sink)); const auto md5_buf = utility::generate_md5(sink.data()); const auto md5 = Botan::BigInt::from_bytes(md5_buf); Botan::BigInt expected("0x3a66b4b718686cb4d5aa143895290d84"); ASSERT_EQ(md5, expected); } // No compression TEST(MPQ, Extract_MP3) { auto archive = mpq::open_archive("test_data/mpqs/v1_16.mpq", 0); ASSERT_TRUE(archive); const auto index = archive->file_lookup("owl.mp3", 0); ASSERT_NE(index, mpq::Archive::npos); const auto& entry = archive->file_entry(index); mpq::DynamicMemorySink sink; ASSERT_NO_THROW(archive->extract_file("owl.mp3", sink)); const auto md5_buf = utility::generate_md5(sink.data()); const auto md5 = Botan::BigInt::from_bytes(md5_buf); Botan::BigInt expected("0x2f7b030648e1f7ec77109c659bbea3ce"); ASSERT_EQ(md5, expected); } // PKWare compression TEST(MPQ, Extract_Binary) { auto archive = mpq::open_archive("test_data/mpqs/v1_16.mpq", 0); ASSERT_TRUE(archive); const auto index = archive->file_lookup("elevated_1920_1080.ex_", 0); ASSERT_NE(index, mpq::Archive::npos); const auto& entry = archive->file_entry(index); mpq::DynamicMemorySink sink; ASSERT_NO_THROW(archive->extract_file("elevated_1920_1080.ex_", sink)); const auto md5_buf = utility::generate_md5(sink.data()); const auto md5 = Botan::BigInt::from_bytes(md5_buf); Botan::BigInt expected("0xf3b82b404a36a9714bb266007cacd489"); ASSERT_EQ(md5, expected); } // Imploded TEST(MPQ, Extract_JPG) { auto archive = mpq::open_archive("test_data/mpqs/v1_16.mpq", 0); ASSERT_TRUE(archive); const auto& index = archive->file_lookup("ember.jpg", 0); ASSERT_NE(index, mpq::Archive::npos); const auto& entry = archive->file_entry(index); mpq::DynamicMemorySink sink; ASSERT_NO_THROW(archive->extract_file("ember.jpg", sink)); const auto md5_buf = utility::generate_md5(sink.data()); const auto md5 = Botan::BigInt::from_bytes(md5_buf); Botan::BigInt expected("0x8a623acdf09f9388719010d76a5c7a52"); ASSERT_EQ(md5, expected); } // Zlib compression, encrypted, single unit, fix key TEST(MPQ, Extract_PNG) { auto archive = mpq::open_archive("test_data/mpqs/v1_16.mpq", 0); ASSERT_TRUE(archive); const auto index = archive->file_lookup("ember.png", 0); ASSERT_NE(index, mpq::Archive::npos); const auto& entry = archive->file_entry(index); mpq::DynamicMemorySink sink; ASSERT_NO_THROW(archive->extract_file("ember.png", sink)); const auto md5_buf = utility::generate_md5(sink.data()); const auto md5 = Botan::BigInt::from_bytes(md5_buf); Botan::BigInt expected("0xb29a1ad8ebeef28de68fa9bb0325b893"); ASSERT_EQ(md5, expected); } TEST(MPQ, Extract_Listfile) { auto archive = mpq::open_archive("test_data/mpqs/v1_16.mpq", 0); ASSERT_TRUE(archive); const auto index = archive->file_lookup("(listfile)", 0); ASSERT_NE(index, mpq::Archive::npos); const auto& entry = archive->file_entry(index); mpq::DynamicMemorySink sink; ASSERT_NO_THROW(archive->extract_file("(listfile)", sink)); const auto md5_buf = utility::generate_md5(sink.data()); const auto md5 = Botan::BigInt::from_bytes(md5_buf); Botan::BigInt expected("0xc4185e8d87a01ac3057a6498deccab1e"); ASSERT_EQ(md5, expected); } TEST(MPQ, Extract_Attributes) { auto archive = mpq::open_archive("test_data/mpqs/v1_16.mpq", 0); ASSERT_TRUE(archive); const auto index = archive->file_lookup("(attributes)", 0); ASSERT_NE(index, mpq::Archive::npos); const auto& entry = archive->file_entry(index); mpq::DynamicMemorySink sink; ASSERT_NO_THROW(archive->extract_file("(attributes)", sink)); const auto md5_buf = utility::generate_md5(sink.data()); const auto md5 = Botan::BigInt::from_bytes(md5_buf); Botan::BigInt expected("0x44fd6bad334ea24d8901e80ab20c07ba"); ASSERT_EQ(md5, expected); } TEST(MPQ, Extract_Text) { auto archive = mpq::open_archive("test_data/mpqs/v1_16.mpq", 0); ASSERT_TRUE(archive); const auto index = archive->file_lookup("compressed.txt", 0); ASSERT_NE(index, mpq::Archive::npos); const auto& entry = archive->file_entry(index); mpq::DynamicMemorySink sink; ASSERT_NO_THROW(archive->extract_file("compressed.txt", sink)); const auto md5_buf = utility::generate_md5(sink.data()); const auto md5 = Botan::BigInt::from_bytes(md5_buf); Botan::BigInt expected("0xf13fad545731a0b71b2cc17e1acd48f4"); ASSERT_EQ(md5, expected); } TEST(MPQ, Locate_BadAlignment) { alignas(mpq::v0::Header) std::byte data[mpq::header_size_v0]{}; std::span span(data + 1, sizeof(data) - 1); // force misaligned buffer auto result = mpq::locate_archive(span); ASSERT_EQ(result.error(), mpq::ErrorCode::bad_alignment); }