From 5730605e7d21803aeb53f6ad1d6de99dc3e7e215 Mon Sep 17 00:00:00 2001 From: whatever-industries <77seventy77@users.noreply.github.com> Date: Fri, 14 Aug 2026 20:44:15 -0700 Subject: [PATCH 1/2] Fix UDF trimming after reserve descriptors --- CMakeLists.txt | 1 + dvd/dvd_dump.ixx | 38 +++++++++++++++++++++++----- filesystem/udf/udf_size.ixx | 39 +++++++++++++++++++++++++++++ tests/gtest/CMakeLists.txt | 5 ++++ tests/gtest/test_udf.cc | 50 +++++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 filesystem/udf/udf_size.ixx create mode 100644 tests/gtest/test_udf.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f2bff75..b0899996 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,7 @@ target_sources(redumper "filesystem/udf/udf.ixx" "filesystem/udf/udf_browser.ixx" "filesystem/udf/udf_defs.ixx" + "filesystem/udf/udf_size.ixx" "hash/block_hasher.ixx" "hash/md5.ixx" "hash/sha1.ixx" diff --git a/dvd/dvd_dump.ixx b/dvd/dvd_dump.ixx index 0b3c26cd..4990df67 100644 --- a/dvd/dvd_dump.ixx +++ b/dvd/dvd_dump.ixx @@ -26,6 +26,7 @@ import dvd.nintendo; import dvd.xbox; import filesystem.iso9660; import filesystem.udf; +import filesystem.udf_size; import interval_set; import options; import range; @@ -415,6 +416,9 @@ struct FilesystemContext bool search = true; bool udf = false; std::vector> udf_vds; + uint32_t udf_partition_end = 0; + std::optional udf_reserve_vds; + std::optional udf_trailing_avdp_lba; }; @@ -458,10 +462,11 @@ std::optional> filesystem_search_size(FilesystemContex // ordering is intentional ctx.udf_vds.emplace_back(avdp.reserve_vds.location, scale_up(avdp.reserve_vds.length, FORM1_DATA_SIZE)); ctx.udf_vds.emplace_back(avdp.main_vds.location, scale_up(avdp.main_vds.length, FORM1_DATA_SIZE)); + ctx.udf_reserve_vds = avdp.reserve_vds; } } - if(!ctx.udf_vds.empty() && ctx.udf_vds.back().first + ctx.udf_vds.back().second <= lba) + if(!ctx.udf_vds.empty() && (uint64_t)ctx.udf_vds.back().first + ctx.udf_vds.back().second <= lba) { std::vector sector_data_file(ctx.udf_vds.back().second * FORM1_DATA_SIZE); std::vector sector_state_file(ctx.udf_vds.back().second); @@ -482,21 +487,42 @@ std::optional> filesystem_search_size(FilesystemContex { auto const &partition = (udf::PartitionDescriptor &)sector_data_file[i * FORM1_DATA_SIZE]; - sectors_count = std::max(sectors_count, partition.partition_starting_location + partition.partition_length); + uint64_t partition_end = (uint64_t)partition.partition_starting_location + partition.partition_length; + if(partition_end <= std::numeric_limits::max()) + sectors_count = std::max(sectors_count, (uint32_t)partition_end); } else if(tag.tag_identifier == udf::TagIdentifier::TERMINATING) break; } - ctx.udf_vds.clear(); + ctx.udf_vds.pop_back(); + ctx.udf_partition_end = std::max(ctx.udf_partition_end, sectors_count); - if(sectors_count) - // account for trailing AVDP - ss = std::make_pair(sectors_count + 1, true); + auto reserve_vds = ctx.udf_reserve_vds.value_or(udf::ExtentDescriptor{}); + ctx.udf_trailing_avdp_lba = udf::get_trailing_avdp_lba(ctx.udf_partition_end, reserve_vds.location, reserve_vds.length, FORM1_DATA_SIZE); } else ctx.udf_vds.pop_back(); } + + if(ctx.udf_trailing_avdp_lba && lba >= *ctx.udf_trailing_avdp_lba) + { + auto const &tag = (udf::DescriptorTag &)data[0]; + + if(udf::is_trailing_avdp_search_lba(lba, *ctx.udf_trailing_avdp_lba)) + { + if(tag.tag_identifier == udf::TagIdentifier::ANCHOR_POINTER && tag.tag_location == lba && lba < std::numeric_limits::max()) + { + ss = std::make_pair(lba + 1, true); + ctx.udf = false; + } + } + else + { + LOG("warning: UDF trailing AVDP not found after reserve VDS"); + ctx.udf = false; + } + } } return ss; diff --git a/filesystem/udf/udf_size.ixx b/filesystem/udf/udf_size.ixx new file mode 100644 index 00000000..4df76508 --- /dev/null +++ b/filesystem/udf/udf_size.ixx @@ -0,0 +1,39 @@ +module; +#include +#include +#include +#include + +export module filesystem.udf_size; + + + +export namespace gpsxre::udf +{ + +constexpr uint32_t TRAILING_AVDP_SEARCH_SECTORS = 256; + + +constexpr std::optional get_trailing_avdp_lba(uint32_t partition_end, uint32_t reserve_vds_location, uint32_t reserve_vds_length, uint32_t sector_size) +{ + if(!partition_end || !sector_size) + return std::nullopt; + + uint64_t reserve_length = ((uint64_t)reserve_vds_length + sector_size - 1) / sector_size; + uint64_t reserve_end = reserve_length ? (uint64_t)reserve_vds_location + reserve_length : 0; + uint64_t metadata_end = std::max(partition_end, reserve_end); + + // The sector at metadata_end is the first possible location of the trailing AVDP. + if(metadata_end >= std::numeric_limits::max()) + return std::nullopt; + + return metadata_end; +} + + +constexpr bool is_trailing_avdp_search_lba(uint32_t lba, uint32_t expected_lba) +{ + return lba >= expected_lba && (uint64_t)lba < (uint64_t)expected_lba + TRAILING_AVDP_SEARCH_SECTORS; +} + +} diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt index 6813a47b..7c139c31 100644 --- a/tests/gtest/CMakeLists.txt +++ b/tests/gtest/CMakeLists.txt @@ -87,3 +87,8 @@ add_gtest(gtest_bit_copy SOURCE "test_bit_copy.cc" MODULES "${CMAKE_SOURCE_DIR}/utils/misc.ixx" ) + +add_gtest(gtest_udf + SOURCE "test_udf.cc" + MODULES "${CMAKE_SOURCE_DIR}/filesystem/udf/udf_size.ixx" +) diff --git a/tests/gtest/test_udf.cc b/tests/gtest/test_udf.cc new file mode 100644 index 00000000..77fa8529 --- /dev/null +++ b/tests/gtest/test_udf.cc @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +import filesystem.udf_size; + +using namespace gpsxre; + + +TEST(UDF, TrailingAVDPLbaFollowsReserveVDS) +{ + constexpr uint32_t sector_size = 2048; + constexpr uint32_t partition_start = 277; + constexpr uint32_t partition_length = 23728682; + auto trailing_avdp_lba = udf::get_trailing_avdp_lba(partition_start + partition_length, 23728959, 32768, sector_size); + + ASSERT_EQ(trailing_avdp_lba, 23728975); + EXPECT_EQ(*trailing_avdp_lba + 1, 23728976); +} + + +TEST(UDF, TrailingAVDPLbaUsesPartitionEndWithoutReserveVDS) +{ + EXPECT_EQ(udf::get_trailing_avdp_lba(1000, 0, 0, 2048), 1000); +} + + +TEST(UDF, TrailingAVDPLbaUsesLargestMetadataEnd) +{ + EXPECT_EQ(udf::get_trailing_avdp_lba(1000, 500, 2048, 2048), 1000); + EXPECT_EQ(udf::get_trailing_avdp_lba(900, 1000, 2049, 2048), 1002); +} + + +TEST(UDF, TrailingAVDPLbaRejectsInvalidOrOverflowingValues) +{ + EXPECT_EQ(udf::get_trailing_avdp_lba(0, 0, 0, 2048), std::nullopt); + EXPECT_EQ(udf::get_trailing_avdp_lba(1000, 0, 0, 0), std::nullopt); + EXPECT_EQ(udf::get_trailing_avdp_lba(1000, std::numeric_limits::max(), 2048, 2048), std::nullopt); +} + + +TEST(UDF, TrailingAVDPSearchIsBounded) +{ + EXPECT_FALSE(udf::is_trailing_avdp_search_lba(999, 1000)); + EXPECT_TRUE(udf::is_trailing_avdp_search_lba(1000, 1000)); + EXPECT_TRUE(udf::is_trailing_avdp_search_lba(1255, 1000)); + EXPECT_FALSE(udf::is_trailing_avdp_search_lba(1256, 1000)); +} From 996077702316c885cabcd59a3fb400157c160ac1 Mon Sep 17 00:00:00 2001 From: whatever-industries <77seventy77@users.noreply.github.com> Date: Fri, 14 Aug 2026 23:27:27 -0700 Subject: [PATCH 2/2] Detect UDF size from primary anchor --- dvd/dvd_dump.ixx | 47 +++++++++++++------------------------ filesystem/udf/udf_size.ixx | 15 +++--------- tests/gtest/test_udf.cc | 34 +++++++++------------------ 3 files changed, 30 insertions(+), 66 deletions(-) diff --git a/dvd/dvd_dump.ixx b/dvd/dvd_dump.ixx index 4990df67..bbf73762 100644 --- a/dvd/dvd_dump.ixx +++ b/dvd/dvd_dump.ixx @@ -416,9 +416,7 @@ struct FilesystemContext bool search = true; bool udf = false; std::vector> udf_vds; - uint32_t udf_partition_end = 0; std::optional udf_reserve_vds; - std::optional udf_trailing_avdp_lba; }; @@ -453,19 +451,22 @@ std::optional> filesystem_search_size(FilesystemContex } } - if(ctx.udf) + // A valid primary AVDP is sufficient to detect UDF even when the volume + // recognition sequence is absent or not recognized. + if(lba == udf::AVDP_PRIMARY_LBA) { - if(lba == udf::AVDP_PRIMARY_LBA) + if(auto const &avdp = (udf::AnchorVolumeDescriptorPointer &)data[0]; avdp.descriptor_tag.tag_identifier == udf::TagIdentifier::ANCHOR_POINTER && avdp.descriptor_tag.tag_location == lba) { - if(auto const &avdp = (udf::AnchorVolumeDescriptorPointer &)data[0]; avdp.descriptor_tag.tag_identifier == udf::TagIdentifier::ANCHOR_POINTER) - { - // ordering is intentional - ctx.udf_vds.emplace_back(avdp.reserve_vds.location, scale_up(avdp.reserve_vds.length, FORM1_DATA_SIZE)); - ctx.udf_vds.emplace_back(avdp.main_vds.location, scale_up(avdp.main_vds.length, FORM1_DATA_SIZE)); - ctx.udf_reserve_vds = avdp.reserve_vds; - } + ctx.udf = true; + // ordering is intentional + ctx.udf_vds.emplace_back(avdp.reserve_vds.location, scale_up(avdp.reserve_vds.length, FORM1_DATA_SIZE)); + ctx.udf_vds.emplace_back(avdp.main_vds.location, scale_up(avdp.main_vds.length, FORM1_DATA_SIZE)); + ctx.udf_reserve_vds = avdp.reserve_vds; } + } + if(ctx.udf) + { if(!ctx.udf_vds.empty() && (uint64_t)ctx.udf_vds.back().first + ctx.udf_vds.back().second <= lba) { std::vector sector_data_file(ctx.udf_vds.back().second * FORM1_DATA_SIZE); @@ -495,33 +496,17 @@ std::optional> filesystem_search_size(FilesystemContex break; } - ctx.udf_vds.pop_back(); - ctx.udf_partition_end = std::max(ctx.udf_partition_end, sectors_count); + ctx.udf_vds.clear(); auto reserve_vds = ctx.udf_reserve_vds.value_or(udf::ExtentDescriptor{}); - ctx.udf_trailing_avdp_lba = udf::get_trailing_avdp_lba(ctx.udf_partition_end, reserve_vds.location, reserve_vds.length, FORM1_DATA_SIZE); - } - else - ctx.udf_vds.pop_back(); - } - - if(ctx.udf_trailing_avdp_lba && lba >= *ctx.udf_trailing_avdp_lba) - { - auto const &tag = (udf::DescriptorTag &)data[0]; - - if(udf::is_trailing_avdp_search_lba(lba, *ctx.udf_trailing_avdp_lba)) - { - if(tag.tag_identifier == udf::TagIdentifier::ANCHOR_POINTER && tag.tag_location == lba && lba < std::numeric_limits::max()) + if(auto volume_sectors_count = udf::get_volume_sectors_count(sectors_count, reserve_vds.location, reserve_vds.length, FORM1_DATA_SIZE); volume_sectors_count) { - ss = std::make_pair(lba + 1, true); + ss = std::make_pair(*volume_sectors_count, true); ctx.udf = false; } } else - { - LOG("warning: UDF trailing AVDP not found after reserve VDS"); - ctx.udf = false; - } + ctx.udf_vds.pop_back(); } } diff --git a/filesystem/udf/udf_size.ixx b/filesystem/udf/udf_size.ixx index 4df76508..4989ec5b 100644 --- a/filesystem/udf/udf_size.ixx +++ b/filesystem/udf/udf_size.ixx @@ -11,10 +11,7 @@ export module filesystem.udf_size; export namespace gpsxre::udf { -constexpr uint32_t TRAILING_AVDP_SEARCH_SECTORS = 256; - - -constexpr std::optional get_trailing_avdp_lba(uint32_t partition_end, uint32_t reserve_vds_location, uint32_t reserve_vds_length, uint32_t sector_size) +constexpr std::optional get_volume_sectors_count(uint32_t partition_end, uint32_t reserve_vds_location, uint32_t reserve_vds_length, uint32_t sector_size) { if(!partition_end || !sector_size) return std::nullopt; @@ -23,17 +20,11 @@ constexpr std::optional get_trailing_avdp_lba(uint32_t partition_end, uint64_t reserve_end = reserve_length ? (uint64_t)reserve_vds_location + reserve_length : 0; uint64_t metadata_end = std::max(partition_end, reserve_end); - // The sector at metadata_end is the first possible location of the trailing AVDP. + // Account for the trailing AVDP immediately following the filesystem metadata. if(metadata_end >= std::numeric_limits::max()) return std::nullopt; - return metadata_end; -} - - -constexpr bool is_trailing_avdp_search_lba(uint32_t lba, uint32_t expected_lba) -{ - return lba >= expected_lba && (uint64_t)lba < (uint64_t)expected_lba + TRAILING_AVDP_SEARCH_SECTORS; + return metadata_end + 1; } } diff --git a/tests/gtest/test_udf.cc b/tests/gtest/test_udf.cc index 77fa8529..da0bbbf2 100644 --- a/tests/gtest/test_udf.cc +++ b/tests/gtest/test_udf.cc @@ -8,43 +8,31 @@ import filesystem.udf_size; using namespace gpsxre; -TEST(UDF, TrailingAVDPLbaFollowsReserveVDS) +TEST(UDF, VolumeSectorsCountIncludesReserveVDSAndTrailingAVDP) { constexpr uint32_t sector_size = 2048; constexpr uint32_t partition_start = 277; constexpr uint32_t partition_length = 23728682; - auto trailing_avdp_lba = udf::get_trailing_avdp_lba(partition_start + partition_length, 23728959, 32768, sector_size); - - ASSERT_EQ(trailing_avdp_lba, 23728975); - EXPECT_EQ(*trailing_avdp_lba + 1, 23728976); -} - - -TEST(UDF, TrailingAVDPLbaUsesPartitionEndWithoutReserveVDS) -{ - EXPECT_EQ(udf::get_trailing_avdp_lba(1000, 0, 0, 2048), 1000); + EXPECT_EQ(udf::get_volume_sectors_count(partition_start + partition_length, 23728959, 32768, sector_size), 23728976); } -TEST(UDF, TrailingAVDPLbaUsesLargestMetadataEnd) +TEST(UDF, VolumeSectorsCountUsesPartitionEndWithoutReserveVDS) { - EXPECT_EQ(udf::get_trailing_avdp_lba(1000, 500, 2048, 2048), 1000); - EXPECT_EQ(udf::get_trailing_avdp_lba(900, 1000, 2049, 2048), 1002); + EXPECT_EQ(udf::get_volume_sectors_count(1000, 0, 0, 2048), 1001); } -TEST(UDF, TrailingAVDPLbaRejectsInvalidOrOverflowingValues) +TEST(UDF, VolumeSectorsCountUsesLargestMetadataEnd) { - EXPECT_EQ(udf::get_trailing_avdp_lba(0, 0, 0, 2048), std::nullopt); - EXPECT_EQ(udf::get_trailing_avdp_lba(1000, 0, 0, 0), std::nullopt); - EXPECT_EQ(udf::get_trailing_avdp_lba(1000, std::numeric_limits::max(), 2048, 2048), std::nullopt); + EXPECT_EQ(udf::get_volume_sectors_count(1000, 500, 2048, 2048), 1001); + EXPECT_EQ(udf::get_volume_sectors_count(900, 1000, 2049, 2048), 1003); } -TEST(UDF, TrailingAVDPSearchIsBounded) +TEST(UDF, VolumeSectorsCountRejectsInvalidOrOverflowingValues) { - EXPECT_FALSE(udf::is_trailing_avdp_search_lba(999, 1000)); - EXPECT_TRUE(udf::is_trailing_avdp_search_lba(1000, 1000)); - EXPECT_TRUE(udf::is_trailing_avdp_search_lba(1255, 1000)); - EXPECT_FALSE(udf::is_trailing_avdp_search_lba(1256, 1000)); + EXPECT_EQ(udf::get_volume_sectors_count(0, 0, 0, 2048), std::nullopt); + EXPECT_EQ(udf::get_volume_sectors_count(1000, 0, 0, 0), std::nullopt); + EXPECT_EQ(udf::get_volume_sectors_count(1000, std::numeric_limits::max(), 2048, 2048), std::nullopt); }