// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #include "common/archives.h" #include "common/logging/log.h" #include "core/file_sys/archive_artic.h" #include "core/file_sys/archive_backend.h" #include "core/file_sys/romfs_reader.h" #include "core/hle/service/fs/fs_user.h" #include "core/loader/loader.h" SERIALIZE_EXPORT_IMPL(FileSys::DirectRomFSReader) namespace FileSys { std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) { length = std::min(length, static_cast(data_size) - offset); if (length == 0) return 0; // Crypto++ does not like zero size buffer const auto segments = BreakupRead(offset, length); std::size_t read_progress = 0; // Skip cache if the read is too big if (segments.size() == 1 && segments[0].second > cache_line_size) { length = file->ReadAtBytes(buffer, length, file_offset + offset); LOG_TRACE(Service_FS, "RomFS Cache SKIP: offset={}, length={}", offset, length); return length; } // TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function. // std::unique_lock read_guard(cache_mutex); for (const auto& seg : segments) { std::size_t read_size = cache_line_size; std::size_t page = OffsetToPage(seg.first); // Check if segment is in cache auto cache_entry = cache.request(page); if (!cache_entry.first) { // If not found, read from disk and cache the data read_size = file->ReadAtBytes(cache_entry.second.data(), read_size, file_offset + page); LOG_TRACE(Service_FS, "RomFS Cache MISS: page={}, length={}, into={}", page, seg.second, (seg.first - page)); } else { LOG_TRACE(Service_FS, "RomFS Cache HIT: page={}, length={}, into={}", page, seg.second, (seg.first - page)); } std::size_t copy_amount = (read_size > (seg.first - page)) ? std::min((seg.first - page) + seg.second, read_size) - (seg.first - page) : 0; std::memcpy(buffer + read_progress, cache_entry.second.data() + (seg.first - page), copy_amount); read_progress += copy_amount; } return read_progress; } bool DirectRomFSReader::AllowsCachedReads() const { return true; } bool DirectRomFSReader::CacheReady(std::size_t file_offset, std::size_t length) { auto segments = BreakupRead(file_offset, length); if (segments.size() == 1 && segments[0].second > cache_line_size) { return false; } else { // TODO(PabloMK7): Since the LRU cache is not thread safe, a lock must be used. // However, this completely breaks the point of using a cache, because // smaller reads may be blocked by bigger reads. For now, always return // data being in cache to prevent the need of a lock, and only read data // asynchronously if it is too big to use the cache. /* std::shared_lock read_guard(cache_mutex); for (auto it = segments.begin(); it != segments.end(); it++) { if (!cache.contains(OffsetToPage(it->first))) return false; } */ return true; } } std::vector> DirectRomFSReader::BreakupRead( std::size_t offset, std::size_t length) { std::vector> ret; // Reads bigger than the cache line size will probably never hit again if (length > cache_line_size) { ret.push_back(std::make_pair(offset, length)); return ret; } std::size_t curr_offset = offset; while (length) { std::size_t next_page = OffsetToPage(curr_offset + cache_line_size); std::size_t curr_page_len = std::min(length, next_page - curr_offset); ret.push_back(std::make_pair(curr_offset, curr_page_len)); curr_offset = next_page; length -= curr_page_len; } return ret; } ArticRomFSReader::ArticRomFSReader(std::shared_ptr& cli, bool is_update_romfs) : client(cli), cache(cli) { auto req = client->NewRequest("FSUSER_OpenFileDirectly"); FileSys::Path archive(FileSys::LowPathType::Empty, {}); std::vector fileVec(0xC); fileVec[0] = static_cast(is_update_romfs ? 5 : 0); FileSys::Path file(FileSys::LowPathType::Binary, fileVec); req.AddParameterS32(static_cast(Service::FS::ArchiveIdCode::SelfNCCH)); auto archive_buf = ArticArchive::BuildFSPath(archive); req.AddParameterBuffer(archive_buf.data(), archive_buf.size()); auto file_buf = ArticArchive::BuildFSPath(file); req.AddParameterBuffer(file_buf.data(), file_buf.size()); req.AddParameterS32(1); req.AddParameterS32(0); auto resp = client->Send(req); if (!resp.has_value() || !resp->Succeeded()) { load_status = Loader::ResultStatus::Error; return; } if (resp->GetMethodResult() != 0) { load_status = Loader::ResultStatus::ErrorNotUsed; return; } auto handle_buf = resp->GetResponseBuffer(0); if (!handle_buf.has_value() || handle_buf->second != sizeof(s32)) { load_status = Loader::ResultStatus::Error; return; } romfs_handle = *reinterpret_cast(handle_buf->first); req = client->NewRequest("FSFILE_GetSize"); req.AddParameterS32(romfs_handle); resp = client->Send(req); if (!resp.has_value() || !resp->Succeeded()) { load_status = Loader::ResultStatus::Error; return; } if (resp->GetMethodResult() != 0) { load_status = Loader::ResultStatus::ErrorNotUsed; return; } auto size_buf = resp->GetResponseBuffer(0); if (!size_buf.has_value() || size_buf->second != sizeof(u64)) { load_status = Loader::ResultStatus::Error; return; } data_size = static_cast(*reinterpret_cast(size_buf->first)); load_status = Loader::ResultStatus::Success; } ArticRomFSReader::~ArticRomFSReader() { if (romfs_handle != -1) { auto req = client->NewRequest("FSFILE_Close"); req.AddParameterS32(romfs_handle); client->Send(req); romfs_handle = -1; } } std::size_t ArticRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) { length = std::min(length, static_cast(data_size) - offset); auto res = cache.Read(romfs_handle, offset, length, buffer); if (res.Failed()) return 0; return res.Unwrap(); } bool ArticRomFSReader::AllowsCachedReads() const { return true; } bool ArticRomFSReader::CacheReady(std::size_t file_offset, std::size_t length) { return cache.CacheReady(file_offset, length); } void ArticRomFSReader::CloseFile() { if (romfs_handle != -1) { auto req = client->NewRequest("FSFILE_Close"); req.AddParameterS32(romfs_handle); client->Send(req); romfs_handle = -1; } } } // namespace FileSys