From b36d65ed30421ca0ed8049024ad11c3789de8d24 Mon Sep 17 00:00:00 2001 From: DH Date: Fri, 4 Apr 2025 17:19:32 +0300 Subject: [PATCH] ps3: add iso device --- rpcs3/rpcs3/CMakeLists.txt | 6 + rpcs3/rpcs3/dev/block_dev.hpp | 81 +++++++ rpcs3/rpcs3/dev/iso.cpp | 397 ++++++++++++++++++++++++++++++++++ rpcs3/rpcs3/dev/iso.hpp | 233 ++++++++++++++++++++ 4 files changed, 717 insertions(+) create mode 100644 rpcs3/rpcs3/dev/block_dev.hpp create mode 100644 rpcs3/rpcs3/dev/iso.cpp create mode 100644 rpcs3/rpcs3/dev/iso.hpp diff --git a/rpcs3/rpcs3/CMakeLists.txt b/rpcs3/rpcs3/CMakeLists.txt index b6d4baecc..273b3ca21 100644 --- a/rpcs3/rpcs3/CMakeLists.txt +++ b/rpcs3/rpcs3/CMakeLists.txt @@ -10,6 +10,10 @@ include(CheckFunctionExists) set(CMAKE_CXX_STANDARD 20) +if (MSVC) + add_compile_options(/MT) +endif() + set(ADDITIONAL_LIBS "") if(CMAKE_SYSTEM_NAME STREQUAL "Linux") #on some Linux distros shm_unlink and similar functions are in librt only @@ -77,6 +81,8 @@ if (NOT ANDROID) stb_image.cpp stdafx.cpp + dev/iso.cpp + Input/basic_keyboard_handler.cpp Input/basic_mouse_handler.cpp Input/ds3_pad_handler.cpp diff --git a/rpcs3/rpcs3/dev/block_dev.hpp b/rpcs3/rpcs3/dev/block_dev.hpp new file mode 100644 index 000000000..e3f2ab995 --- /dev/null +++ b/rpcs3/rpcs3/dev/block_dev.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include "Utilities/File.h" +#include +#include + +class block_dev { + std::size_t m_block_size = 0; + std::size_t m_block_count = 0; + +public: + virtual ~block_dev() = default; + + std::size_t block_size() const { return m_block_size; } + std::size_t block_count() const { return m_block_count; } + std::size_t size() const { return block_size() * block_count(); } + + virtual std::size_t read(std::size_t blockIndex, void *data, + std::size_t blockCount) = 0; + + virtual std::size_t write(std::size_t blockIndex, const void *data, + std::size_t blockCount) = 0; + +protected: + void set_block_info(std::size_t size, std::size_t count) { + m_block_size = size; + m_block_count = count; + } +}; + +class file_block_dev final : public block_dev { + fs::file m_file; + +public: + explicit file_block_dev(fs::file file, std::size_t blockSize = 2048) + : m_file(std::move(file)) { + set_block_info(blockSize, m_file.size() / blockSize); + } + + std::size_t read(std::size_t blockIndex, void *data, + std::size_t blockCount) override { + auto result = m_file.read_at(block_size() * blockIndex, data, + blockCount * block_size()); + return result / block_size(); + } + + std::size_t write(std::size_t blockIndex, const void *data, + std::size_t blockCount) override { + auto result = m_file.write_at(block_size() * blockIndex, data, + blockCount * block_size()); + return result / block_size(); + } + + fs::file &file() { return m_file; } + fs::file release() { return std::exchange(m_file, {}); } +}; + +class file_view_block_dev final : public block_dev { + const fs::file *m_file; + +public: + explicit file_view_block_dev(const fs::file &file, + std::size_t blockSize = 2048) + : m_file(&file) { + set_block_info(blockSize, m_file->size() / blockSize); + } + + std::size_t read(std::size_t blockIndex, void *data, + std::size_t blockCount) override { + auto result = m_file->read_at(block_size() * blockIndex, data, + blockCount * block_size()); + return result / block_size(); + } + + std::size_t write(std::size_t blockIndex, const void *data, + std::size_t blockCount) override { + auto result = m_file->write_at(block_size() * blockIndex, data, + blockCount * block_size()); + return result / block_size(); + } +}; diff --git a/rpcs3/rpcs3/dev/iso.cpp b/rpcs3/rpcs3/dev/iso.cpp new file mode 100644 index 000000000..430eb1fcf --- /dev/null +++ b/rpcs3/rpcs3/dev/iso.cpp @@ -0,0 +1,397 @@ + +#include "iso.hpp" +#include "Utilities/File.h" +#include "util/types.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static std::string u16_ne_to_string(const char16_t* bytes, std::size_t count) +{ + std::wstring_convert, char16_t> convert; + return convert.to_bytes(bytes, bytes + count); +} + +static std::string u16_se_to_string(const char16_t* bytes, std::size_t count) +{ + auto seBytes = reinterpret_cast*>(bytes); + + std::vector neBytes(count); + for (std::size_t i = 0; i < count; ++i) + { + neBytes[i] = seBytes[i]; + } + + return u16_ne_to_string(neBytes.data(), neBytes.size()); +} + +static std::string u16_be_to_string(const char16_t* bytes, std::size_t count) +{ + if constexpr (std::endian::native == std::endian::big) + { + return u16_ne_to_string(bytes, count); + } + else + { + return u16_se_to_string(bytes, count); + } +} + +static std::string decodeString(std::string_view data, + iso::StringEncoding encoding) +{ + switch (encoding) + { + case iso::StringEncoding::ascii: + break; + + case iso::StringEncoding::utf16_be: + return u16_be_to_string(reinterpret_cast(data.data()), data.size() / 2); + } + + return std::string(data); +} + +bool iso_dev::initialize() +{ + constexpr std::size_t primaryVolumeDescOffset = 16; + ensure(m_dev->block_size() >= sizeof(iso::VolumeHeader)); + std::vector block(m_dev->block_size()); + + std::optional primaryVolume; + std::optional supplementaryVolume; + + for (std::size_t i = 0; i < 256; ++i) + { + if (m_dev->read(primaryVolumeDescOffset + i, block.data(), 1) != 1) + { + break; + } + + auto header = reinterpret_cast(block.data()); + + if (header->type == 255) + { + break; + } + + if (std::memcmp(header->standard_id, "CD001", 5) != 0) + { + continue; + } + + if (header->type == 1) + { + primaryVolume = + *reinterpret_cast(block.data()); + continue; + } + + if (header->type == 2) + { + std::printf("found supplementary volume\n"); + supplementaryVolume = + *reinterpret_cast(block.data()); + continue; + } + } + + if (!primaryVolume) + { + return false; + } + + auto& pvd = supplementaryVolume ? *supplementaryVolume : *primaryVolume; + m_encoding = supplementaryVolume ? iso::StringEncoding::utf16_be : iso::StringEncoding::ascii; + m_root_dir = pvd.root; + + return true; +} + +bool iso_dev::stat(const std::string& path, fs::stat_t& info) +{ + auto optEntry = open_entry(path); + if (!optEntry) + { + fs::g_tls_error = fs::error::noent; + return false; + } + + info = optEntry->to_fs_stat(); + return true; +} + +bool iso_dev::statfs(const std::string& path, fs::device_stat& info) +{ + auto optEntry = open_entry(path); + if (!optEntry) + { + fs::g_tls_error = fs::error::noent; + return false; + } + + info = { + .block_size = m_dev->block_size(), + .total_size = m_dev->size(), + .total_free = 0, + .avail_free = 0, + }; + + return true; +} + +std::unique_ptr iso_dev::open(const std::string& path, bs_t mode) +{ + if (mode != fs::open_mode::read) + { + fs::g_tls_error = fs::error::acces; + return {}; + } + + auto optEntry = open_entry(path); + if (!optEntry) + { + fs::g_tls_error = fs::error::noent; + return {}; + } + + if ((optEntry->flags & iso::DirEntryFlags::Directory) == iso::DirEntryFlags::Directory) + { + fs::g_tls_error = fs::error::isdir; + return {}; + } + + return read_file(*optEntry).release(); +} + +std::unique_ptr iso_dev::open_dir(const std::string& path) +{ + auto optEntry = open_entry(path); + if (!optEntry) + { + fs::g_tls_error = fs::error::noent; + return {}; + } + + if ((optEntry->flags & iso::DirEntryFlags::Directory) != iso::DirEntryFlags::Directory) + { + fs::g_tls_error = fs::error::exist; + return {}; + } + + auto items = read_dir(*optEntry); + std::vector result_items(items.first.size()); + + for (std::size_t i = 0; i < result_items.size(); ++i) + { + result_items[i] = items.first[i].to_fs_entry(items.second[i]); + } + + return std::make_unique(std::move(result_items)); +} + +std::optional +iso_dev::open_entry(const std::filesystem::path& path) +{ + auto pathString = std::filesystem::weakly_canonical(path).string(); + + if (pathString == "/" || pathString == "\\" || pathString.empty()) + { + return m_root_dir; + } + + auto item = m_root_dir; + auto pathView = std::string_view(pathString); + + auto isStringEqNoCase = [](std::string_view lhs, std::string_view rhs) + { + if (lhs.size() != rhs.size()) + { + return false; + } + + for (std::size_t i = 0; i < lhs.size(); ++i) + { + if (std::tolower(lhs[i]) != std::tolower(rhs[i])) + { + return false; + } + } + + return true; + }; + + while (!pathView.empty()) + { + auto sepPos = pathView.find_first_of("/\\"); + if (sepPos == 0) + { + pathView.remove_prefix(1); + continue; + } + + if ((item.flags & iso::DirEntryFlags::Directory) != + iso::DirEntryFlags::Directory) + { + return {}; + } + + auto dirName = pathView.substr(0, sepPos); + if (sepPos == std::string_view::npos) + { + pathView = {}; + } + else + { + pathView.remove_prefix(sepPos); + } + + auto items = read_dir(item); + + bool found = false; + for (std::size_t i = 0; i < items.first.size(); ++i) + { + if (!isStringEqNoCase(items.second[i], dirName)) + { + continue; + } + + item = items.first[i]; + found = true; + break; + } + + if (!found) + { + return {}; + } + } + + return item; +} + +std::pair, std::vector> +iso_dev::read_dir(const iso::DirEntry& entry) +{ + if ((entry.flags & iso::DirEntryFlags::Directory) != + iso::DirEntryFlags::Directory) + { + return {}; + } + + auto block_size = m_dev->block_size(); + auto total_block_count = (entry.length.value() + block_size - 1) / block_size; + auto total_buffer_block_count = std::min(total_block_count, 10); + + std::vector buffer(total_buffer_block_count * block_size); + auto first_block = entry.lba.value(); + + std::vector isoEntries; + std::vector names; + + for (std::size_t block = first_block, end = first_block + total_block_count; + block < end;) + { + auto block_count = + m_dev->read(block, buffer.data(), total_buffer_block_count); + block += block_count; + + std::size_t buffer_offset = 0; + std::size_t buffer_size = block_count * block_size; + + while (buffer_offset < buffer_size) + { + auto item = reinterpret_cast(buffer.data() + + buffer_offset); + if (item->entry_length == 0) + { + buffer_offset += block_size; + buffer_offset &= ~(block_size - 1); + continue; + } + + auto filename_end = + sizeof(iso::DirEntry) + + ((item->filename_length + 1) & ~static_cast(1)); + + if (item->entry_length < filename_end) + { + buffer_offset += item->entry_length; + continue; + } + + buffer_offset += item->entry_length; + + if (item->filename_length == 0 || + item->filename_length + sizeof(iso::DirEntry) > item->entry_length) + { + continue; + } + + auto filename = + item->filename_length > 1 ? decodeString({reinterpret_cast(item + 1), + item->filename_length}, + m_encoding) : + std::string{}; + + if (item->filename_length == 1) + { + char c = *reinterpret_cast(item + 1); + // can be special name + if (c == 0) + { + filename = "."; + } + else if (c == 1) + { + filename = ".."; + } + } + + filename = filename.substr(0, filename.find(';')); + if (filename.empty()) + { + continue; + } + + isoEntries.push_back(*item); + names.emplace_back(filename); + } + } + + return {std::move(isoEntries), std::move(names)}; +} + +fs::file iso_dev::read_file(const iso::DirEntry& entry) +{ + if ((entry.flags & iso::DirEntryFlags::Directory) == + iso::DirEntryFlags::Directory) + { + return {}; + } + + if (entry.length.value() == 0) + { + return fs::make_stream>(); + } + + auto block_size = m_dev->block_size(); + auto block_count = (entry.length.value() + block_size - 1) / block_size; + + std::vector data; + data.resize(block_count * block_size); + if (!m_dev->read(entry.lba.value(), data.data(), block_count)) + { + return {}; + } + + data.resize(entry.length.value()); + return fs::make_stream(std::move(data), entry.to_fs_stat()); +} diff --git a/rpcs3/rpcs3/dev/iso.hpp b/rpcs3/rpcs3/dev/iso.hpp new file mode 100644 index 000000000..85c70cc9c --- /dev/null +++ b/rpcs3/rpcs3/dev/iso.hpp @@ -0,0 +1,233 @@ +#pragma once + +#include "Utilities/File.h" +#include "block_dev.hpp" +#include "util/endian.hpp" +#include "util/types.hpp" +#include +#include +#include + +namespace iso +{ +#pragma pack(push, 1) + template + struct le_be_pair + { + le_t le; + be_t be; + + T value() const + { + if constexpr (std::endian::native == std::endian::little) + { + return le; + } + else + { + return be; + } + } + }; + + struct PrimaryVolumeDescriptorDateTime + { + char year[4]; + char month[2]; + char day[2]; + char hour[2]; + char minute[2]; + char second[2]; + char milliseconds[2]; + s8 gmt_offset; + }; + + struct VolumeHeader + { + u8 type; + char standard_id[5]; + u8 version; + }; + + struct DirDateTime + { + u8 year; // + 1900 + u8 month; + u8 day; + u8 hour; + u8 minute; + u8 second; + s8 gmt_offset; + + struct tm to_tm() const + { + struct tm time{}; + time.tm_year = year; + time.tm_mon = month; + time.tm_mday = day; + time.tm_hour = hour; + time.tm_min = minute; + time.tm_sec = second; + time.tm_gmtoff = gmt_offset; + return time; + } + + time_t to_time_t() const + { + auto tm = to_tm(); + return mktime(&tm); + } + }; + + enum class DirEntryFlags : u8 + { + None = 0, + Hidden = 1 << 0, + Directory = 1 << 1, + File = 1 << 2, + ExtAttr = 1 << 3, + Permissions = 1 << 4, + }; + + constexpr DirEntryFlags operator&(DirEntryFlags lhs, DirEntryFlags rhs) + { + return static_cast(static_cast(lhs) & + static_cast(rhs)); + } + constexpr DirEntryFlags operator|(DirEntryFlags lhs, DirEntryFlags rhs) + { + return static_cast(static_cast(lhs) | + static_cast(rhs)); + } + + struct DirEntry + { + u8 entry_length; + u8 ext_attr_length; + le_be_pair lba; + le_be_pair length; + DirDateTime create_time; + DirEntryFlags flags; + u8 interleave_unit_size; + u8 interleave_gap_size; + le_be_pair sequence; + u8 filename_length; + + fs::stat_t to_fs_stat() const + { + fs::stat_t result{}; + result.is_directory = + (flags & iso::DirEntryFlags::Directory) != iso::DirEntryFlags::None; + result.size = length.value(); + result.ctime = create_time.to_time_t(); + result.mtime = result.ctime; + result.atime = result.ctime; + return result; + } + fs::dir_entry to_fs_entry(std::string name) const + { + fs::dir_entry entry = {}; + static_cast(entry) = to_fs_stat(); + entry.name = std::move(name); + return entry; + } + }; + + struct PrimaryVolumeDescriptor + { + VolumeHeader header; + uint8_t pad0; + char system_id[32]; + char volume_id[32]; + char pad1[8]; + le_be_pair block_count; + char pad2[32]; + le_be_pair volume_set_size; + le_be_pair vol_seq_num; + le_be_pair block_size; + le_be_pair path_table_size; + le_t path_table_block_le; + le_t ext_path_table_block_le; + be_t path_table_block_be; + be_t ext_path_table_block_be; + DirEntry root; + u8 pad3; + uint8_t volume_set_id[128]; + uint8_t publisher_id[128]; + uint8_t data_preparer_id[128]; + uint8_t application_id[128]; + uint8_t copyright_file_id[37]; + uint8_t abstract_file_id[37]; + uint8_t bibliographical_file_id[37]; + PrimaryVolumeDescriptorDateTime vol_creation_time; + PrimaryVolumeDescriptorDateTime vol_modification_time; + PrimaryVolumeDescriptorDateTime vol_expire_time; + PrimaryVolumeDescriptorDateTime vol_effective_time; + uint8_t version; + uint8_t pad4; + uint8_t app_used[512]; + + u32 path_table_block() const + { + if constexpr (std::endian::native == std::endian::little) + { + return path_table_block_le; + } + else + { + return path_table_block_be; + } + } + }; + + struct PathTableEntryHeader + { + u8 name_length; + u8 ext_attr_length; + le_t location; + le_t parent_id; + }; + + enum class StringEncoding + { + ascii, + utf16_be, + }; + +#pragma pack(pop) +} // namespace iso + +class iso_dev final : public fs::device_base +{ + std::unique_ptr m_dev; + iso::DirEntry m_root_dir; + iso::StringEncoding m_encoding = iso::StringEncoding::ascii; + +public: + iso_dev() = default; + + static std::optional open(std::unique_ptr device) + { + iso_dev result; + result.m_dev = std::move(device); + + if (!result.initialize()) + { + return {}; + } + + return result; + } + + bool stat(const std::string& path, fs::stat_t& info) override; + bool statfs(const std::string& path, fs::device_stat& info) override; + std::unique_ptr open(const std::string& path, bs_t mode) override; + std::unique_ptr open_dir(const std::string& path) override; + +private: + bool initialize(); + + std::optional open_entry(const std::filesystem::path& path); + std::pair, std::vector> read_dir(const iso::DirEntry& entry); + fs::file read_file(const iso::DirEntry& entry); +};