Support reading data from read-only host locations

This commit is contained in:
Tony Wasserka 2024-03-23 17:05:55 +01:00
parent fb67cbc947
commit b262167f58
9 changed files with 50 additions and 34 deletions

View file

@ -287,7 +287,7 @@ if (bootstrap_nand) // Experimental system bootstrapper
} }
auto file_context = HLE::PXI::FS::FileContext { *frontend_logger }; auto file_context = HLE::PXI::FS::FileContext { *frontend_logger };
auto [result] = (*update_partition)->Open(file_context, false); auto [result] = (*update_partition)->OpenReadOnly(file_context);
if (result != HLE::OS::RESULT_OK) { if (result != HLE::OS::RESULT_OK) {
throw std::runtime_error("Failed to open update partition"); throw std::runtime_error("Failed to open update partition");
} }
@ -298,7 +298,7 @@ if (bootstrap_nand) // Experimental system bootstrapper
FileFormat::RomFSLevel3Header level3_header; FileFormat::RomFSLevel3Header level3_header;
const uint32_t lv3_offset = 0x1000; const uint32_t lv3_offset = 0x1000;
std::tie(result) = romfs->Open(file_context, false); std::tie(result) = romfs->OpenReadOnly(file_context);
uint32_t bytes_read; uint32_t bytes_read;
std::tie(result, bytes_read) = romfs->Read(file_context, lv3_offset, sizeof(level3_header), HLE::PXI::FS::FileBufferInHostMemory(level3_header)); std::tie(result, bytes_read) = romfs->Read(file_context, lv3_offset, sizeof(level3_header), HLE::PXI::FS::FileBufferInHostMemory(level3_header));
if (result != HLE::OS::RESULT_OK) { if (result != HLE::OS::RESULT_OK) {

View file

@ -49,7 +49,7 @@ static bool IsLoadableCXIFile(HLE::PXI::FS::File& file) {
// TODO: Enable proper logging // TODO: Enable proper logging
auto logger = std::make_shared<spdlog::logger>("dummy", std::make_shared<spdlog::sinks::null_sink_st>()); auto logger = std::make_shared<spdlog::logger>("dummy", std::make_shared<spdlog::sinks::null_sink_st>());
auto file_context = HLE::PXI::FS::FileContext { *logger }; auto file_context = HLE::PXI::FS::FileContext { *logger };
file.Open(file_context, false); file.OpenReadOnly(file_context);
FileFormat::NCCHHeader header; FileFormat::NCCHHeader header;
auto [result, bytes_read] = file.Read(file_context, 0, sizeof(header), HLE::PXI::FS::FileBufferInHostMemory(header)); auto [result, bytes_read] = file.Read(file_context, 0, sizeof(header), HLE::PXI::FS::FileBufferInHostMemory(header));
@ -80,7 +80,7 @@ std::optional<std::unique_ptr<HLE::PXI::FS::File>> GameCardFromCCI::GetPartition
auto file_context = HLE::PXI::FS::FileContext { *logger }; auto file_context = HLE::PXI::FS::FileContext { *logger };
auto cci_file = std::visit(GameCardSourceOpener{}, source); auto cci_file = std::visit(GameCardSourceOpener{}, source);
cci_file->Open(file_context, false); cci_file->OpenReadOnly(file_context);
FileFormat::NCSDHeader ncsd; FileFormat::NCSDHeader ncsd;
cci_file->Read(file_context, 0, decltype(ncsd)::Tags::expected_serialized_size, HLE::PXI::FS::FileBufferInHostMemory(ncsd)); cci_file->Read(file_context, 0, decltype(ncsd)::Tags::expected_serialized_size, HLE::PXI::FS::FileBufferInHostMemory(ncsd));
cci_file->Close(); cci_file->Close();
@ -94,7 +94,7 @@ static bool IsLoadableCCIFile(HLE::PXI::FS::File& file) {
// TODO: Enable proper logging // TODO: Enable proper logging
auto logger = std::make_shared<spdlog::logger>("dummy", std::make_shared<spdlog::sinks::null_sink_st>()); auto logger = std::make_shared<spdlog::logger>("dummy", std::make_shared<spdlog::sinks::null_sink_st>());
auto file_context = HLE::PXI::FS::FileContext { *logger }; auto file_context = HLE::PXI::FS::FileContext { *logger };
file.Open(file_context, false); file.OpenReadOnly(file_context);
FileFormat::NCSDHeader header; FileFormat::NCSDHeader header;
auto [result, bytes_read] = file.Read(file_context, 0, sizeof(header), HLE::PXI::FS::FileBufferInHostMemory(header)); auto [result, bytes_read] = file.Read(file_context, 0, sizeof(header), HLE::PXI::FS::FileBufferInHostMemory(header));
@ -244,12 +244,12 @@ public:
exheader.aci_limits.flags = exheader.aci_limits.flags.ideal_processor()(1).priority()(0x18); exheader.aci_limits.flags = exheader.aci_limits.flags.ideal_processor()(1).priority()(0x18);
} }
HLE::OS::OS::ResultAnd<> Open(HLE::PXI::FS::FileContext& context, bool create_or_truncate) override { HLE::OS::OS::ResultAnd<> Open(HLE::PXI::FS::FileContext& context, HLE::PXI::FS::OpenFlags flags) override {
if (create_or_truncate) { if (flags.create) {
throw std::runtime_error("Can't create/truncate game cards"); throw std::runtime_error("Can't create/truncate game cards");
} }
auto ret = file->Open(context, create_or_truncate); auto ret = file->Open(context, flags);
if (ret != std::tie(HLE::OS::RESULT_OK)) { if (ret != std::tie(HLE::OS::RESULT_OK)) {
return ret; return ret;
} }
@ -554,7 +554,7 @@ static bool IsLoadable3DSXFile(HLE::PXI::FS::File& file) {
// TODO: Enable proper logging // TODO: Enable proper logging
auto logger = std::make_shared<spdlog::logger>("dummy", std::make_shared<spdlog::sinks::null_sink_st>()); auto logger = std::make_shared<spdlog::logger>("dummy", std::make_shared<spdlog::sinks::null_sink_st>());
auto file_context = HLE::PXI::FS::FileContext { *logger }; auto file_context = HLE::PXI::FS::FileContext { *logger };
file.Open(file_context, false); file.OpenReadOnly(file_context);
FileFormat::Dot3DSX::Header header; FileFormat::Dot3DSX::Header header;
auto [result, bytes_read] = file.Read(file_context, 0, sizeof(header), HLE::PXI::FS::FileBufferInHostMemory(header)); auto [result, bytes_read] = file.Read(file_context, 0, sizeof(header), HLE::PXI::FS::FileBufferInHostMemory(header));

View file

@ -13,7 +13,7 @@ public:
NativeFile(int file_descriptor) : source(file_descriptor, boost::iostreams::never_close_handle) { NativeFile(int file_descriptor) : source(file_descriptor, boost::iostreams::never_close_handle) {
} }
HLE::OS::ResultAnd<> Open(HLE::PXI::FS::FileContext&, bool create) override { HLE::OS::ResultAnd<> Open(HLE::PXI::FS::FileContext&, HLE::PXI::FS::OpenFlags) override {
// Nothing to do // Nothing to do
return std::make_tuple(HLE::OS::RESULT_OK); return std::make_tuple(HLE::OS::RESULT_OK);
} }

View file

@ -996,7 +996,7 @@ public:
HLE::PXI::FS::FileContext file_context { *GetLogger() }; HLE::PXI::FS::FileContext file_context { *GetLogger() };
std::vector<uint8_t> firm_data; std::vector<uint8_t> firm_data;
{ {
firm_file->Open(file_context, false); firm_file->OpenReadOnly(file_context);
auto [result, num_bytes] = firm_file->GetSize(file_context); auto [result, num_bytes] = firm_file->GetSize(file_context);
if (result != RESULT_OK) { if (result != RESULT_OK) {
throw std::runtime_error("Could not determine file size"); throw std::runtime_error("Could not determine file size");

View file

@ -147,7 +147,7 @@ HandleTable::Entry<Process> LoadProcessFromFile(FakeThread& source,
auto input_file = PXI::FS::NCCHOpenExeFSSection(*source.GetLogger(), file_context, auto input_file = PXI::FS::NCCHOpenExeFSSection(*source.GetLogger(), file_context,
source.GetParentProcess().interpreter_setup.keydb, source.GetParentProcess().interpreter_setup.keydb,
std::move(file), 1, std::basic_string_view<uint8_t>(code, sizeof(code))); std::move(file), 1, std::basic_string_view<uint8_t>(code, sizeof(code)));
if (std::get<0>(input_file->Open(file_context, false)) != RESULT_OK) { if (std::get<0>(input_file->OpenReadOnly(file_context)) != RESULT_OK) {
// TODO: Better error message // TODO: Better error message
throw std::runtime_error("Could not launch title from emulated NAND."); throw std::runtime_error("Could not launch title from emulated NAND.");
} }
@ -394,7 +394,7 @@ OS::ResultAnd<ProcessId> LaunchTitleInternal(FakeThread& source, bool from_firm,
uint8_t code[8] = { '.', 'c', 'o', 'd', 'e' }; uint8_t code[8] = { '.', 'c', 'o', 'd', 'e' };
auto input_file = PXI::FS::OpenNCCHSubFile(source, info, 0, 1, std::basic_string_view<uint8_t>(code, sizeof(code)), source.GetOS().setup.gamecard.get()); auto input_file = PXI::FS::OpenNCCHSubFile(source, info, 0, 1, std::basic_string_view<uint8_t>(code, sizeof(code)), source.GetOS().setup.gamecard.get());
HLE::PXI::FS::FileContext file_context { *source.GetLogger() }; HLE::PXI::FS::FileContext file_context { *source.GetLogger() };
if (std::get<0>(input_file->Open(file_context, false)) != RESULT_OK) { if (std::get<0>(input_file->OpenReadOnly(file_context)) != RESULT_OK) {
throw std::runtime_error(fmt::format( "Tried to launch non-existing title {:#x} from emulated NAND.\n\nPlease dump the title from your 3DS and install it manually to this path:\n{}", throw std::runtime_error(fmt::format( "Tried to launch non-existing title {:#x} from emulated NAND.\n\nPlease dump the title from your 3DS and install it manually to this path:\n{}",
title_id, "filename" /* TODO */)); title_id, "filename" /* TODO */));
} }

View file

@ -190,7 +190,7 @@ FakePXI::FakePXI(FakeThread& thread)
namespace PM = Platform::PXI::PM; namespace PM = Platform::PXI::PM;
FileFormat::ExHeader GetExtendedHeader(FS::FileContext& file_context, const KeyDatabase& keydb, HLE::PXI::FS::File& ncch_file) { FileFormat::ExHeader GetExtendedHeader(FS::FileContext& file_context, const KeyDatabase& keydb, HLE::PXI::FS::File& ncch_file) {
ncch_file.Open(file_context, false); ncch_file.OpenReadOnly(file_context);
std::array<uint8_t, FileFormat::NCCHHeader::Tags::expected_serialized_size> ncch_raw; std::array<uint8_t, FileFormat::NCCHHeader::Tags::expected_serialized_size> ncch_raw;
auto [result, bytes_read] = ncch_file.Read(file_context, 0, sizeof(ncch_raw), FS::FileBufferInHostMemory(ncch_raw.data(), sizeof(ncch_raw))); auto [result, bytes_read] = ncch_file.Read(file_context, 0, sizeof(ncch_raw), FS::FileBufferInHostMemory(ncch_raw.data(), sizeof(ncch_raw)));

View file

@ -229,8 +229,8 @@ void File::Fail() {
throw std::runtime_error("Unspecified error in PXI file operation"); throw std::runtime_error("Unspecified error in PXI file operation");
} }
OS::ResultAnd<> File::Open(FileContext& context, bool create_or_truncate) { OS::ResultAnd<> File::Open(FileContext& context, OpenFlags flags) {
return Open(context, create_or_truncate); return Open(context, flags);
} }
OS::ResultAnd<uint32_t> File::Read(FileContext&, uint64_t offset, uint32_t num_bytes, FileBuffer&& dest) { OS::ResultAnd<uint32_t> File::Read(FileContext&, uint64_t offset, uint32_t num_bytes, FileBuffer&& dest) {
@ -331,8 +331,8 @@ public:
: file(std::move(file)), aes_offset(aes_offset), key(key), iv(iv) { : file(std::move(file)), aes_offset(aes_offset), key(key), iv(iv) {
} }
OS::ResultAnd<> Open(FileContext& context, bool create) override { OS::ResultAnd<> Open(FileContext& context, OpenFlags flags) override {
return file->Open(context, create); return file->Open(context, flags);
} }
OS::ResultAnd<uint64_t> GetSize(FileContext& context) override { OS::ResultAnd<uint64_t> GetSize(FileContext& context) override {
@ -357,7 +357,7 @@ class DummyExeFSFile final : public File {
public: public:
DummyExeFSFile() {} DummyExeFSFile() {}
OS::ResultAnd<> Open(FileContext&, bool) override { OS::ResultAnd<> Open(FileContext&, OpenFlags) override {
return { RESULT_OK }; return { RESULT_OK };
} }
@ -557,7 +557,7 @@ std::unique_ptr<File> OpenNCCHSubFile(Thread& thread, Platform::FS::ProgramInfo
} }
FileContext file_context { *thread.GetLogger() }; FileContext file_context { *thread.GetLogger() };
auto result = ncch->Open(file_context, false); auto result = ncch->OpenReadOnly(file_context);
if (std::get<0>(result) != RESULT_OK) { if (std::get<0>(result) != RESULT_OK) {
throw std::runtime_error(fmt::format( "Tried to access non-existing title {:#x} from emulated NAND.\n\nPlease dump the title from your 3DS and install it manually to this path:\n{}", throw std::runtime_error(fmt::format( "Tried to access non-existing title {:#x} from emulated NAND.\n\nPlease dump the title from your 3DS and install it manually to this path:\n{}",
program_info.program_id, (char*)file_path.data())); program_info.program_id, (char*)file_path.data()));
@ -685,7 +685,7 @@ static std::tuple<Result, uint64_t> OpenFile(FakeThread& thread, Context& contex
// TODO: Respect flags & ~0x4 ... in particular, write/read-only! // TODO: Respect flags & ~0x4 ... in particular, write/read-only!
if (file) {// TODO: Remove this check! We only do this so that we don't need to implement archive 0x56789a0b0 properly... if (file) {// TODO: Remove this check! We only do this so that we don't need to implement archive 0x56789a0b0 properly...
FileContext file_context { *thread.GetLogger() }; FileContext file_context { *thread.GetLogger() };
std::tie(result) = file->Open(file_context, flags & 0x4); std::tie(result) = file->Open(file_context, { .create = flags & 0x4 });
} }
if (result != RESULT_OK) { if (result != RESULT_OK) {
thread.GetLogger()->warn("Failed to open file"); thread.GetLogger()->warn("Failed to open file");

View file

@ -75,6 +75,12 @@ public:
void Write(char* source, uint32_t num_bytes) override; void Write(char* source, uint32_t num_bytes) override;
}; };
struct OpenFlags {
bool read = true;
bool write = true;
bool create = false; // create or truncate
};
class File { class File {
[[noreturn]] void Fail(); [[noreturn]] void Fail();
@ -86,9 +92,13 @@ public:
/** /**
* Open the given file on the disk. * Open the given file on the disk.
* Returns an error if the file does not exist unless the "create" flag is set. * Returns an error if the file does not exist unless the "create" flag is set.
* If the file does already exist and the "create_or_truncate" flag is set, the file contents are discarded. * If the file does already exist and the "create" flag is set, the file contents are discarded.
*/ */
virtual OS::ResultAnd<> Open(FileContext&, bool create_or_truncate); virtual OS::ResultAnd<> Open(FileContext&, OpenFlags);
OS::ResultAnd<> OpenReadOnly(FileContext& context) {
return Open(context, { .write = false });
}
/** /**
* @return Result code and number of bytes read (0 on error) * @return Result code and number of bytes read (0 on error)
@ -131,7 +141,7 @@ private:
public: public:
HostFile(std::string_view path, Policy policy); HostFile(std::string_view path, Policy policy);
OS::ResultAnd<> Open(FileContext&, bool create) override; OS::ResultAnd<> Open(FileContext&, OpenFlags create) override;
OS::ResultAnd<> SetSize(OS::FakeThread& thread, uint64_t size) override; OS::ResultAnd<> SetSize(OS::FakeThread& thread, uint64_t size) override;
OS::ResultAnd<uint64_t> GetSize(FileContext&) override; OS::ResultAnd<uint64_t> GetSize(FileContext&) override;
@ -154,7 +164,7 @@ class FileView final : public File {
public: public:
FileView(std::unique_ptr<File> file, uint64_t offset, uint32_t num_bytes, boost::optional<std::array<uint8_t, 0x20>> precomputed_hash = boost::none); FileView(std::unique_ptr<File> file, uint64_t offset, uint32_t num_bytes, boost::optional<std::array<uint8_t, 0x20>> precomputed_hash = boost::none);
OS::ResultAnd<> Open(FileContext&, bool create) override; OS::ResultAnd<> Open(FileContext&, OpenFlags) override;
// SetSize is not allowed on views // SetSize is not allowed on views
// OS::ResultAnd<> SetSize(OS::FakeThread& thread, uint64_t size) override; // OS::ResultAnd<> SetSize(OS::FakeThread& thread, uint64_t size) override;

View file

@ -22,18 +22,24 @@ HostFile::HostFile(std::string_view path, Policy policy) : path(std::begin(path)
stream.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit); stream.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
} }
ResultAnd<> HostFile::Open(FileContext& context, bool create_or_truncate) { ResultAnd<> HostFile::Open(FileContext& context, OpenFlags flags) {
context.logger.info("Attempting to open {} (create={})", path, create_or_truncate); context.logger.info("Attempting to open {} (create={})", path, flags.create);
if (!create_or_truncate && !boost::filesystem::exists(path)) { if (!flags.create && !boost::filesystem::exists(path)) {
return std::make_tuple(-1); return std::make_tuple(-1);
} }
// TODO: Add support for read-only/write-only files
// NOTE: When we only specify read-mode, this would fail to create a file. Test what happens on the actual system in such a case! // NOTE: When we only specify read-mode, this would fail to create a file. Test what happens on the actual system in such a case!
auto stream_flags = std::ios_base::in | std::ios_base::out | std::ios_base::binary; auto stream_flags = std::ios_base::binary;
if (create_or_truncate) if (flags.read) {
stream_flags |= std::ios_base::in;
}
if (flags.write) {
stream_flags |= std::ios_base::out;
}
if (flags.create) {
stream_flags |= std::ios_base::trunc; stream_flags |= std::ios_base::trunc;
}
try { try {
// TODO: We should make sure files are never opened twice instead (currently, this happens for gamecard though). What if, for instance, one were to call Open twice with different flags? The stream wouldn't get updated for this change! // TODO: We should make sure files are never opened twice instead (currently, this happens for gamecard though). What if, for instance, one were to call Open twice with different flags? The stream wouldn't get updated for this change!
@ -107,7 +113,7 @@ FileView::FileView(std::unique_ptr<File> file, uint64_t offset, uint32_t num_byt
// } // }
} }
OS::ResultAnd<> FileView::Open(FileContext& context, bool create) { OS::ResultAnd<> FileView::Open(FileContext& context, OpenFlags flags) {
context.logger.info("Opening FileView at offset={:#x} for {:#x} bytes of data", offset, num_bytes); context.logger.info("Opening FileView at offset={:#x} for {:#x} bytes of data", offset, num_bytes);
if (auto [result, size] = file->GetSize(context); result != OS::RESULT_OK || offset + num_bytes > size) { if (auto [result, size] = file->GetSize(context); result != OS::RESULT_OK || offset + num_bytes > size) {
@ -116,11 +122,11 @@ OS::ResultAnd<> FileView::Open(FileContext& context, bool create) {
} }
// The "create" flag doesn't have well-defined semantics for file views // The "create" flag doesn't have well-defined semantics for file views
if (create) { if (flags.create) {
throw std::runtime_error("Cannot set create flag on file views"); throw std::runtime_error("Cannot set create flag on file views");
} }
return file->Open(context, create); return file->Open(context, flags);
} }
OS::ResultAnd<uint64_t> FileView::GetSize(FileContext&) { OS::ResultAnd<uint64_t> FileView::GetSize(FileContext&) {