#pragma once #include "ipc.hpp" #include #include #include #include namespace HLE { namespace OS { class Thread; class FakeThread; using Result = uint32_t; template using ResultAnd = std::tuple; extern const Result RESULT_OK; } struct CommonPath; struct Utf8PathType; struct BinaryPathType; namespace PXI { struct PXIBuffer; namespace FS { struct FileContext { spdlog::logger& logger; }; /** * Interface for an internal buffer used as the data source/target for file * read/write operations. * * Usually, files operate on emulated memory, but it's beneficial for code * reuse in frontend code to enable file implementations to work with * host memory, too. This class enables this by abstracting away the notion * of emulated memory. */ class FileBuffer { protected: virtual ~FileBuffer() = default; public: /// Read the specified number of bytes from the internal buffer to dest // virtual void Read(char* dest, uint32_t num_bytes) = 0; /// Write the specified number of bytes from the source to the internal buffer virtual void Write(char* source, uint32_t num_bytes) = 0; }; class FileBufferInHostMemory : public FileBuffer { char* memory; uint32_t size; template char* CheckCast(T& t) { static_assert(!std::is_pointer_v, "Pointers must use the sized constructor"); return reinterpret_cast(&t); } public: FileBufferInHostMemory(void* ptr, uint32_t size) : memory(reinterpret_cast(ptr)), size(size) { } template FileBufferInHostMemory(T& t) : memory(CheckCast(t)), size(sizeof(T)) { } void Write(char* source, uint32_t num_bytes) override; }; class File { [[noreturn]] void Fail(); public: virtual ~File() { Close(); } /** * 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. */ virtual OS::ResultAnd<> Open(FileContext&, bool create_or_truncate); /** * @return Result code and number of bytes read (0 on error) */ virtual OS::ResultAnd Read(FileContext&, uint64_t offset, uint32_t num_bytes, FileBuffer&& dest); /** * Overwrites file contents of the given size (num_bytes) at the given * offset with the data from the PXIBuffer argument. * @return Result code and number of bytes written (0 on error) */ virtual OS::ResultAnd Overwrite(OS::FakeThread& thread, uint64_t offset, uint32_t num_bytes, const PXIBuffer& data); // Returns result code and file size in bytes (0 on error) virtual OS::ResultAnd GetSize(FileContext&) {return std::make_tuple(OS::RESULT_OK, 0);} /** * Resize the file by inserting unspecified data or removing backmost data. * For the sake of deterministic emulation, all future read operations are * guaranteed to return 0 until the data is initialized by the application. */ virtual OS::ResultAnd<> SetSize(OS::FakeThread& thread, uint64_t size) {return std::make_tuple(OS::RESULT_OK);} virtual void Close(/*OS::FakeThread& thread*/) {} }; class HostFile final : public File { public: enum Policy { Default = 0, PatchHash = 1, }; private: const boost::filesystem::path path; boost::filesystem::fstream stream; Policy policy; public: HostFile(std::string_view path, Policy policy); OS::ResultAnd<> Open(FileContext&, bool create) override; OS::ResultAnd<> SetSize(OS::FakeThread& thread, uint64_t size) override; OS::ResultAnd GetSize(FileContext&) override; OS::ResultAnd Read(FileContext&, uint64_t offset, uint32_t num_bytes, FileBuffer&& dest) override; OS::ResultAnd Overwrite(OS::FakeThread& thread, uint64_t offset, uint32_t num_bytes, const PXIBuffer& input) override; // TODO: Delete functionality void Close(/*OS::FakeThread& thread*/) override; }; class FileView final : public File { std::unique_ptr file; uint64_t offset; uint32_t num_bytes; boost::optional> precomputed_hash; 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; // SetSize is not allowed on views // OS::ResultAnd<> SetSize(OS::FakeThread& thread, uint64_t size) override; OS::ResultAnd GetSize(FileContext&) override; OS::ResultAnd Read(FileContext&, uint64_t offset, uint32_t num_bytes, FileBuffer&& dest) override; OS::ResultAnd Overwrite(OS::FakeThread& thread, uint64_t offset, uint32_t num_bytes, const PXIBuffer& input) override; std::array GetFileHash(OS::FakeThread& thread) const; // TODO: Should we use this for FS emulation too? std::unique_ptr ReleaseParentAndClose(); }; class Archive { Archive(const Archive&) = delete; protected: Archive() = default; public: virtual ~Archive() = default; std::pair> OpenFile(OS::FakeThread&, const HLE::CommonPath&); virtual std::pair> OpenFile(OS::FakeThread&, const HLE::Utf8PathType&); virtual std::pair> OpenFile(OS::FakeThread&, const HLE::BinaryPathType&); }; // TODO: Deprecate in favor of OpenNCCHSubFile std::unique_ptr NCCHOpenExeFSSection(spdlog::logger&, FileContext&, const KeyDatabase&, std::unique_ptr ncch, uint8_t sub_file_type, std::basic_string_view requested_exefs_section_name); std::unique_ptr OpenNCCHSubFile(OS::Thread& thread, Platform::PXI::PM::ProgramInfo, uint32_t content_id, uint32_t sub_file_type, std::basic_string_view file_path, Loader::GameCard*); } // namespace FS } // namespace PXI } // namespace HLE