ps3: add iso device

This commit is contained in:
DH 2025-04-04 17:19:32 +03:00
parent 06dcf32e1b
commit b36d65ed30
4 changed files with 717 additions and 0 deletions

View file

@ -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

View file

@ -0,0 +1,81 @@
#pragma once
#include "Utilities/File.h"
#include <cstddef>
#include <utility>
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();
}
};

397
rpcs3/rpcs3/dev/iso.cpp Normal file
View file

@ -0,0 +1,397 @@
#include "iso.hpp"
#include "Utilities/File.h"
#include "util/types.hpp"
#include <bit>
#include <cctype>
#include <codecvt>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <ctime>
#include <filesystem>
#include <memory>
#include <string>
static std::string u16_ne_to_string(const char16_t* bytes, std::size_t count)
{
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, 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<const se_t<char16_t, true, alignof(char16_t)>*>(bytes);
std::vector<char16_t> 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<const char16_t*>(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<std::byte> block(m_dev->block_size());
std::optional<iso::PrimaryVolumeDescriptor> primaryVolume;
std::optional<iso::PrimaryVolumeDescriptor> 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<iso::VolumeHeader*>(block.data());
if (header->type == 255)
{
break;
}
if (std::memcmp(header->standard_id, "CD001", 5) != 0)
{
continue;
}
if (header->type == 1)
{
primaryVolume =
*reinterpret_cast<iso::PrimaryVolumeDescriptor*>(block.data());
continue;
}
if (header->type == 2)
{
std::printf("found supplementary volume\n");
supplementaryVolume =
*reinterpret_cast<iso::PrimaryVolumeDescriptor*>(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<fs::file_base> iso_dev::open(const std::string& path, bs_t<fs::open_mode> 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<fs::dir_base> 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<fs::dir_entry> 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<fs::virtual_dir>(std::move(result_items));
}
std::optional<iso::DirEntry>
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::DirEntry>, std::vector<std::string>>
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<std::size_t>(total_block_count, 10);
std::vector<std::byte> buffer(total_buffer_block_count * block_size);
auto first_block = entry.lba.value();
std::vector<iso::DirEntry> isoEntries;
std::vector<std::string> 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<const iso::DirEntry*>(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<std::size_t>(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<const char*>(item + 1),
item->filename_length},
m_encoding) :
std::string{};
if (item->filename_length == 1)
{
char c = *reinterpret_cast<const char*>(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<std::vector<std::uint8_t>>();
}
auto block_size = m_dev->block_size();
auto block_count = (entry.length.value() + block_size - 1) / block_size;
std::vector<std::uint8_t> 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());
}

233
rpcs3/rpcs3/dev/iso.hpp Normal file
View file

@ -0,0 +1,233 @@
#pragma once
#include "Utilities/File.h"
#include "block_dev.hpp"
#include "util/endian.hpp"
#include "util/types.hpp"
#include <bit>
#include <optional>
#include <filesystem>
namespace iso
{
#pragma pack(push, 1)
template <typename T>
struct le_be_pair
{
le_t<T> le;
be_t<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<DirEntryFlags>(static_cast<unsigned>(lhs) &
static_cast<unsigned>(rhs));
}
constexpr DirEntryFlags operator|(DirEntryFlags lhs, DirEntryFlags rhs)
{
return static_cast<DirEntryFlags>(static_cast<unsigned>(lhs) |
static_cast<unsigned>(rhs));
}
struct DirEntry
{
u8 entry_length;
u8 ext_attr_length;
le_be_pair<u32> lba;
le_be_pair<u32> length;
DirDateTime create_time;
DirEntryFlags flags;
u8 interleave_unit_size;
u8 interleave_gap_size;
le_be_pair<u16> 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<fs::stat_t&>(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<u32> block_count;
char pad2[32];
le_be_pair<u16> volume_set_size;
le_be_pair<u16> vol_seq_num;
le_be_pair<u16> block_size;
le_be_pair<u32> path_table_size;
le_t<u32> path_table_block_le;
le_t<u32> ext_path_table_block_le;
be_t<u32> path_table_block_be;
be_t<u32> 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<u32> location;
le_t<u16> parent_id;
};
enum class StringEncoding
{
ascii,
utf16_be,
};
#pragma pack(pop)
} // namespace iso
class iso_dev final : public fs::device_base
{
std::unique_ptr<block_dev> m_dev;
iso::DirEntry m_root_dir;
iso::StringEncoding m_encoding = iso::StringEncoding::ascii;
public:
iso_dev() = default;
static std::optional<iso_dev> open(std::unique_ptr<block_dev> 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<fs::file_base> open(const std::string& path, bs_t<fs::open_mode> mode) override;
std::unique_ptr<fs::dir_base> open_dir(const std::string& path) override;
private:
bool initialize();
std::optional<iso::DirEntry> open_entry(const std::filesystem::path& path);
std::pair<std::vector<iso::DirEntry>, std::vector<std::string>> read_dir(const iso::DirEntry& entry);
fs::file read_file(const iso::DirEntry& entry);
};