diff --git a/CMakeLists.txt b/CMakeLists.txt index d448fcc0..608fe26b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,7 @@ target_sources(redumper "filesystem/iso9660/iso9660_entry.ixx" "filesystem/iso9660/iso9660_map.ixx" "filesystem/udf/udf.ixx" + "filesystem/udf/udf_browser.ixx" "filesystem/udf/udf_defs.ixx" "hash/block_hasher.ixx" "hash/md5.ixx" diff --git a/filesystem/udf/udf.ixx b/filesystem/udf/udf.ixx index a91e89a6..ba078ed0 100644 --- a/filesystem/udf/udf.ixx +++ b/filesystem/udf/udf.ixx @@ -1,3 +1,4 @@ export module filesystem.udf; +export import :browser; export import :defs; diff --git a/filesystem/udf/udf_browser.ixx b/filesystem/udf/udf_browser.ixx new file mode 100644 index 00000000..609bacd4 --- /dev/null +++ b/filesystem/udf/udf_browser.ixx @@ -0,0 +1,112 @@ +module; +#include +#include +#include + +export module filesystem.udf:browser; + +import :defs; + +import cd.cdrom; +import readers.data_reader; + +export namespace gpsxre::udf +{ + +class Browser +{ +public: + const static std::optional findAnchorVolumeDescriptorPointer(DataReader *data_reader) + { + std::vector data(FORM1_DATA_SIZE); + data_reader->read(data.data(), AVDP_PRIMARY_LBA, 1); + + auto const &avdp = (udf::AnchorVolumeDescriptorPointer &)data[0]; + + if(avdp.descriptor_tag.tag_identifier == udf::TagIdentifier::ANCHOR_POINTER) + { + return avdp; + } + else + { + return {}; + } + } + + const static std::optional findDescriptorOffset(DataReader *data_reader, ExtentDescriptor main_vds, TagIdentifier type) + { + for(uint32_t lba = main_vds.location; lba <= main_vds.location + ((main_vds.length - 1) / FORM1_DATA_SIZE); ++lba) + { + std::vector data(FORM1_DATA_SIZE); + data_reader->read(data.data(), lba, 1); + + auto const &tag = (udf::DescriptorTag &)data[0]; + + if(tag.tag_identifier == type) + { + return lba; + } + else if(tag.tag_identifier == udf::TagIdentifier::TERMINATING) + { + break; + } + } + + return {}; + } + + static std::shared_ptr rootDirectory(DataReader *data_reader) + { + // Try and find the AVDP so we can look through the volume descriptor sequence. + auto const avdp = findAnchorVolumeDescriptorPointer(data_reader); + if(avdp.has_value()) + { + // Search for a partition in the volume descriptor sequence. This is probably partition 0. + auto partition_offset = findDescriptorOffset(data_reader, avdp.value().main_vds, udf::TagIdentifier::PARTITION); + if(!partition_offset.has_value()) + { + return {}; + } + std::vector partition_data(FORM1_DATA_SIZE); + + data_reader->read(partition_data.data(), partition_offset.value(), 1); + struct udf::PartitionDescriptor partition_descriptor = (udf::PartitionDescriptor &)partition_data[0]; + + // Search for the logical volume descriptor. This contains the extent of the file set descriptor. + auto lvd_offset = findDescriptorOffset(data_reader, avdp.value().main_vds, udf::TagIdentifier::LOGICAL); + if(!lvd_offset.has_value()) + { + return {}; + } + std::vector lvd_data(FORM1_DATA_SIZE); + data_reader->read(lvd_data.data(), lvd_offset.value(), 1); + struct udf::LogicalVolumeDescriptor logical_volume_descriptor = (udf::LogicalVolumeDescriptor &)lvd_data[0]; + + auto const &file_set_descriptor_extent = (udf::long_ad &)logical_volume_descriptor.logical_volume_contents_use; + + // If we have the partition the file set descriptor is found in, parse it to find the root directory ICB extent. + if(partition_descriptor.partition_number == file_set_descriptor_extent.extent_location.partition_reference_number) + { + std::vector fsd_data(FORM1_DATA_SIZE); + data_reader->read(fsd_data.data(), partition_descriptor.partition_starting_location + file_set_descriptor_extent.extent_location.logical_block_number, 1); + auto const &file_set_descriptor = (udf::FileSetDescriptor &)fsd_data[0]; + + auto const &root_directory_icb_extent = file_set_descriptor.root_directory_icb; + + // If we have the partition the root directory is in, read and return it. + if(partition_descriptor.partition_number == root_directory_icb_extent.extent_location.partition_reference_number) + { + std::vector root_directory_data(FORM1_DATA_SIZE); + data_reader->read(root_directory_data.data(), partition_descriptor.partition_starting_location + root_directory_icb_extent.extent_location.logical_block_number, 1); + auto const &root_directory_file_entry = (udf::FileEntry &)root_directory_data[0]; + + return std::make_shared(root_directory_file_entry); + } + } + } + + return {}; + } +}; + +} diff --git a/filesystem/udf/udf_defs.ixx b/filesystem/udf/udf_defs.ixx index 566e6360..cdae123b 100644 --- a/filesystem/udf/udf_defs.ixx +++ b/filesystem/udf/udf_defs.ixx @@ -64,6 +64,122 @@ struct AnchorVolumeDescriptorPointer uint8_t reserved[480]; }; +struct timestamp +{ + uint16_t type_and_timezone; + int16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t centiseconds; + uint8_t hundreds_of_microseconds; + uint8_t microseconds; +}; + +struct charspec +{ + uint8_t character_set_type; + uint8_t character_set_info[63]; +}; + +struct lb_addr +{ + uint32_t logical_block_number; + uint16_t partition_reference_number; +}; + +struct long_ad +{ + uint32_t extent_length; + lb_addr extent_location; + uint8_t implementation_use[6]; +}; + +struct EntityID +{ + uint8_t flags; + uint8_t identifier[23]; + uint8_t identifier_suffix[8]; +}; + +struct LogicalVolumeDescriptor +{ + DescriptorTag descriptor_tag; + uint32_t volume_descriptor_sequence_number; + charspec descriptor_character_set; + char logical_volume_identifier[128]; + uint32_t logical_block_size; + EntityID domain_identifier; + uint8_t logical_volume_contents_use[16]; + uint32_t map_table_length; + uint32_t number_of_partition_maps; + EntityID implementation_identifier; + uint8_t implementation_use[128]; + ExtentDescriptor integrity_sequence_extent; + uint8_t PartitionMaps[]; +}; + +struct FileSetDescriptor +{ + DescriptorTag descriptor_tag; + timestamp recording_date_and_time; + uint16_t interchange_level; + uint16_t maximum_interchange_level; + uint32_t character_set_list; + uint32_t maximum_character_set_list; + uint32_t file_set_number; + uint32_t file_set_descriptor_number; + charspec logical_volume_identifier_character_set; + char logical_volume_identifier[128]; + charspec file_set_character_set; + char file_set_identifier[32]; + char copyright_file_identifier[32]; + char abstract_file_identifier[32]; + long_ad root_directory_icb; + EntityID domain_identifier; + long_ad next_extent; + long_ad system_stream_directory_icb; + uint8_t reserved[32]; +}; + +struct icbtag +{ + uint32_t prior_recorded_number_of_direct_entries; + uint16_t strategy_type; + uint8_t strategy_parameter[2]; + uint16_t maximum_number_of_entries; + uint8_t reserved; + uint8_t file_type; + lb_addr parent_icb_location; + uint16_t flags; +}; + +struct FileEntry +{ + DescriptorTag descriptor_tag; + icbtag icb_tag; + uint32_t uid; + uint32_t gid; + uint32_t permissions; + uint16_t file_link_count; + uint8_t record_format; + uint8_t record_display_attributes; + uint32_t record_length; + uint64_t information_length; + uint64_t logical_blocks_recorded; + timestamp access_time; + timestamp modification_time; + timestamp attribute_time; + uint32_t checkpoint; + long_ad extended_attribute_icb; + EntityID implementation_identifier; + uint64_t unique_id; + uint32_t length_of_extended_attributes; + uint32_t length_of_allocation_descriptors; + uint8_t extended_attributes_and_allocation_descriptors[]; +}; struct PartitionDescriptor { @@ -71,12 +187,12 @@ struct PartitionDescriptor uint32_t volume_descriptor_sequence_number; uint16_t partition_flags; uint16_t partition_number; - uint8_t partition_contents[32]; + EntityID partition_contents; uint8_t partition_contents_use[128]; uint32_t access_type; uint32_t partition_starting_location; uint32_t partition_length; - uint8_t implementation_identifier[32]; + EntityID implementation_identifier; uint8_t implementation_use[128]; uint8_t reserved[156]; };