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..bbf73762 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,7 @@ struct FilesystemContext bool search = true; bool udf = false; std::vector> udf_vds; + std::optional udf_reserve_vds; }; @@ -449,19 +451,23 @@ 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 = 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_vds.empty() && ctx.udf_vds.back().first + ctx.udf_vds.back().second <= lba) + 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); std::vector sector_state_file(ctx.udf_vds.back().second); @@ -482,7 +488,9 @@ 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; @@ -490,9 +498,12 @@ std::optional> filesystem_search_size(FilesystemContex ctx.udf_vds.clear(); - 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{}); + 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(*volume_sectors_count, true); + ctx.udf = false; + } } else ctx.udf_vds.pop_back(); diff --git a/filesystem/udf/udf_size.ixx b/filesystem/udf/udf_size.ixx new file mode 100644 index 00000000..4989ec5b --- /dev/null +++ b/filesystem/udf/udf_size.ixx @@ -0,0 +1,30 @@ +module; +#include +#include +#include +#include + +export module filesystem.udf_size; + + + +export namespace gpsxre::udf +{ + +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; + + 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); + + // Account for the trailing AVDP immediately following the filesystem metadata. + if(metadata_end >= std::numeric_limits::max()) + return std::nullopt; + + return metadata_end + 1; +} + +} 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..da0bbbf2 --- /dev/null +++ b/tests/gtest/test_udf.cc @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +import filesystem.udf_size; + +using namespace gpsxre; + + +TEST(UDF, VolumeSectorsCountIncludesReserveVDSAndTrailingAVDP) +{ + constexpr uint32_t sector_size = 2048; + constexpr uint32_t partition_start = 277; + constexpr uint32_t partition_length = 23728682; + EXPECT_EQ(udf::get_volume_sectors_count(partition_start + partition_length, 23728959, 32768, sector_size), 23728976); +} + + +TEST(UDF, VolumeSectorsCountUsesPartitionEndWithoutReserveVDS) +{ + EXPECT_EQ(udf::get_volume_sectors_count(1000, 0, 0, 2048), 1001); +} + + +TEST(UDF, VolumeSectorsCountUsesLargestMetadataEnd) +{ + 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, VolumeSectorsCountRejectsInvalidOrOverflowingValues) +{ + 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); +}