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 [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) {

View file

@ -49,7 +49,7 @@ static bool IsLoadableCXIFile(HLE::PXI::FS::File& file) {
// TODO: Enable proper logging
auto logger = std::make_shared<spdlog::logger>("dummy", std::make_shared<spdlog::sinks::null_sink_st>());
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<std::unique_ptr<HLE::PXI::FS::File>> 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<spdlog::logger>("dummy", std::make_shared<spdlog::sinks::null_sink_st>());
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<spdlog::logger>("dummy", std::make_shared<spdlog::sinks::null_sink_st>());
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));

View file

@ -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);
}

View file

@ -996,7 +996,7 @@ public:
HLE::PXI::FS::FileContext file_context { *GetLogger() };
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);
if (result != RESULT_OK) {
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,
source.GetParentProcess().interpreter_setup.keydb,
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
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' };
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() };
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 */));
}

View file

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

View file

@ -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<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) {
}
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<uint64_t> 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<File> 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<Result, uint64_t> 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");

View file

@ -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<uint64_t> GetSize(FileContext&) override;
@ -154,7 +164,7 @@ class FileView final : public File {
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);
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;

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);
}
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> 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<uint64_t> FileView::GetSize(FileContext&) {