From b262167f5895bb476ecc5350deca77d1c20171e8 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sat, 23 Mar 2024 17:05:55 +0100 Subject: [PATCH] Support reading data from read-only host locations --- source/gui-sdl/main.cpp | 4 ++-- source/loader/gamecard.cpp | 14 +++++++------- source/loader/host_file.cpp | 2 +- source/os.cpp | 2 +- source/processes/ns.cpp | 4 ++-- source/processes/pxi.cpp | 2 +- source/processes/pxi_fs.cpp | 14 +++++++------- source/processes/pxi_fs.hpp | 18 ++++++++++++++---- source/processes/pxi_fs_host_file.cpp | 24 +++++++++++++++--------- 9 files changed, 50 insertions(+), 34 deletions(-) diff --git a/source/gui-sdl/main.cpp b/source/gui-sdl/main.cpp index 3abd087..a81f3b2 100644 --- a/source/gui-sdl/main.cpp +++ b/source/gui-sdl/main.cpp @@ -287,7 +287,7 @@ if (bootstrap_nand) // Experimental system bootstrapper } 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) { throw std::runtime_error("Failed to open update partition"); } @@ -298,7 +298,7 @@ if (bootstrap_nand) // Experimental system bootstrapper FileFormat::RomFSLevel3Header level3_header; 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; 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) { diff --git a/source/loader/gamecard.cpp b/source/loader/gamecard.cpp index e5d3b21..614e727 100644 --- a/source/loader/gamecard.cpp +++ b/source/loader/gamecard.cpp @@ -49,7 +49,7 @@ static bool IsLoadableCXIFile(HLE::PXI::FS::File& file) { // TODO: Enable proper logging auto logger = std::make_shared("dummy", std::make_shared()); auto file_context = HLE::PXI::FS::FileContext { *logger }; - file.Open(file_context, false); + file.OpenReadOnly(file_context); FileFormat::NCCHHeader header; auto [result, bytes_read] = file.Read(file_context, 0, sizeof(header), HLE::PXI::FS::FileBufferInHostMemory(header)); @@ -80,7 +80,7 @@ std::optional> GameCardFromCCI::GetPartition auto file_context = HLE::PXI::FS::FileContext { *logger }; auto cci_file = std::visit(GameCardSourceOpener{}, source); - cci_file->Open(file_context, false); + cci_file->OpenReadOnly(file_context); FileFormat::NCSDHeader ncsd; cci_file->Read(file_context, 0, decltype(ncsd)::Tags::expected_serialized_size, HLE::PXI::FS::FileBufferInHostMemory(ncsd)); cci_file->Close(); @@ -94,7 +94,7 @@ static bool IsLoadableCCIFile(HLE::PXI::FS::File& file) { // TODO: Enable proper logging auto logger = std::make_shared("dummy", std::make_shared()); auto file_context = HLE::PXI::FS::FileContext { *logger }; - file.Open(file_context, false); + file.OpenReadOnly(file_context); FileFormat::NCSDHeader 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); } - HLE::OS::OS::ResultAnd<> Open(HLE::PXI::FS::FileContext& context, bool create_or_truncate) override { - if (create_or_truncate) { + HLE::OS::OS::ResultAnd<> Open(HLE::PXI::FS::FileContext& context, HLE::PXI::FS::OpenFlags flags) override { + if (flags.create) { 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)) { return ret; } @@ -554,7 +554,7 @@ static bool IsLoadable3DSXFile(HLE::PXI::FS::File& file) { // TODO: Enable proper logging auto logger = std::make_shared("dummy", std::make_shared()); auto file_context = HLE::PXI::FS::FileContext { *logger }; - file.Open(file_context, false); + file.OpenReadOnly(file_context); FileFormat::Dot3DSX::Header header; auto [result, bytes_read] = file.Read(file_context, 0, sizeof(header), HLE::PXI::FS::FileBufferInHostMemory(header)); diff --git a/source/loader/host_file.cpp b/source/loader/host_file.cpp index be31f74..90b5f9b 100644 --- a/source/loader/host_file.cpp +++ b/source/loader/host_file.cpp @@ -13,7 +13,7 @@ public: 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 return std::make_tuple(HLE::OS::RESULT_OK); } diff --git a/source/os.cpp b/source/os.cpp index e62ae01..2b66285 100644 --- a/source/os.cpp +++ b/source/os.cpp @@ -996,7 +996,7 @@ public: HLE::PXI::FS::FileContext file_context { *GetLogger() }; std::vector firm_data; { - firm_file->Open(file_context, false); + firm_file->OpenReadOnly(file_context); auto [result, num_bytes] = firm_file->GetSize(file_context); if (result != RESULT_OK) { throw std::runtime_error("Could not determine file size"); diff --git a/source/processes/ns.cpp b/source/processes/ns.cpp index a816a4a..b9c6cda 100644 --- a/source/processes/ns.cpp +++ b/source/processes/ns.cpp @@ -147,7 +147,7 @@ HandleTable::Entry LoadProcessFromFile(FakeThread& source, auto input_file = PXI::FS::NCCHOpenExeFSSection(*source.GetLogger(), file_context, source.GetParentProcess().interpreter_setup.keydb, std::move(file), 1, std::basic_string_view(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 throw std::runtime_error("Could not launch title from emulated NAND."); } @@ -394,7 +394,7 @@ OS::ResultAnd LaunchTitleInternal(FakeThread& source, bool from_firm, uint8_t code[8] = { '.', 'c', 'o', 'd', 'e' }; auto input_file = PXI::FS::OpenNCCHSubFile(source, info, 0, 1, std::basic_string_view(code, sizeof(code)), source.GetOS().setup.gamecard.get()); 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{}", title_id, "filename" /* TODO */)); } diff --git a/source/processes/pxi.cpp b/source/processes/pxi.cpp index f21ae9b..3c06e4e 100644 --- a/source/processes/pxi.cpp +++ b/source/processes/pxi.cpp @@ -190,7 +190,7 @@ FakePXI::FakePXI(FakeThread& thread) namespace PM = Platform::PXI::PM; 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 ncch_raw; auto [result, bytes_read] = ncch_file.Read(file_context, 0, sizeof(ncch_raw), FS::FileBufferInHostMemory(ncch_raw.data(), sizeof(ncch_raw))); diff --git a/source/processes/pxi_fs.cpp b/source/processes/pxi_fs.cpp index a82f46e..907919c 100644 --- a/source/processes/pxi_fs.cpp +++ b/source/processes/pxi_fs.cpp @@ -229,8 +229,8 @@ void File::Fail() { throw std::runtime_error("Unspecified error in PXI file operation"); } -OS::ResultAnd<> File::Open(FileContext& context, bool create_or_truncate) { - return Open(context, create_or_truncate); +OS::ResultAnd<> File::Open(FileContext& context, OpenFlags flags) { + return Open(context, flags); } OS::ResultAnd 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) { } - OS::ResultAnd<> Open(FileContext& context, bool create) override { - return file->Open(context, create); + OS::ResultAnd<> Open(FileContext& context, OpenFlags flags) override { + return file->Open(context, flags); } OS::ResultAnd GetSize(FileContext& context) override { @@ -357,7 +357,7 @@ class DummyExeFSFile final : public File { public: DummyExeFSFile() {} - OS::ResultAnd<> Open(FileContext&, bool) override { + OS::ResultAnd<> Open(FileContext&, OpenFlags) override { return { RESULT_OK }; } @@ -557,7 +557,7 @@ std::unique_ptr OpenNCCHSubFile(Thread& thread, Platform::FS::ProgramInfo } 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) { 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())); @@ -685,7 +685,7 @@ static std::tuple OpenFile(FakeThread& thread, Context& contex // 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... 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) { thread.GetLogger()->warn("Failed to open file"); diff --git a/source/processes/pxi_fs.hpp b/source/processes/pxi_fs.hpp index e00c49d..b1be44a 100644 --- a/source/processes/pxi_fs.hpp +++ b/source/processes/pxi_fs.hpp @@ -75,6 +75,12 @@ public: 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 { [[noreturn]] void Fail(); @@ -86,9 +92,13 @@ public: /** * Open the given file on the disk. * 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) @@ -131,7 +141,7 @@ private: public: 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 GetSize(FileContext&) override; @@ -154,7 +164,7 @@ class FileView final : public File { public: FileView(std::unique_ptr file, uint64_t offset, uint32_t num_bytes, boost::optional> precomputed_hash = boost::none); - OS::ResultAnd<> Open(FileContext&, bool create) override; + OS::ResultAnd<> Open(FileContext&, OpenFlags) override; // SetSize is not allowed on views // OS::ResultAnd<> SetSize(OS::FakeThread& thread, uint64_t size) override; diff --git a/source/processes/pxi_fs_host_file.cpp b/source/processes/pxi_fs_host_file.cpp index 1220a1a..04e5a2e 100644 --- a/source/processes/pxi_fs_host_file.cpp +++ b/source/processes/pxi_fs_host_file.cpp @@ -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); } -ResultAnd<> HostFile::Open(FileContext& context, bool create_or_truncate) { - context.logger.info("Attempting to open {} (create={})", path, create_or_truncate); +ResultAnd<> HostFile::Open(FileContext& context, OpenFlags flags) { + 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); } - // 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! - auto stream_flags = std::ios_base::in | std::ios_base::out | std::ios_base::binary; - if (create_or_truncate) + auto stream_flags = std::ios_base::binary; + 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; + } 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! @@ -107,7 +113,7 @@ FileView::FileView(std::unique_ptr 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); 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 - if (create) { + if (flags.create) { throw std::runtime_error("Cannot set create flag on file views"); } - return file->Open(context, create); + return file->Open(context, flags); } OS::ResultAnd FileView::GetSize(FileContext&) {