mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Add Artic Base support (#105)
* Add Artic Base support * Add Android support
This commit is contained in:
		
							parent
							
								
									572d3ab71c
								
							
						
					
					
						commit
						24c6ec5e6a
					
				
					 83 changed files with 5592 additions and 516 deletions
				
			
		|  | @ -40,6 +40,8 @@ add_library(citra_core STATIC | |||
|     dumping/backend.h | ||||
|     dumping/ffmpeg_backend.cpp | ||||
|     dumping/ffmpeg_backend.h | ||||
|     file_sys/archive_artic.cpp | ||||
|     file_sys/archive_artic.h | ||||
|     file_sys/archive_backend.cpp | ||||
|     file_sys/archive_backend.h | ||||
|     file_sys/archive_extsavedata.cpp | ||||
|  | @ -60,6 +62,8 @@ add_library(citra_core STATIC | |||
|     file_sys/archive_source_sd_savedata.h | ||||
|     file_sys/archive_systemsavedata.cpp | ||||
|     file_sys/archive_systemsavedata.h | ||||
|     file_sys/artic_cache.cpp | ||||
|     file_sys/artic_cache.h | ||||
|     file_sys/cia_common.h | ||||
|     file_sys/cia_container.cpp | ||||
|     file_sys/cia_container.h | ||||
|  | @ -87,6 +91,10 @@ add_library(citra_core STATIC | |||
|     file_sys/romfs_reader.h | ||||
|     file_sys/savedata_archive.cpp | ||||
|     file_sys/savedata_archive.h | ||||
|     file_sys/secure_value_backend_artic.cpp | ||||
|     file_sys/secure_value_backend_artic.h | ||||
|     file_sys/secure_value_backend.cpp | ||||
|     file_sys/secure_value_backend.h | ||||
|     file_sys/seed_db.cpp | ||||
|     file_sys/seed_db.h | ||||
|     file_sys/ticket.cpp | ||||
|  | @ -445,6 +453,8 @@ add_library(citra_core STATIC | |||
|     hw/y2r.h | ||||
|     loader/3dsx.cpp | ||||
|     loader/3dsx.h | ||||
|     loader/artic.cpp | ||||
|     loader/artic.h | ||||
|     loader/elf.cpp | ||||
|     loader/elf.h | ||||
|     loader/loader.cpp | ||||
|  | @ -470,7 +480,7 @@ add_library(citra_core STATIC | |||
|     tracer/citrace.h | ||||
|     tracer/recorder.cpp | ||||
|     tracer/recorder.h | ||||
| ) | ||||
|  ) | ||||
| 
 | ||||
| create_target_directory_groups(citra_core) | ||||
| 
 | ||||
|  |  | |||
|  | @ -256,7 +256,11 @@ System::ResultStatus System::SingleStep() { | |||
| System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath, | ||||
|                                   Frontend::EmuWindow* secondary_window) { | ||||
|     FileUtil::SetCurrentRomPath(filepath); | ||||
|     app_loader = Loader::GetLoader(filepath); | ||||
|     if (early_app_loader) { | ||||
|         app_loader = std::move(early_app_loader); | ||||
|     } else { | ||||
|         app_loader = Loader::GetLoader(filepath); | ||||
|     } | ||||
|     if (!app_loader) { | ||||
|         LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); | ||||
|         return ResultStatus::ErrorGetLoader; | ||||
|  | @ -286,6 +290,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st | |||
|             return ResultStatus::ErrorLoader_ErrorInvalidFormat; | ||||
|         case Loader::ResultStatus::ErrorGbaTitle: | ||||
|             return ResultStatus::ErrorLoader_ErrorGbaTitle; | ||||
|         case Loader::ResultStatus::ErrorArtic: | ||||
|             return ResultStatus::ErrorArticDisconnected; | ||||
|         default: | ||||
|             return ResultStatus::ErrorSystemMode; | ||||
|         } | ||||
|  | @ -334,6 +340,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st | |||
|             return ResultStatus::ErrorLoader_ErrorInvalidFormat; | ||||
|         case Loader::ResultStatus::ErrorGbaTitle: | ||||
|             return ResultStatus::ErrorLoader_ErrorGbaTitle; | ||||
|         case Loader::ResultStatus::ErrorArtic: | ||||
|             return ResultStatus::ErrorArticDisconnected; | ||||
|         default: | ||||
|             return ResultStatus::ErrorLoader; | ||||
|         } | ||||
|  | @ -691,6 +699,10 @@ void System::ApplySettings() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void System::RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader) { | ||||
|     early_app_loader = std::move(loader); | ||||
| } | ||||
| 
 | ||||
| template <class Archive> | ||||
| void System::serialize(Archive& ar, const unsigned int file_version) { | ||||
| 
 | ||||
|  |  | |||
|  | @ -99,6 +99,7 @@ public: | |||
|                                    ///< Console
 | ||||
|         ErrorSystemFiles,          ///< Error in finding system files
 | ||||
|         ErrorSavestate,            ///< Error saving or loading
 | ||||
|         ErrorArticDisconnected,    ///< Error when artic base disconnects
 | ||||
|         ShutdownRequested,         ///< Emulated program requested a system shutdown
 | ||||
|         ErrorUnknown               ///< Any other error
 | ||||
|     }; | ||||
|  | @ -169,6 +170,18 @@ public: | |||
| 
 | ||||
|     [[nodiscard]] PerfStats::Results GetAndResetPerfStats(); | ||||
| 
 | ||||
|     void ReportArticTraffic(u32 bytes) { | ||||
|         if (perf_stats) { | ||||
|             perf_stats->AddArticBaseTraffic(bytes); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void ReportPerfArticEvent(PerfStats::PerfArticEventBits event, bool set) { | ||||
|         if (perf_stats) { | ||||
|             perf_stats->ReportPerfArticEvent(event, set); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     [[nodiscard]] PerfStats::Results GetLastPerfStats(); | ||||
| 
 | ||||
|     /**
 | ||||
|  | @ -346,6 +359,8 @@ public: | |||
|     /// Applies any changes to settings to this core instance.
 | ||||
|     void ApplySettings(); | ||||
| 
 | ||||
|     void RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader); | ||||
| 
 | ||||
| private: | ||||
|     /**
 | ||||
|      * Initialize the emulated system. | ||||
|  | @ -366,6 +381,9 @@ private: | |||
|     /// AppLoader used to load the current executing application
 | ||||
|     std::unique_ptr<Loader::AppLoader> app_loader; | ||||
| 
 | ||||
|     // Temporary app loader passed from frontend
 | ||||
|     std::unique_ptr<Loader::AppLoader> early_app_loader; | ||||
| 
 | ||||
|     /// ARM11 CPU core
 | ||||
|     std::vector<std::shared_ptr<ARM_Interface>> cpu_cores; | ||||
|     ARM_Interface* running_core = nullptr; | ||||
|  |  | |||
							
								
								
									
										535
									
								
								src/core/file_sys/archive_artic.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										535
									
								
								src/core/file_sys/archive_artic.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,535 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "archive_artic.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| std::vector<u8> ArticArchive::BuildFSPath(const Path& path) { | ||||
|     std::vector<u8> ret(sizeof(u32) * 2); | ||||
|     u32* raw_data = reinterpret_cast<u32*>(ret.data()); | ||||
|     auto path_type = path.GetType(); | ||||
|     auto binary = path.AsBinary(); | ||||
|     raw_data[0] = static_cast<u32>(path_type); | ||||
|     raw_data[1] = static_cast<u32>(binary.size()); | ||||
|     if (!binary.empty()) { | ||||
|         ret.insert(ret.end(), binary.begin(), binary.end()); | ||||
|     } | ||||
| 
 | ||||
|     // The insert may have invalidated the pointer
 | ||||
|     raw_data = reinterpret_cast<u32*>(ret.data()); | ||||
|     if (path_type != LowPathType::Binary && path_type != LowPathType::Invalid) { | ||||
|         if (path_type == LowPathType::Wchar) { | ||||
|             raw_data[1] += 2; | ||||
|             ret.push_back(0); | ||||
|             ret.push_back(0); | ||||
|         } else { | ||||
|             raw_data[1] += 1; | ||||
|             ret.push_back(0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::RespResult(const std::optional<Network::ArticBase::Client::Response>& resp) { | ||||
|     if (!resp.has_value() || !resp->Succeeded()) { | ||||
|         return ResultUnknown; | ||||
|     } | ||||
|     return Result(static_cast<u32>(resp->GetMethodResult())); | ||||
| } | ||||
| 
 | ||||
| ArticArchive::~ArticArchive() { | ||||
|     if (clear_cache_on_close) { | ||||
|         cache_provider->ClearAllCache(); | ||||
|     } | ||||
|     if (archive_handle != -1) { | ||||
|         auto req = client->NewRequest("FSUSER_CloseArchive"); | ||||
|         req.AddParameterS64(archive_handle); | ||||
|         client->Send(req); | ||||
|         if (report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { | ||||
|             client->ReportArticEvent(static_cast<u64>(report_artic_event)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArticArchive::Open( | ||||
|     std::shared_ptr<Network::ArticBase::Client>& client, Service::FS::ArchiveIdCode archive_id, | ||||
|     const Path& path, Core::PerfStats::PerfArticEventBits report_artic_event, | ||||
|     ArticCacheProvider& cache_provider, bool clear_cache_on_close) { | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSUSER_OpenArchive"); | ||||
| 
 | ||||
|     req.AddParameterS32(static_cast<s32>(archive_id)); | ||||
|     auto path_buf = BuildFSPath(path); | ||||
|     req.AddParameterBuffer(path_buf.data(), path_buf.size()); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     if (!resp.has_value() || !resp->Succeeded()) { | ||||
|         return ResultUnknown; | ||||
|     } | ||||
|     Result res(static_cast<u32>(resp->GetMethodResult())); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     auto handle_opt = resp->GetResponseS64(0); | ||||
|     if (!handle_opt.has_value()) { | ||||
|         return ResultUnknown; | ||||
|     } | ||||
| 
 | ||||
|     return std::make_unique<ArticArchive>(client, *handle_opt, report_artic_event, cache_provider, | ||||
|                                           path, clear_cache_on_close); | ||||
| } | ||||
| 
 | ||||
| void ArticArchive::Close() { | ||||
|     if (clear_cache_on_close) { | ||||
|         cache_provider->ClearAllCache(); | ||||
|     } | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSUSER_CloseArchive"); | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     if (RespResult(client->Send(req)).IsSuccess()) { | ||||
|         archive_handle = -1; | ||||
|         if (report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { | ||||
|             client->ReportArticEvent(static_cast<u64>(report_artic_event)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::string ArticArchive::GetName() const { | ||||
|     return "ArticArchive"; | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<FileBackend>> ArticArchive::OpenFile(const Path& path, const Mode& mode, | ||||
|                                                                u32 attributes) { | ||||
|     if (mode.create_flag) { | ||||
|         auto cache = cache_provider->ProvideCache( | ||||
|             client, cache_provider->PathsToVector(archive_path, path), false); | ||||
|         if (cache != nullptr) { | ||||
|             cache->Clear(); | ||||
|         } | ||||
|     } | ||||
|     auto req = client->NewRequest("FSUSER_OpenFile"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto path_buf = BuildFSPath(path); | ||||
|     req.AddParameterBuffer(path_buf.data(), path_buf.size()); | ||||
|     req.AddParameterU32(mode.hex); | ||||
|     req.AddParameterU32(attributes); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = RespResult(resp); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     auto handle_opt = resp->GetResponseS32(0); | ||||
|     if (!handle_opt.has_value()) | ||||
|         return ResultUnknown; | ||||
| 
 | ||||
|     auto size_opt = resp->GetResponseU64(1); | ||||
|     if (size_opt.has_value()) { | ||||
|         auto cache = cache_provider->ProvideCache( | ||||
|             client, cache_provider->PathsToVector(archive_path, path), true); | ||||
|         if (cache != nullptr) { | ||||
|             cache->ForceSetSize(static_cast<size_t>(*size_opt)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (open_reporter->open_files++ == 0 && | ||||
|         report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { | ||||
|         client->ReportArticEvent(static_cast<u64>(report_artic_event) | (1ULL << 32)); | ||||
|     } | ||||
| 
 | ||||
|     return std::make_unique<ArticFileBackend>(client, *handle_opt, open_reporter, archive_path, | ||||
|                                               *cache_provider, path); | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::DeleteFile(const Path& path) const { | ||||
|     auto cache = cache_provider->ProvideCache( | ||||
|         client, cache_provider->PathsToVector(archive_path, path), false); | ||||
|     if (cache != nullptr) { | ||||
|         cache->Clear(); | ||||
|     } | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSUSER_DeleteFile"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto path_buf = BuildFSPath(path); | ||||
|     req.AddParameterBuffer(path_buf.data(), path_buf.size()); | ||||
| 
 | ||||
|     return RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::RenameFile(const Path& src_path, const Path& dest_path) const { | ||||
|     auto cache = cache_provider->ProvideCache( | ||||
|         client, cache_provider->PathsToVector(archive_path, src_path), false); | ||||
|     if (cache != nullptr) { | ||||
|         cache->Clear(); | ||||
|     } | ||||
|     cache = cache_provider->ProvideCache( | ||||
|         client, cache_provider->PathsToVector(archive_path, dest_path), false); | ||||
|     if (cache != nullptr) { | ||||
|         cache->Clear(); | ||||
|     } | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSUSER_RenameFile"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto src_path_buf = BuildFSPath(src_path); | ||||
|     req.AddParameterBuffer(src_path_buf.data(), src_path_buf.size()); | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto dest_path_buf = BuildFSPath(dest_path); | ||||
|     req.AddParameterBuffer(dest_path_buf.data(), dest_path_buf.size()); | ||||
| 
 | ||||
|     return RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::DeleteDirectory(const Path& path) const { | ||||
|     cache_provider->ClearAllCache(); | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSUSER_DeleteDirectory"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto path_buf = BuildFSPath(path); | ||||
|     req.AddParameterBuffer(path_buf.data(), path_buf.size()); | ||||
| 
 | ||||
|     return RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::DeleteDirectoryRecursively(const Path& path) const { | ||||
|     cache_provider->ClearAllCache(); | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSUSER_DeleteDirectoryRec"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto path_buf = BuildFSPath(path); | ||||
|     req.AddParameterBuffer(path_buf.data(), path_buf.size()); | ||||
| 
 | ||||
|     return RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::CreateFile(const Path& path, u64 size, u32 attributes) const { | ||||
|     auto cache = cache_provider->ProvideCache( | ||||
|         client, cache_provider->PathsToVector(archive_path, path), false); | ||||
|     if (cache != nullptr) { | ||||
|         cache->Clear(); | ||||
|     } | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSUSER_CreateFile"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto path_buf = BuildFSPath(path); | ||||
|     req.AddParameterBuffer(path_buf.data(), path_buf.size()); | ||||
|     req.AddParameterU32(attributes); | ||||
|     req.AddParameterU64(size); | ||||
| 
 | ||||
|     return RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::CreateDirectory(const Path& path, u32 attributes) const { | ||||
|     auto req = client->NewRequest("FSUSER_CreateDirectory"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto path_buf = BuildFSPath(path); | ||||
|     req.AddParameterBuffer(path_buf.data(), path_buf.size()); | ||||
|     req.AddParameterU32(attributes); | ||||
| 
 | ||||
|     return RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { | ||||
|     cache_provider->ClearAllCache(); | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSUSER_RenameDirectory"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto src_path_buf = BuildFSPath(src_path); | ||||
|     req.AddParameterBuffer(src_path_buf.data(), src_path_buf.size()); | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto dest_path_buf = BuildFSPath(dest_path); | ||||
|     req.AddParameterBuffer(dest_path_buf.data(), dest_path_buf.size()); | ||||
| 
 | ||||
|     return RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> ArticArchive::OpenDirectory(const Path& path) { | ||||
|     auto req = client->NewRequest("FSUSER_OpenDirectory"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     auto path_buf = BuildFSPath(path); | ||||
|     req.AddParameterBuffer(path_buf.data(), path_buf.size()); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = RespResult(resp); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     auto handle_opt = resp->GetResponseS32(0); | ||||
|     if (!handle_opt.has_value()) | ||||
|         return ResultUnknown; | ||||
| 
 | ||||
|     if (open_reporter->open_files++ == 0 && | ||||
|         report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { | ||||
|         client->ReportArticEvent(static_cast<u64>(report_artic_event) | (1ULL << 32)); | ||||
|     } | ||||
| 
 | ||||
|     return std::make_unique<ArticDirectoryBackend>(client, *handle_opt, archive_path, | ||||
|                                                    open_reporter); | ||||
| } | ||||
| 
 | ||||
| u64 ArticArchive::GetFreeBytes() const { | ||||
|     auto req = client->NewRequest("FSUSER_GetFreeBytes"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = RespResult(resp); | ||||
|     if (res.IsError()) // TODO(PabloMK7): Return error code and not u64
 | ||||
|         return 0; | ||||
| 
 | ||||
|     auto free_bytes_opt = resp->GetResponseS64(0); | ||||
|     return free_bytes_opt.has_value() ? static_cast<u64>(*free_bytes_opt) : 0; | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::Control(u32 action, u8* input, size_t input_size, u8* output, | ||||
|                              size_t output_size) { | ||||
|     auto req = client->NewRequest("FSUSER_ControlArchive"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     req.AddParameterU32(action); | ||||
|     req.AddParameterBuffer(input, input_size); | ||||
|     req.AddParameterU32(static_cast<u32>(output_size)); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = RespResult(resp); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     auto output_buf = resp->GetResponseBuffer(0); | ||||
|     if (!output_buf.has_value()) | ||||
|         return res; | ||||
| 
 | ||||
|     if (output_buf->second != output_size) | ||||
|         return ResultUnknown; | ||||
| 
 | ||||
|     memcpy(output, output_buf->first, output_buf->second); | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| Result ArticArchive::SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) { | ||||
|     auto req = client->NewRequest("FSUSER_SetSaveDataSecureValue"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     req.AddParameterU32(secure_value_slot); | ||||
|     req.AddParameterU64(secure_value); | ||||
|     req.AddParameterS8(flush != 0); | ||||
| 
 | ||||
|     return RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::tuple<bool, bool, u64>> ArticArchive::GetSaveDataSecureValue(u32 secure_value_slot) { | ||||
|     auto req = client->NewRequest("FSUSER_GetSaveDataSecureValue"); | ||||
| 
 | ||||
|     req.AddParameterS64(archive_handle); | ||||
|     req.AddParameterU32(secure_value_slot); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = RespResult(resp); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     struct { | ||||
|         bool exists; | ||||
|         bool isGamecard; | ||||
|         u64 secure_value; | ||||
|     } secure_value_result; | ||||
|     static_assert(sizeof(secure_value_result) == 0x10); | ||||
| 
 | ||||
|     auto output_buf = resp->GetResponseBuffer(0); | ||||
|     if (!output_buf.has_value()) | ||||
|         return res; | ||||
| 
 | ||||
|     if (output_buf->second != sizeof(secure_value_result)) | ||||
|         return ResultUnknown; | ||||
| 
 | ||||
|     memcpy(&secure_value_result, output_buf->first, output_buf->second); | ||||
|     return std::make_tuple(secure_value_result.exists, secure_value_result.isGamecard, | ||||
|                            secure_value_result.secure_value); | ||||
| } | ||||
| 
 | ||||
| void ArticArchive::OpenFileReporter::OnFileClosed() { | ||||
|     if (--open_files == 0 && report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { | ||||
|         client->ReportArticEvent(static_cast<u64>(report_artic_event)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ArticArchive::OpenFileReporter::OnDirectoryClosed() { | ||||
|     if (--open_files == 0 && report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { | ||||
|         client->ReportArticEvent(static_cast<u64>(report_artic_event)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ArticFileBackend::~ArticFileBackend() { | ||||
|     if (file_handle != -1) { | ||||
|         auto req = client->NewRequest("FSFILE_Close"); | ||||
|         req.AddParameterS32(file_handle); | ||||
|         client->Send(req); | ||||
|         open_reporter->OnFileClosed(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::size_t> ArticFileBackend::Read(u64 offset, std::size_t length, u8* buffer) const { | ||||
|     auto cache = cache_provider->ProvideCache( | ||||
|         client, cache_provider->PathsToVector(archive_path, file_path), true); | ||||
| 
 | ||||
|     if (cache != nullptr) { | ||||
|         return cache->Read(file_handle, offset, length, buffer); | ||||
|     } | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSFILE_Read"); | ||||
| 
 | ||||
|     req.AddParameterS32(file_handle); | ||||
|     req.AddParameterU64(offset); | ||||
|     req.AddParameterU32(static_cast<u32>(length)); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = ArticArchive::RespResult(resp); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     auto read_buf = resp->GetResponseBuffer(0); | ||||
|     if (!read_buf || read_buf->second > length) { | ||||
|         return std::size_t(0); | ||||
|     } | ||||
| 
 | ||||
|     memcpy(buffer, read_buf->first, read_buf->second); | ||||
|     return read_buf->second; | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::size_t> ArticFileBackend::Write(u64 offset, std::size_t length, bool flush, | ||||
|                                                bool update_timestamp, const u8* buffer) { | ||||
|     u32 flags = (flush ? 1 : 0) | (update_timestamp ? (1 << 8) : 0); | ||||
|     auto cache = cache_provider->ProvideCache( | ||||
|         client, cache_provider->PathsToVector(archive_path, file_path), true); | ||||
|     if (cache != nullptr) { | ||||
|         return cache->Write(file_handle, offset, length, buffer, flags); | ||||
|     } else { | ||||
|         auto req = client->NewRequest("FSFILE_Write"); | ||||
| 
 | ||||
|         req.AddParameterS32(file_handle); | ||||
|         req.AddParameterU64(offset); | ||||
|         req.AddParameterU32(static_cast<u32>(length)); | ||||
|         req.AddParameterU32(flags); | ||||
|         req.AddParameterBuffer(buffer, length); | ||||
| 
 | ||||
|         auto resp = client->Send(req); | ||||
|         auto res = ArticArchive::RespResult(resp); | ||||
|         if (res.IsError()) | ||||
|             return res; | ||||
| 
 | ||||
|         auto writen_buf = resp->GetResponseS32(0); | ||||
|         if (!writen_buf) { | ||||
|             return std::size_t(0); | ||||
|         } | ||||
| 
 | ||||
|         return std::size_t(*writen_buf); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| u64 ArticFileBackend::GetSize() const { | ||||
|     auto cache = cache_provider->ProvideCache( | ||||
|         client, cache_provider->PathsToVector(archive_path, file_path), true); | ||||
|     if (cache != nullptr) { | ||||
|         auto res = cache->GetSize(file_handle); | ||||
|         if (res.Failed()) | ||||
|             return 0; | ||||
|         return res.Unwrap(); | ||||
|     } else { | ||||
| 
 | ||||
|         auto req = client->NewRequest("FSFILE_GetSize"); | ||||
| 
 | ||||
|         req.AddParameterS32(file_handle); | ||||
| 
 | ||||
|         auto resp = client->Send(req); | ||||
|         auto res = ArticArchive::RespResult(resp); | ||||
|         if (res.IsError()) | ||||
|             return 0; | ||||
| 
 | ||||
|         auto size_buf = resp->GetResponseS64(0); | ||||
|         if (!size_buf) { | ||||
|             return 0; | ||||
|         } | ||||
|         return *size_buf; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool ArticFileBackend::SetSize(u64 size) const { | ||||
|     auto req = client->NewRequest("FSFILE_SetSize"); | ||||
| 
 | ||||
|     req.AddParameterS32(file_handle); | ||||
|     req.AddParameterU64(size); | ||||
| 
 | ||||
|     return ArticArchive::RespResult(client->Send(req)).IsSuccess(); | ||||
| } | ||||
| 
 | ||||
| bool ArticFileBackend::Close() { | ||||
|     auto req = client->NewRequest("FSFILE_Close"); | ||||
|     req.AddParameterS32(file_handle); | ||||
|     bool ret = ArticArchive::RespResult(client->Send(req)).IsSuccess(); | ||||
|     if (ret) { | ||||
|         file_handle = -1; | ||||
|         open_reporter->OnFileClosed(); | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void ArticFileBackend::Flush() const { | ||||
|     auto req = client->NewRequest("FSFILE_Flush"); | ||||
| 
 | ||||
|     req.AddParameterS32(file_handle); | ||||
| 
 | ||||
|     client->Send(req); | ||||
| } | ||||
| 
 | ||||
| ArticDirectoryBackend::~ArticDirectoryBackend() { | ||||
|     if (dir_handle != -1) { | ||||
|         auto req = client->NewRequest("FSDIR_Close"); | ||||
|         req.AddParameterS32(dir_handle); | ||||
|         client->Send(req); | ||||
|         open_reporter->OnDirectoryClosed(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| u32 ArticDirectoryBackend::Read(const u32 count, Entry* entries) { | ||||
|     auto req = client->NewRequest("FSDIR_Read"); | ||||
| 
 | ||||
|     req.AddParameterS32(dir_handle); | ||||
|     req.AddParameterU32(count); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = ArticArchive::RespResult(resp); | ||||
|     if (res.IsError()) | ||||
|         return 0; | ||||
| 
 | ||||
|     auto entry_buf = resp->GetResponseBuffer(0); | ||||
|     if (!entry_buf) { | ||||
|         return 0; | ||||
|     } | ||||
|     u32 ret_count = static_cast<u32>(entry_buf->second / sizeof(Entry)); | ||||
| 
 | ||||
|     memcpy(entries, entry_buf->first, ret_count * sizeof(Entry)); | ||||
|     return ret_count; | ||||
| } | ||||
| 
 | ||||
| bool ArticDirectoryBackend::Close() { | ||||
|     auto req = client->NewRequest("FSDIR_Close"); | ||||
|     req.AddParameterS32(dir_handle); | ||||
|     bool ret = ArticArchive::RespResult(client->Send(req)).IsSuccess(); | ||||
|     if (ret) { | ||||
|         dir_handle = -1; | ||||
|         open_reporter->OnDirectoryClosed(); | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										268
									
								
								src/core/file_sys/archive_artic.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/core/file_sys/archive_artic.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,268 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "atomic" | ||||
| 
 | ||||
| #include <boost/serialization/unique_ptr.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/archive_backend.h" | ||||
| #include "core/file_sys/artic_cache.h" | ||||
| #include "core/file_sys/directory_backend.h" | ||||
| #include "core/file_sys/file_backend.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "core/perf_stats.h" | ||||
| #include "network/artic_base/artic_base_client.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| class ArticArchive : public ArchiveBackend { | ||||
| public: | ||||
|     static std::vector<u8> BuildFSPath(const Path& path); | ||||
|     static Result RespResult(const std::optional<Network::ArticBase::Client::Response>& resp); | ||||
| 
 | ||||
|     explicit ArticArchive(std::shared_ptr<Network::ArticBase::Client>& _client, s64 _archive_handle, | ||||
|                           Core::PerfStats::PerfArticEventBits _report_artic_event, | ||||
|                           ArticCacheProvider& _cache_provider, const Path& _archive_path, | ||||
|                           bool _clear_cache_on_close) | ||||
|         : client(_client), archive_handle(_archive_handle), report_artic_event(_report_artic_event), | ||||
|           cache_provider(&_cache_provider), archive_path(_archive_path), | ||||
|           clear_cache_on_close(_clear_cache_on_close) { | ||||
|         open_reporter = std::make_shared<OpenFileReporter>(_client, _report_artic_event); | ||||
|     } | ||||
|     ~ArticArchive() override; | ||||
| 
 | ||||
|     static ResultVal<std::unique_ptr<ArchiveBackend>> Open( | ||||
|         std::shared_ptr<Network::ArticBase::Client>& client, Service::FS::ArchiveIdCode archive_id, | ||||
|         const Path& path, Core::PerfStats::PerfArticEventBits report_artic_event, | ||||
|         ArticCacheProvider& cache_provider, bool clear_cache_on_close); | ||||
| 
 | ||||
|     void Close() override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.) | ||||
|      */ | ||||
|     std::string GetName() const override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Open a file specified by its path, using the specified mode | ||||
|      * @param path Path relative to the archive | ||||
|      * @param mode Mode to open the file with | ||||
|      * @return Opened file, or error code | ||||
|      */ | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode, | ||||
|                                                      u32 attributes) override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Delete a file specified by its path | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     Result DeleteFile(const Path& path) const override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Rename a File specified by its path | ||||
|      * @param src_path Source path relative to the archive | ||||
|      * @param dest_path Destination path relative to the archive | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     Result RenameFile(const Path& src_path, const Path& dest_path) const override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Delete a directory specified by its path | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     Result DeleteDirectory(const Path& path) const override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Delete a directory specified by its path and anything under it | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     Result DeleteDirectoryRecursively(const Path& path) const override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Create a file specified by its path | ||||
|      * @param path Path relative to the Archive | ||||
|      * @param size The size of the new file, filled with zeroes | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     Result CreateFile(const Path& path, u64 size, u32 attributes) const override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Create a directory specified by its path | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     Result CreateDirectory(const Path& path, u32 attributes) const override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Rename a Directory specified by its path | ||||
|      * @param src_path Source path relative to the archive | ||||
|      * @param dest_path Destination path relative to the archive | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Open a directory specified by its path | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Opened directory, or error code | ||||
|      */ | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Get the free space | ||||
|      * @return The number of free bytes in the archive | ||||
|      */ | ||||
|     u64 GetFreeBytes() const override; | ||||
| 
 | ||||
|     Result Control(u32 action, u8* input, size_t input_size, u8* output, | ||||
|                    size_t output_size) override; | ||||
| 
 | ||||
|     Result SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) override; | ||||
| 
 | ||||
|     ResultVal<std::tuple<bool, bool, u64>> GetSaveDataSecureValue(u32 secure_value_slot) override; | ||||
| 
 | ||||
|     bool IsSlow() override { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     const Path& GetArchivePath() { | ||||
|         return archive_path; | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     ArticArchive() = default; | ||||
| 
 | ||||
| private: | ||||
|     friend class ArticFileBackend; | ||||
|     friend class ArticDirectoryBackend; | ||||
|     class OpenFileReporter { | ||||
|     public: | ||||
|         OpenFileReporter(const std::shared_ptr<Network::ArticBase::Client>& cli, | ||||
|                          Core::PerfStats::PerfArticEventBits _report_artic_event) | ||||
|             : client(cli), report_artic_event(_report_artic_event) {} | ||||
| 
 | ||||
|         void OnFileClosed(); | ||||
| 
 | ||||
|         void OnDirectoryClosed(); | ||||
| 
 | ||||
|         std::shared_ptr<Network::ArticBase::Client> client; | ||||
|         Core::PerfStats::PerfArticEventBits report_artic_event = | ||||
|             Core::PerfStats::PerfArticEventBits::NONE; | ||||
|         std::atomic<u32> open_files = 0; | ||||
|     }; | ||||
| 
 | ||||
|     std::shared_ptr<Network::ArticBase::Client> client; | ||||
|     s64 archive_handle; | ||||
|     std::shared_ptr<OpenFileReporter> open_reporter; | ||||
|     Core::PerfStats::PerfArticEventBits report_artic_event = | ||||
|         Core::PerfStats::PerfArticEventBits::NONE; | ||||
|     ArticCacheProvider* cache_provider = nullptr; | ||||
|     Path archive_path; | ||||
|     bool clear_cache_on_close; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<ArchiveBackend>(*this); | ||||
|         ar& archive_handle; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| 
 | ||||
| class ArticFileBackend : public FileBackend { | ||||
| public: | ||||
|     explicit ArticFileBackend(std::shared_ptr<Network::ArticBase::Client>& _client, | ||||
|                               s32 _file_handle, | ||||
|                               const std::shared_ptr<ArticArchive::OpenFileReporter>& _open_reporter, | ||||
|                               const Path& _archive_path, ArticCacheProvider& _cache_provider, | ||||
|                               const Path& _file_path) | ||||
|         : client(_client), file_handle(_file_handle), open_reporter(_open_reporter), | ||||
|           archive_path(_archive_path), cache_provider(&_cache_provider), file_path(_file_path) {} | ||||
|     ~ArticFileBackend() override; | ||||
| 
 | ||||
|     ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override; | ||||
| 
 | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override; | ||||
| 
 | ||||
|     u64 GetSize() const override; | ||||
| 
 | ||||
|     bool SetSize(u64 size) const override; | ||||
| 
 | ||||
|     bool Close() override; | ||||
| 
 | ||||
|     void Flush() const override; | ||||
| 
 | ||||
|     bool AllowsCachedReads() const override { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     bool CacheReady(std::size_t file_offset, std::size_t length) override { | ||||
|         auto cache = cache_provider->ProvideCache( | ||||
|             client, cache_provider->PathsToVector(archive_path, file_path), true); | ||||
|         if (cache == nullptr) { | ||||
|             return false; | ||||
|         } | ||||
|         return cache->CacheReady(file_offset, length); | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     ArticFileBackend() = default; | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<Network::ArticBase::Client> client; | ||||
|     s32 file_handle; | ||||
|     std::shared_ptr<ArticArchive::OpenFileReporter> open_reporter; | ||||
|     Path archive_path; | ||||
|     ArticCacheProvider* cache_provider = nullptr; | ||||
|     Path file_path; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<FileBackend>(*this); | ||||
|         ar& file_handle; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| 
 | ||||
| class ArticDirectoryBackend : public DirectoryBackend { | ||||
| public: | ||||
|     explicit ArticDirectoryBackend( | ||||
|         std::shared_ptr<Network::ArticBase::Client>& _client, s32 _dir_handle, | ||||
|         const Path& _archive_path, | ||||
|         const std::shared_ptr<ArticArchive::OpenFileReporter>& _open_reporter) | ||||
|         : client(_client), dir_handle(_dir_handle), archive_path(_archive_path), | ||||
|           open_reporter(_open_reporter) {} | ||||
|     ~ArticDirectoryBackend() override; | ||||
| 
 | ||||
|     u32 Read(const u32 count, Entry* entries) override; | ||||
|     bool Close() override; | ||||
| 
 | ||||
|     bool IsSlow() override { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     ArticDirectoryBackend() = default; | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<Network::ArticBase::Client> client; | ||||
|     s32 dir_handle; | ||||
|     Path archive_path; | ||||
|     std::shared_ptr<ArticArchive::OpenFileReporter> open_reporter; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<DirectoryBackend>(*this); | ||||
|         ar& dir_handle; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| BOOST_CLASS_EXPORT_KEY(FileSys::ArticArchive) | ||||
| BOOST_CLASS_EXPORT_KEY(FileSys::ArticFileBackend) | ||||
| BOOST_CLASS_EXPORT_KEY(FileSys::ArticDirectoryBackend) | ||||
|  | @ -105,8 +105,7 @@ std::vector<u8> Path::AsBinary() const { | |||
|         std::vector<u8> to_return(u16str.size() * 2); | ||||
|         for (std::size_t i = 0; i < u16str.size(); ++i) { | ||||
|             u16 tmp_char = u16str.at(i); | ||||
|             to_return[i * 2] = (tmp_char & 0xFF00) >> 8; | ||||
|             to_return[i * 2 + 1] = (tmp_char & 0x00FF); | ||||
|             *reinterpret_cast<u16*>(to_return.data() + i * 2) = tmp_char; | ||||
|         } | ||||
|         return to_return; | ||||
|     } | ||||
|  |  | |||
|  | @ -103,6 +103,7 @@ struct ArchiveFormatInfo { | |||
|     u8 duplicate_data;         ///< Whether the archive should duplicate the data.
 | ||||
| }; | ||||
| static_assert(std::is_trivial_v<ArchiveFormatInfo>, "ArchiveFormatInfo is not POD"); | ||||
| static_assert(sizeof(ArchiveFormatInfo) == 16, "Invalid ArchiveFormatInfo size"); | ||||
| 
 | ||||
| class ArchiveBackend : NonCopyable { | ||||
| public: | ||||
|  | @ -119,8 +120,8 @@ public: | |||
|      * @param mode Mode to open the file with | ||||
|      * @return Opened file, or error code | ||||
|      */ | ||||
|     virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||
|                                                              const Mode& mode) const = 0; | ||||
|     virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode, | ||||
|                                                              u32 attributes = 0) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Delete a file specified by its path | ||||
|  | @ -157,14 +158,14 @@ public: | |||
|      * @param size The size of the new file, filled with zeroes | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     virtual Result CreateFile(const Path& path, u64 size) const = 0; | ||||
|     virtual Result CreateFile(const Path& path, u64 size, u32 attributes = 0) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Create a directory specified by its path | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     virtual Result CreateDirectory(const Path& path) const = 0; | ||||
|     virtual Result CreateDirectory(const Path& path, u32 attributes = 0) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Rename a Directory specified by its path | ||||
|  | @ -179,7 +180,7 @@ public: | |||
|      * @param path Path relative to the archive | ||||
|      * @return Opened directory, or error code | ||||
|      */ | ||||
|     virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0; | ||||
|     virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Get the free space | ||||
|  | @ -187,6 +188,20 @@ public: | |||
|      */ | ||||
|     virtual u64 GetFreeBytes() const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Close the archive | ||||
|      */ | ||||
|     virtual void Close() {} | ||||
| 
 | ||||
|     virtual Result Control(u32 action, u8* input, size_t input_size, u8* output, | ||||
|                            size_t output_size) { | ||||
|         LOG_WARNING(Service_FS, | ||||
|                     "(STUBBED) called, archive={}, action={:08X}, input_size={:08X}, " | ||||
|                     "output_size={:08X}", | ||||
|                     GetName(), action, input_size, output_size); | ||||
|         return ResultSuccess; | ||||
|     } | ||||
| 
 | ||||
|     u64 GetOpenDelayNs() { | ||||
|         if (delay_generator != nullptr) { | ||||
|             return delay_generator->GetOpenDelayNs(); | ||||
|  | @ -196,6 +211,31 @@ public: | |||
|         return delay_generator->GetOpenDelayNs(); | ||||
|     } | ||||
| 
 | ||||
|     virtual Result SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) { | ||||
| 
 | ||||
|         // TODO: Generate and Save the Secure Value
 | ||||
| 
 | ||||
|         LOG_WARNING(Service_FS, | ||||
|                     "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:04X} " | ||||
|                     "flush={}", | ||||
|                     secure_value, secure_value_slot, flush); | ||||
| 
 | ||||
|         return ResultSuccess; | ||||
|     } | ||||
| 
 | ||||
|     virtual ResultVal<std::tuple<bool, bool, u64>> GetSaveDataSecureValue(u32 secure_value_slot) { | ||||
| 
 | ||||
|         // TODO: Implement Secure Value Lookup & Generation
 | ||||
| 
 | ||||
|         LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X}", secure_value_slot); | ||||
| 
 | ||||
|         return std::make_tuple<bool, bool, u64>(false, true, 0); | ||||
|     } | ||||
| 
 | ||||
|     virtual bool IsSlow() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     std::unique_ptr<DelayGenerator> delay_generator; | ||||
| 
 | ||||
|  | @ -232,7 +272,7 @@ public: | |||
|      * @return Result of the operation, 0 on success | ||||
|      */ | ||||
|     virtual Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                           u64 program_id) = 0; | ||||
|                           u64 program_id, u32 directory_buckets, u32 file_buckets) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Retrieves the format info about the archive with the specified path | ||||
|  | @ -242,6 +282,10 @@ public: | |||
|      */ | ||||
|     virtual ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const = 0; | ||||
| 
 | ||||
|     virtual bool IsSlow() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) {} | ||||
|     friend class boost::serialization::access; | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| #include "common/common_types.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/file_sys/archive_artic.h" | ||||
| #include "core/file_sys/archive_extsavedata.h" | ||||
| #include "core/file_sys/disk_archive.h" | ||||
| #include "core/file_sys/errors.h" | ||||
|  | @ -37,7 +38,7 @@ public: | |||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override { | ||||
|         if (offset > size) { | ||||
|             return ResultWriteBeyondEnd; | ||||
|  | @ -49,7 +50,7 @@ public: | |||
|             length = size - offset; | ||||
|         } | ||||
| 
 | ||||
|         return DiskFile::Write(offset, length, flush, buffer); | ||||
|         return DiskFile::Write(offset, length, flush, update_timestamp, buffer); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|  | @ -100,8 +101,8 @@ public: | |||
|         return "ExtSaveDataArchive: " + mount_point; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||
|                                                      const Mode& mode) const override { | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode, | ||||
|                                                      u32 attributes) override { | ||||
|         LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex); | ||||
| 
 | ||||
|         const PathParser path_parser(path); | ||||
|  | @ -234,69 +235,187 @@ Path ArchiveFactory_ExtSaveData::GetCorrectedPath(const Path& path) { | |||
|     return {binary_data}; | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path, | ||||
|                                                                             u64 program_id) { | ||||
|     const auto directory = type == ExtSaveDataType::Boss ? "boss/" : "user/"; | ||||
|     const auto fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + directory; | ||||
|     if (!FileUtil::Exists(fullpath)) { | ||||
|         // TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
 | ||||
|         // ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
 | ||||
|         if (type != ExtSaveDataType::Shared) { | ||||
|             return ResultNotFoundInvalidState; | ||||
|         } else { | ||||
|             return ResultNotFormatted; | ||||
|         } | ||||
| static Service::FS::ArchiveIdCode ExtSaveDataTypeToArchiveID(ExtSaveDataType type) { | ||||
|     switch (type) { | ||||
|     case FileSys::ExtSaveDataType::Normal: | ||||
|         return Service::FS::ArchiveIdCode::ExtSaveData; | ||||
|     case FileSys::ExtSaveDataType::Shared: | ||||
|         return Service::FS::ArchiveIdCode::SharedExtSaveData; | ||||
|     case FileSys::ExtSaveDataType::Boss: | ||||
|         return Service::FS::ArchiveIdCode::BossExtSaveData; | ||||
|     default: | ||||
|         return Service::FS::ArchiveIdCode::ExtSaveData; | ||||
|     } | ||||
|     std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<ExtSaveDataDelayGenerator>(); | ||||
|     return std::make_unique<ExtSaveDataArchive>(fullpath, std::move(delay_generator)); | ||||
| } | ||||
| 
 | ||||
| Result ArchiveFactory_ExtSaveData::Format(const Path& path, | ||||
|                                           const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                           u64 program_id) { | ||||
|     auto corrected_path = GetCorrectedPath(path); | ||||
| 
 | ||||
|     // These folders are always created with the ExtSaveData
 | ||||
|     std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/"; | ||||
|     std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/"; | ||||
|     FileUtil::CreateFullPath(user_path); | ||||
|     FileUtil::CreateFullPath(boss_path); | ||||
| 
 | ||||
|     // Write the format metadata
 | ||||
|     std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "metadata"; | ||||
|     FileUtil::IOFile file(metadata_path, "wb"); | ||||
| 
 | ||||
|     if (!file.IsOpen()) { | ||||
|         // TODO(Subv): Find the correct error code
 | ||||
|         return ResultUnknown; | ||||
| static Core::PerfStats::PerfArticEventBits ExtSaveDataTypeToPerfArtic(ExtSaveDataType type) { | ||||
|     switch (type) { | ||||
|     case FileSys::ExtSaveDataType::Normal: | ||||
|         return Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA; | ||||
|     case FileSys::ExtSaveDataType::Shared: | ||||
|         return Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA; | ||||
|     case FileSys::ExtSaveDataType::Boss: | ||||
|         return Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA; | ||||
|     default: | ||||
|         return Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     file.WriteBytes(&format_info, sizeof(format_info)); | ||||
|     return ResultSuccess; | ||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path, | ||||
|                                                                             u64 program_id) { | ||||
|     if (IsUsingArtic()) { | ||||
|         EnsureCacheCreated(); | ||||
|         return ArticArchive::Open(artic_client, ExtSaveDataTypeToArchiveID(type), path, | ||||
|                                   ExtSaveDataTypeToPerfArtic(type), *this, | ||||
|                                   type != FileSys::ExtSaveDataType::Normal); | ||||
|     } else { | ||||
|         const auto directory = type == ExtSaveDataType::Boss ? "boss/" : "user/"; | ||||
|         const auto fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + directory; | ||||
|         if (!FileUtil::Exists(fullpath)) { | ||||
|             // TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
 | ||||
|             // ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
 | ||||
|             if (type != ExtSaveDataType::Shared) { | ||||
|                 return ResultNotFoundInvalidState; | ||||
|             } else { | ||||
|                 return ResultNotFormatted; | ||||
|             } | ||||
|         } | ||||
|         std::unique_ptr<DelayGenerator> delay_generator = | ||||
|             std::make_unique<ExtSaveDataDelayGenerator>(); | ||||
|         return std::make_unique<ExtSaveDataArchive>(fullpath, std::move(delay_generator)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Result ArchiveFactory_ExtSaveData::FormatAsExtData(const Path& path, | ||||
|                                                    const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                                    u8 unknown, u64 program_id, u64 total_size, | ||||
|                                                    std::span<const u8> icon) { | ||||
|     if (IsUsingArtic()) { | ||||
|         ExtSaveDataArchivePath path_data; | ||||
|         std::memcpy(&path_data, path.AsBinary().data(), sizeof(path_data)); | ||||
| 
 | ||||
|         Service::FS::ExtSaveDataInfo artic_extdata_path; | ||||
| 
 | ||||
|         artic_extdata_path.media_type = static_cast<u8>(path_data.media_type); | ||||
|         artic_extdata_path.unknown = unknown; | ||||
|         artic_extdata_path.save_id_low = path_data.save_low; | ||||
|         artic_extdata_path.save_id_high = path_data.save_high; | ||||
| 
 | ||||
|         auto req = artic_client->NewRequest("FSUSER_CreateExtSaveData"); | ||||
| 
 | ||||
|         req.AddParameterBuffer(&artic_extdata_path, sizeof(artic_extdata_path)); | ||||
|         req.AddParameterU32(format_info.number_directories); | ||||
|         req.AddParameterU32(format_info.number_files); | ||||
|         req.AddParameterU64(total_size); | ||||
|         req.AddParameterBuffer(icon.data(), icon.size()); | ||||
| 
 | ||||
|         return ArticArchive::RespResult(artic_client->Send(req)); | ||||
|     } else { | ||||
|         auto corrected_path = GetCorrectedPath(path); | ||||
| 
 | ||||
|         // These folders are always created with the ExtSaveData
 | ||||
|         std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/"; | ||||
|         std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/"; | ||||
|         FileUtil::CreateFullPath(user_path); | ||||
|         FileUtil::CreateFullPath(boss_path); | ||||
| 
 | ||||
|         // Write the format metadata
 | ||||
|         std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "metadata"; | ||||
|         FileUtil::IOFile file(metadata_path, "wb"); | ||||
| 
 | ||||
|         if (!file.IsOpen()) { | ||||
|             // TODO(Subv): Find the correct error code
 | ||||
|             return ResultUnknown; | ||||
|         } | ||||
| 
 | ||||
|         file.WriteBytes(&format_info, sizeof(format_info)); | ||||
| 
 | ||||
|         FileUtil::IOFile icon_file(FileSys::GetExtSaveDataPath(GetMountPoint(), path) + "icon", | ||||
|                                    "wb"); | ||||
|         icon_file.WriteBytes(icon.data(), icon.size()); | ||||
| 
 | ||||
|         return ResultSuccess; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Result ArchiveFactory_ExtSaveData::DeleteExtData(Service::FS::MediaType media_type, u8 unknown, | ||||
|                                                  u32 high, u32 low) { | ||||
|     if (IsUsingArtic()) { | ||||
|         Service::FS::ExtSaveDataInfo artic_extdata_path; | ||||
| 
 | ||||
|         artic_extdata_path.media_type = static_cast<u8>(media_type); | ||||
|         artic_extdata_path.unknown = unknown; | ||||
|         artic_extdata_path.save_id_low = low; | ||||
|         artic_extdata_path.save_id_high = high; | ||||
| 
 | ||||
|         auto req = artic_client->NewRequest("FSUSER_DeleteExtSaveData"); | ||||
| 
 | ||||
|         req.AddParameterBuffer(&artic_extdata_path, sizeof(artic_extdata_path)); | ||||
| 
 | ||||
|         return ArticArchive::RespResult(artic_client->Send(req)); | ||||
|     } else { | ||||
|         // Construct the binary path to the archive first
 | ||||
|         FileSys::Path path = | ||||
|             FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low); | ||||
| 
 | ||||
|         std::string media_type_directory; | ||||
|         if (media_type == Service::FS::MediaType::NAND) { | ||||
|             media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); | ||||
|         } else if (media_type == Service::FS::MediaType::SDMC) { | ||||
|             media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); | ||||
|         } else { | ||||
|             LOG_ERROR(Service_FS, "Unsupported media type {}", media_type); | ||||
|             return ResultUnknown; // TODO(Subv): Find the right error code
 | ||||
|         } | ||||
| 
 | ||||
|         // Delete all directories (/user, /boss) and the icon file.
 | ||||
|         std::string base_path = FileSys::GetExtDataContainerPath( | ||||
|             media_type_directory, media_type == Service::FS::MediaType::NAND); | ||||
|         std::string extsavedata_path = FileSys::GetExtSaveDataPath(base_path, path); | ||||
|         if (FileUtil::Exists(extsavedata_path) && !FileUtil::DeleteDirRecursively(extsavedata_path)) | ||||
|             return ResultUnknown; // TODO(Subv): Find the right error code
 | ||||
|         return ResultSuccess; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Path& path, | ||||
|                                                                        u64 program_id) const { | ||||
|     std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata"; | ||||
|     FileUtil::IOFile file(metadata_path, "rb"); | ||||
|     if (IsUsingArtic()) { | ||||
|         auto req = artic_client->NewRequest("FSUSER_GetFormatInfo"); | ||||
| 
 | ||||
|     if (!file.IsOpen()) { | ||||
|         LOG_ERROR(Service_FS, "Could not open metadata information for archive"); | ||||
|         // TODO(Subv): Verify error code
 | ||||
|         return ResultNotFormatted; | ||||
|         req.AddParameterS32(static_cast<u32>(ExtSaveDataTypeToArchiveID(type))); | ||||
|         auto path_artic = ArticArchive::BuildFSPath(path); | ||||
|         req.AddParameterBuffer(path_artic.data(), path_artic.size()); | ||||
| 
 | ||||
|         auto resp = artic_client->Send(req); | ||||
|         Result res = ArticArchive::RespResult(resp); | ||||
|         if (R_FAILED(res)) { | ||||
|             return res; | ||||
|         } | ||||
| 
 | ||||
|         auto info_buf = resp->GetResponseBuffer(0); | ||||
|         if (!info_buf.has_value() || info_buf->second != sizeof(ArchiveFormatInfo)) { | ||||
|             return ResultUnknown; | ||||
|         } | ||||
| 
 | ||||
|         ArchiveFormatInfo info; | ||||
|         memcpy(&info, info_buf->first, sizeof(info)); | ||||
|         return info; | ||||
|     } else { | ||||
|         std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata"; | ||||
|         FileUtil::IOFile file(metadata_path, "rb"); | ||||
| 
 | ||||
|         if (!file.IsOpen()) { | ||||
|             LOG_ERROR(Service_FS, "Could not open metadata information for archive"); | ||||
|             // TODO(Subv): Verify error code
 | ||||
|             return ResultNotFormatted; | ||||
|         } | ||||
| 
 | ||||
|         ArchiveFormatInfo info = {}; | ||||
|         file.ReadBytes(&info, sizeof(info)); | ||||
|         return info; | ||||
|     } | ||||
| 
 | ||||
|     ArchiveFormatInfo info = {}; | ||||
|     file.ReadBytes(&info, sizeof(info)); | ||||
|     return info; | ||||
| } | ||||
| 
 | ||||
| void ArchiveFactory_ExtSaveData::WriteIcon(const Path& path, std::span<const u8> icon) { | ||||
|     std::string game_path = FileSys::GetExtSaveDataPath(GetMountPoint(), path); | ||||
|     FileUtil::IOFile icon_file(game_path + "icon", "wb"); | ||||
|     icon_file.WriteBytes(icon.data(), icon.size()); | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataDelayGenerator) | ||||
|  |  | |||
|  | @ -11,7 +11,10 @@ | |||
| #include <boost/serialization/string.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/archive_backend.h" | ||||
| #include "core/file_sys/artic_cache.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "network/artic_base/artic_base_client.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
|  | @ -22,7 +25,7 @@ enum class ExtSaveDataType { | |||
| }; | ||||
| 
 | ||||
| /// File system interface to the ExtSaveData archive
 | ||||
| class ArchiveFactory_ExtSaveData final : public ArchiveFactory { | ||||
| class ArchiveFactory_ExtSaveData final : public ArchiveFactory, public ArticCacheProvider { | ||||
| public: | ||||
|     ArchiveFactory_ExtSaveData(const std::string& mount_point, ExtSaveDataType type_); | ||||
| 
 | ||||
|  | @ -31,21 +34,34 @@ public: | |||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   u64 program_id) override; | ||||
| 
 | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; | ||||
| 
 | ||||
|     bool IsSlow() override { | ||||
|         return IsUsingArtic(); | ||||
|     } | ||||
| 
 | ||||
|     const std::string& GetMountPoint() const { | ||||
|         return mount_point; | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Writes the SMDH icon of the ExtSaveData to file | ||||
|      * @param path Path of this ExtSaveData | ||||
|      * @param icon_data Binary data of the icon | ||||
|      * @param icon_size Size of the icon data | ||||
|      */ | ||||
|     void WriteIcon(const Path& path, std::span<const u8> icon); | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                   u32 directory_buckets, u32 file_buckets) override { | ||||
|         return UnimplementedFunction(ErrorModule::FS); | ||||
|     }; | ||||
| 
 | ||||
|     Result FormatAsExtData(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                            u8 unknown, u64 program_id, u64 total_size, std::span<const u8> icon); | ||||
| 
 | ||||
|     Result DeleteExtData(Service::FS::MediaType media_type, u8 unknown, u32 high, u32 low); | ||||
| 
 | ||||
|     void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) { | ||||
|         artic_client = client; | ||||
|     } | ||||
| 
 | ||||
|     bool IsUsingArtic() const { | ||||
|         return artic_client.get() != nullptr; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     /// Type of ext save data archive being accessed.
 | ||||
|  | @ -61,10 +77,13 @@ private: | |||
|     /// Returns a path with the correct SaveIdHigh value for Shared extdata paths.
 | ||||
|     Path GetCorrectedPath(const Path& path); | ||||
| 
 | ||||
|     std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr; | ||||
| 
 | ||||
|     ArchiveFactory_ExtSaveData() = default; | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<ArchiveFactory>(*this); | ||||
|         ar& boost::serialization::base_object<ArticCacheProvider>(*this); | ||||
|         ar& type; | ||||
|         ar& mount_point; | ||||
|     } | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ | |||
| #include "common/string_util.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/core.h" | ||||
| #include "core/file_sys/archive_artic.h" | ||||
| #include "core/file_sys/archive_ncch.h" | ||||
| #include "core/file_sys/errors.h" | ||||
| #include "core/file_sys/ivfc_archive.h" | ||||
|  | @ -69,8 +70,9 @@ Path MakeNCCHFilePath(NCCHFileOpenType open_type, u32 content_index, NCCHFilePat | |||
|     return FileSys::Path(std::move(file)); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path, | ||||
|                                                               const Mode& mode) const { | ||||
| ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path, const Mode& mode, | ||||
|                                                               u32 attributes) { | ||||
| 
 | ||||
|     if (path.GetType() != LowPathType::Binary) { | ||||
|         LOG_ERROR(Service_FS, "Path need to be Binary"); | ||||
|         return ResultInvalidPath; | ||||
|  | @ -207,14 +209,14 @@ Result NCCHArchive::DeleteDirectoryRecursively(const Path& path) const { | |||
|     return ResultUnknown; | ||||
| } | ||||
| 
 | ||||
| Result NCCHArchive::CreateFile(const Path& path, u64 size) const { | ||||
| Result NCCHArchive::CreateFile(const Path& path, u64 size, u32 attributes) const { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to create a file in an NCCH archive ({}).", GetName()); | ||||
|     // TODO: Verify error code
 | ||||
|     return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, | ||||
|                   ErrorLevel::Permanent); | ||||
| } | ||||
| 
 | ||||
| Result NCCHArchive::CreateDirectory(const Path& path) const { | ||||
| Result NCCHArchive::CreateDirectory(const Path& path, u32 attributes) const { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to create a directory in an NCCH archive ({}).", GetName()); | ||||
|     // TODO(wwylele): Use correct error code
 | ||||
|     return ResultUnknown; | ||||
|  | @ -226,7 +228,7 @@ Result NCCHArchive::RenameDirectory(const Path& src_path, const Path& dest_path) | |||
|     return ResultUnknown; | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> NCCHArchive::OpenDirectory(const Path& path) const { | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> NCCHArchive::OpenDirectory(const Path& path) { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to open a directory within an NCCH archive ({}).", | ||||
|                  GetName().c_str()); | ||||
|     // TODO(shinyquagsire23): Use correct error code
 | ||||
|  | @ -255,7 +257,7 @@ ResultVal<std::size_t> NCCHFile::Read(const u64 offset, const std::size_t length | |||
| } | ||||
| 
 | ||||
| ResultVal<std::size_t> NCCHFile::Write(const u64 offset, const std::size_t length, const bool flush, | ||||
|                                        const u8* buffer) { | ||||
|                                        const bool update_timestamp, const u8* buffer) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to write to NCCH file"); | ||||
|     // TODO(shinyquagsire23): Find error code
 | ||||
|     return 0ULL; | ||||
|  | @ -274,6 +276,13 @@ ArchiveFactory_NCCH::ArchiveFactory_NCCH() {} | |||
| 
 | ||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path, | ||||
|                                                                      u64 program_id) { | ||||
| 
 | ||||
|     if (IsUsingArtic()) { | ||||
|         EnsureCacheCreated(); | ||||
|         return ArticArchive::Open(artic_client, Service::FS::ArchiveIdCode::NCCH, path, | ||||
|                                   Core::PerfStats::PerfArticEventBits::NONE, *this, false); | ||||
|     } | ||||
| 
 | ||||
|     if (path.GetType() != LowPathType::Binary) { | ||||
|         LOG_ERROR(Service_FS, "Path need to be Binary"); | ||||
|         return ResultInvalidPath; | ||||
|  | @ -293,7 +302,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& | |||
| } | ||||
| 
 | ||||
| Result ArchiveFactory_NCCH::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                    u64 program_id) { | ||||
|                                    u64 program_id, u32 directory_buckets, u32 file_buckets) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to format a NCCH archive."); | ||||
|     // TODO: Verify error code
 | ||||
|     return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, | ||||
|  |  | |||
|  | @ -11,8 +11,10 @@ | |||
| #include <boost/serialization/export.hpp> | ||||
| #include <boost/serialization/vector.hpp> | ||||
| #include "core/file_sys/archive_backend.h" | ||||
| #include "core/file_sys/artic_cache.h" | ||||
| #include "core/file_sys/file_backend.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "network/artic_base/artic_base_client.h" | ||||
| 
 | ||||
| namespace Service::FS { | ||||
| enum class MediaType : u32; | ||||
|  | @ -48,16 +50,16 @@ public: | |||
|         return "NCCHArchive"; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||
|                                                      const Mode& mode) const override; | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode, | ||||
|                                                      u32 attributes) override; | ||||
|     Result DeleteFile(const Path& path) const override; | ||||
|     Result RenameFile(const Path& src_path, const Path& dest_path) const override; | ||||
|     Result DeleteDirectory(const Path& path) const override; | ||||
|     Result DeleteDirectoryRecursively(const Path& path) const override; | ||||
|     Result CreateFile(const Path& path, u64 size) const override; | ||||
|     Result CreateDirectory(const Path& path) const override; | ||||
|     Result CreateFile(const Path& path, u64 size, u32 attributes) const override; | ||||
|     Result CreateDirectory(const Path& path, u32 attributes) const override; | ||||
|     Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override; | ||||
|     u64 GetFreeBytes() const override; | ||||
| 
 | ||||
| protected: | ||||
|  | @ -82,11 +84,11 @@ public: | |||
|     explicit NCCHFile(std::vector<u8> buffer, std::unique_ptr<DelayGenerator> delay_generator_); | ||||
| 
 | ||||
|     ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override; | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override; | ||||
|     u64 GetSize() const override; | ||||
|     bool SetSize(u64 size) const override; | ||||
|     bool Close() const override { | ||||
|     bool Close() override { | ||||
|         return false; | ||||
|     } | ||||
|     void Flush() const override {} | ||||
|  | @ -105,7 +107,7 @@ private: | |||
| }; | ||||
| 
 | ||||
| /// File system interface to the NCCH archive
 | ||||
| class ArchiveFactory_NCCH final : public ArchiveFactory { | ||||
| class ArchiveFactory_NCCH final : public ArchiveFactory, public ArticCacheProvider { | ||||
| public: | ||||
|     explicit ArchiveFactory_NCCH(); | ||||
| 
 | ||||
|  | @ -114,14 +116,29 @@ public: | |||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                   u32 directory_buckets, u32 file_buckets) override; | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; | ||||
| 
 | ||||
|     bool IsSlow() override { | ||||
|         return IsUsingArtic(); | ||||
|     } | ||||
| 
 | ||||
|     void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) { | ||||
|         artic_client = client; | ||||
|     } | ||||
| 
 | ||||
|     bool IsUsingArtic() const { | ||||
|         return artic_client.get() != nullptr; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<ArchiveFactory>(*this); | ||||
|         ar& boost::serialization::base_object<ArticCacheProvider>(*this); | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
|  |  | |||
|  | @ -75,12 +75,14 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_OtherSaveDataPermitted | |||
|         return ResultGamecardNotInserted; | ||||
|     } | ||||
| 
 | ||||
|     return sd_savedata_source->Open(program_id); | ||||
|     return sd_savedata_source->Open(Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path, | ||||
|                                     program_id); | ||||
| } | ||||
| 
 | ||||
| Result ArchiveFactory_OtherSaveDataPermitted::Format(const Path& path, | ||||
|                                                      const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                                      u64 program_id) { | ||||
|                                                      u64 program_id, u32 directory_buckets, | ||||
|                                                      u32 file_buckets) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to format a OtherSaveDataPermitted archive."); | ||||
|     return ResultInvalidPath; | ||||
| } | ||||
|  | @ -96,7 +98,8 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataPermitted::GetFormatInf | |||
|         return ResultGamecardNotInserted; | ||||
|     } | ||||
| 
 | ||||
|     return sd_savedata_source->GetFormatInfo(program_id); | ||||
|     return sd_savedata_source->GetFormatInfo( | ||||
|         program_id, Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path); | ||||
| } | ||||
| 
 | ||||
| ArchiveFactory_OtherSaveDataGeneral::ArchiveFactory_OtherSaveDataGeneral( | ||||
|  | @ -114,12 +117,14 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_OtherSaveDataGeneral:: | |||
|         return ResultGamecardNotInserted; | ||||
|     } | ||||
| 
 | ||||
|     return sd_savedata_source->Open(program_id); | ||||
|     return sd_savedata_source->Open(Service::FS::ArchiveIdCode::OtherSaveDataGeneral, path, | ||||
|                                     program_id); | ||||
| } | ||||
| 
 | ||||
| Result ArchiveFactory_OtherSaveDataGeneral::Format(const Path& path, | ||||
|                                                    const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                                    u64 /*client_program_id*/) { | ||||
|                                                    u64 /*client_program_id*/, u32 directory_buckets, | ||||
|                                                    u32 file_buckets) { | ||||
|     MediaType media_type; | ||||
|     u64 program_id; | ||||
|     CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path)); | ||||
|  | @ -129,7 +134,9 @@ Result ArchiveFactory_OtherSaveDataGeneral::Format(const Path& path, | |||
|         return ResultGamecardNotInserted; | ||||
|     } | ||||
| 
 | ||||
|     return sd_savedata_source->Format(program_id, format_info); | ||||
|     return sd_savedata_source->Format(program_id, format_info, | ||||
|                                       Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path, | ||||
|                                       directory_buckets, file_buckets); | ||||
| } | ||||
| 
 | ||||
| ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataGeneral::GetFormatInfo( | ||||
|  | @ -143,7 +150,8 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataGeneral::GetFormatInfo( | |||
|         return ResultGamecardNotInserted; | ||||
|     } | ||||
| 
 | ||||
|     return sd_savedata_source->GetFormatInfo(program_id); | ||||
|     return sd_savedata_source->GetFormatInfo( | ||||
|         program_id, Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path); | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  |  | |||
|  | @ -22,10 +22,14 @@ public: | |||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                   u32 directory_buckets, u32 file_buckets) override; | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; | ||||
| 
 | ||||
|     bool IsSlow() override { | ||||
|         return sd_savedata_source->IsUsingArtic(); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source; | ||||
| 
 | ||||
|  | @ -49,8 +53,8 @@ public: | |||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                   u32 directory_buckets, u32 file_buckets) override; | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; | ||||
| 
 | ||||
| private: | ||||
|  |  | |||
|  | @ -18,18 +18,20 @@ ArchiveFactory_SaveData::ArchiveFactory_SaveData( | |||
| 
 | ||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const Path& path, | ||||
|                                                                          u64 program_id) { | ||||
|     return sd_savedata_source->Open(program_id); | ||||
|     return sd_savedata_source->Open(Service::FS::ArchiveIdCode::SaveData, path, program_id); | ||||
| } | ||||
| 
 | ||||
| Result ArchiveFactory_SaveData::Format(const Path& path, | ||||
|                                        const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                        u64 program_id) { | ||||
|     return sd_savedata_source->Format(program_id, format_info); | ||||
|                                        u64 program_id, u32 directory_buckets, u32 file_buckets) { | ||||
|     return sd_savedata_source->Format(program_id, format_info, Service::FS::ArchiveIdCode::SaveData, | ||||
|                                       path, directory_buckets, file_buckets); | ||||
| } | ||||
| 
 | ||||
| ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveData::GetFormatInfo(const Path& path, | ||||
|                                                                     u64 program_id) const { | ||||
|     return sd_savedata_source->GetFormatInfo(program_id); | ||||
|     return sd_savedata_source->GetFormatInfo(program_id, Service::FS::ArchiveIdCode::SaveData, | ||||
|                                              path); | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  |  | |||
|  | @ -20,11 +20,15 @@ public: | |||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                   u32 directory_buckets, u32 file_buckets) override; | ||||
| 
 | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; | ||||
| 
 | ||||
|     bool IsSlow() override { | ||||
|         return sd_savedata_source->IsUsingArtic(); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source; | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,8 +43,8 @@ public: | |||
|     SERIALIZE_DELAY_GENERATOR | ||||
| }; | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path, | ||||
|                                                               const Mode& mode) const { | ||||
| ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path, const Mode& mode, | ||||
|                                                               u32 attributes) { | ||||
|     Mode modified_mode; | ||||
|     modified_mode.hex = mode.hex; | ||||
| 
 | ||||
|  | @ -222,7 +222,7 @@ Result SDMCArchive::DeleteDirectoryRecursively(const Path& path) const { | |||
|         path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); | ||||
| } | ||||
| 
 | ||||
| Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const { | ||||
| Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size, u32 attributes) const { | ||||
|     const PathParser path_parser(path); | ||||
| 
 | ||||
|     if (!path_parser.IsValid()) { | ||||
|  | @ -267,7 +267,7 @@ Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const { | |||
|                   ErrorLevel::Info); | ||||
| } | ||||
| 
 | ||||
| Result SDMCArchive::CreateDirectory(const Path& path) const { | ||||
| Result SDMCArchive::CreateDirectory(const Path& path, u32 attributes) const { | ||||
|     const PathParser path_parser(path); | ||||
| 
 | ||||
|     if (!path_parser.IsValid()) { | ||||
|  | @ -331,7 +331,7 @@ Result SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) | |||
|                   ErrorSummary::NothingHappened, ErrorLevel::Status); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) const { | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) { | ||||
|     const PathParser path_parser(path); | ||||
| 
 | ||||
|     if (!path_parser.IsValid()) { | ||||
|  | @ -392,7 +392,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& | |||
| } | ||||
| 
 | ||||
| Result ArchiveFactory_SDMC::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                    u64 program_id) { | ||||
|                                    u64 program_id, u32 directory_buckets, u32 file_buckets) { | ||||
|     // This is kind of an undesirable operation, so let's just ignore it. :)
 | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  |  | |||
|  | @ -27,16 +27,16 @@ public: | |||
|         return "SDMCArchive: " + mount_point; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||
|                                                      const Mode& mode) const override; | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode, | ||||
|                                                      u32 attributes) override; | ||||
|     Result DeleteFile(const Path& path) const override; | ||||
|     Result RenameFile(const Path& src_path, const Path& dest_path) const override; | ||||
|     Result DeleteDirectory(const Path& path) const override; | ||||
|     Result DeleteDirectoryRecursively(const Path& path) const override; | ||||
|     Result CreateFile(const Path& path, u64 size) const override; | ||||
|     Result CreateDirectory(const Path& path) const override; | ||||
|     Result CreateFile(const Path& path, u64 size, u32 attributes) const override; | ||||
|     Result CreateDirectory(const Path& path, u32 attributes) const override; | ||||
|     Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override; | ||||
|     u64 GetFreeBytes() const override; | ||||
| 
 | ||||
| protected: | ||||
|  | @ -68,8 +68,8 @@ public: | |||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                   u32 directory_buckets, u32 file_buckets) override; | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; | ||||
| 
 | ||||
| private: | ||||
|  |  | |||
|  | @ -41,7 +41,8 @@ public: | |||
| }; | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path, | ||||
|                                                                        const Mode& mode) const { | ||||
|                                                                        const Mode& mode, | ||||
|                                                                        u32 attributes) { | ||||
|     if (mode.read_flag) { | ||||
|         LOG_ERROR(Service_FS, "Read flag is not supported"); | ||||
|         return ResultInvalidReadFlag; | ||||
|  | @ -49,8 +50,7 @@ ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Pat | |||
|     return SDMCArchive::OpenFileBase(path, mode); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory( | ||||
|     const Path& path) const { | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory(const Path& path) { | ||||
|     LOG_ERROR(Service_FS, "Not supported"); | ||||
|     return ResultUnsupportedOpenFlags; | ||||
| } | ||||
|  | @ -83,7 +83,8 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(co | |||
| 
 | ||||
| Result ArchiveFactory_SDMCWriteOnly::Format(const Path& path, | ||||
|                                             const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                             u64 program_id) { | ||||
|                                             u64 program_id, u32 directory_buckets, | ||||
|                                             u32 file_buckets) { | ||||
|     // TODO(wwylele): hwtest this
 | ||||
|     LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive."); | ||||
|     return ResultUnknown; | ||||
|  |  | |||
|  | @ -24,10 +24,10 @@ public: | |||
|         return "SDMCWriteOnlyArchive: " + mount_point; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||
|                                                      const Mode& mode) const override; | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode, | ||||
|                                                      u32 attributes) override; | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override; | ||||
| 
 | ||||
| private: | ||||
|     SDMCWriteOnlyArchive() = default; | ||||
|  | @ -54,8 +54,8 @@ public: | |||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                   u32 directory_buckets, u32 file_buckets) override; | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; | ||||
| 
 | ||||
| private: | ||||
|  |  | |||
|  | @ -51,7 +51,7 @@ public: | |||
|         return data->size(); | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override { | ||||
|         LOG_ERROR(Service_FS, "The file is read-only!"); | ||||
|         return ResultUnsupportedOpenFlags; | ||||
|  | @ -65,7 +65,7 @@ public: | |||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool Close() const override { | ||||
|     bool Close() override { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|  | @ -94,7 +94,8 @@ public: | |||
|         return "SelfNCCHArchive"; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode&) const override { | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode&, | ||||
|                                                      u32 attributes) override { | ||||
|         // Note: SelfNCCHArchive doesn't check the open mode.
 | ||||
| 
 | ||||
|         if (path.GetType() != LowPathType::Binary) { | ||||
|  | @ -154,12 +155,12 @@ public: | |||
|         return ResultUnsupportedOpenFlags; | ||||
|     } | ||||
| 
 | ||||
|     Result CreateFile(const Path& path, u64 size) const override { | ||||
|     Result CreateFile(const Path& path, u64 size, u32 attributes) const override { | ||||
|         LOG_ERROR(Service_FS, "Unsupported"); | ||||
|         return ResultUnsupportedOpenFlags; | ||||
|     } | ||||
| 
 | ||||
|     Result CreateDirectory(const Path& path) const override { | ||||
|     Result CreateDirectory(const Path& path, u32 attributes) const override { | ||||
|         LOG_ERROR(Service_FS, "Unsupported"); | ||||
|         return ResultUnsupportedOpenFlags; | ||||
|     } | ||||
|  | @ -169,7 +170,7 @@ public: | |||
|         return ResultUnsupportedOpenFlags; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override { | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override { | ||||
|         LOG_ERROR(Service_FS, "Unsupported"); | ||||
|         return ResultUnsupportedOpenFlags; | ||||
|     } | ||||
|  | @ -297,7 +298,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const P | |||
| } | ||||
| 
 | ||||
| Result ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&, | ||||
|                                        u64 program_id) { | ||||
|                                        u64 program_id, u32 directory_buckets, u32 file_buckets) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to format a SelfNCCH archive."); | ||||
|     return ResultInvalidPath; | ||||
| } | ||||
|  |  | |||
|  | @ -50,8 +50,8 @@ public: | |||
|         return "SelfNCCH"; | ||||
|     } | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                   u32 directory_buckets, u32 file_buckets) override; | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; | ||||
| 
 | ||||
| private: | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include "common/archives.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/file_sys/archive_artic.h" | ||||
| #include "core/file_sys/archive_source_sd_savedata.h" | ||||
| #include "core/file_sys/errors.h" | ||||
| #include "core/file_sys/savedata_archive.h" | ||||
|  | @ -40,49 +41,101 @@ ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_direc | |||
|     LOG_DEBUG(Service_FS, "Directory {} set as SaveData.", mount_point); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 program_id) { | ||||
|     std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); | ||||
|     if (!FileUtil::Exists(concrete_mount_point)) { | ||||
|         // When a SaveData archive is created for the first time, it is not yet formatted and the
 | ||||
|         // save file/directory structure expected by the game has not yet been initialized.
 | ||||
|         // Returning the NotFormatted error code will signal the game to provision the SaveData
 | ||||
|         // archive with the files and folders that it expects.
 | ||||
|         return ResultNotFormatted; | ||||
|     } | ||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open( | ||||
|     Service::FS::ArchiveIdCode archive_id, const Path& path, u64 program_id) { | ||||
|     if (IsUsingArtic()) { | ||||
|         EnsureCacheCreated(); | ||||
|         return ArticArchive::Open(artic_client, archive_id, path, | ||||
|                                   Core::PerfStats::PerfArticEventBits::ARTIC_SAVE_DATA, *this, | ||||
|                                   archive_id != Service::FS::ArchiveIdCode::SaveData); | ||||
|     } else { | ||||
|         std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); | ||||
|         if (!FileUtil::Exists(concrete_mount_point)) { | ||||
|             // When a SaveData archive is created for the first time, it is not yet formatted and
 | ||||
|             // the save file/directory structure expected by the game has not yet been initialized.
 | ||||
|             // Returning the NotFormatted error code will signal the game to provision the SaveData
 | ||||
|             // archive with the files and folders that it expects.
 | ||||
|             return ResultNotFormatted; | ||||
|         } | ||||
| 
 | ||||
|     return std::make_unique<SaveDataArchive>(std::move(concrete_mount_point)); | ||||
|         return std::make_unique<SaveDataArchive>(std::move(concrete_mount_point)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Result ArchiveSource_SDSaveData::Format(u64 program_id, | ||||
|                                         const FileSys::ArchiveFormatInfo& format_info) { | ||||
|     std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); | ||||
|     FileUtil::DeleteDirRecursively(concrete_mount_point); | ||||
|     FileUtil::CreateFullPath(concrete_mount_point); | ||||
|                                         const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                         Service::FS::ArchiveIdCode archive_id, const Path& path, | ||||
|                                         u32 directory_buckets, u32 file_buckets) { | ||||
|     if (IsUsingArtic()) { | ||||
|         ClearAllCache(); | ||||
|         auto req = artic_client->NewRequest("FSUSER_FormatSaveData"); | ||||
| 
 | ||||
|     // Write the format metadata
 | ||||
|     std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); | ||||
|     FileUtil::IOFile file(metadata_path, "wb"); | ||||
|         req.AddParameterS32(static_cast<u32>(archive_id)); | ||||
|         auto artic_path = ArticArchive::BuildFSPath(path); | ||||
|         req.AddParameterBuffer(artic_path.data(), artic_path.size()); | ||||
|         req.AddParameterU32(format_info.total_size / 512); | ||||
|         req.AddParameterU32(format_info.number_directories); | ||||
|         req.AddParameterU32(format_info.number_files); | ||||
|         req.AddParameterU32(directory_buckets); | ||||
|         req.AddParameterU32(file_buckets); | ||||
|         req.AddParameterU8(format_info.duplicate_data); | ||||
| 
 | ||||
|     if (file.IsOpen()) { | ||||
|         file.WriteBytes(&format_info, sizeof(format_info)); | ||||
|         auto resp = artic_client->Send(req); | ||||
|         return ArticArchive::RespResult(resp); | ||||
|     } else { | ||||
|         std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); | ||||
|         FileUtil::DeleteDirRecursively(concrete_mount_point); | ||||
|         FileUtil::CreateFullPath(concrete_mount_point); | ||||
| 
 | ||||
|         // Write the format metadata
 | ||||
|         std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); | ||||
|         FileUtil::IOFile file(metadata_path, "wb"); | ||||
| 
 | ||||
|         if (file.IsOpen()) { | ||||
|             file.WriteBytes(&format_info, sizeof(format_info)); | ||||
|             return ResultSuccess; | ||||
|         } | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|     return ResultSuccess; | ||||
| } | ||||
| 
 | ||||
| ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(u64 program_id) const { | ||||
|     std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); | ||||
|     FileUtil::IOFile file(metadata_path, "rb"); | ||||
| ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo( | ||||
|     u64 program_id, Service::FS::ArchiveIdCode archive_id, const Path& path) const { | ||||
|     if (IsUsingArtic()) { | ||||
|         auto req = artic_client->NewRequest("FSUSER_GetFormatInfo"); | ||||
| 
 | ||||
|     if (!file.IsOpen()) { | ||||
|         LOG_ERROR(Service_FS, "Could not open metadata information for archive"); | ||||
|         // TODO(Subv): Verify error code
 | ||||
|         return ResultNotFormatted; | ||||
|         req.AddParameterS32(static_cast<u32>(archive_id)); | ||||
|         auto path_artic = ArticArchive::BuildFSPath(path); | ||||
|         req.AddParameterBuffer(path_artic.data(), path_artic.size()); | ||||
| 
 | ||||
|         auto resp = artic_client->Send(req); | ||||
|         Result res = ArticArchive::RespResult(resp); | ||||
|         if (R_FAILED(res)) { | ||||
|             return res; | ||||
|         } | ||||
| 
 | ||||
|         auto info_buf = resp->GetResponseBuffer(0); | ||||
|         if (!info_buf.has_value() || info_buf->second != sizeof(ArchiveFormatInfo)) { | ||||
|             return ResultUnknown; | ||||
|         } | ||||
| 
 | ||||
|         ArchiveFormatInfo info; | ||||
|         memcpy(&info, info_buf->first, sizeof(info)); | ||||
|         return info; | ||||
|     } else { | ||||
|         std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); | ||||
|         FileUtil::IOFile file(metadata_path, "rb"); | ||||
| 
 | ||||
|         if (!file.IsOpen()) { | ||||
|             LOG_ERROR(Service_FS, "Could not open metadata information for archive"); | ||||
|             // TODO(Subv): Verify error code
 | ||||
|             return ResultNotFormatted; | ||||
|         } | ||||
| 
 | ||||
|         ArchiveFormatInfo info = {}; | ||||
|         file.ReadBytes(&info, sizeof(info)); | ||||
|         return info; | ||||
|     } | ||||
| 
 | ||||
|     ArchiveFormatInfo info = {}; | ||||
|     file.ReadBytes(&info, sizeof(info)); | ||||
|     return info; | ||||
| } | ||||
| 
 | ||||
| std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point, | ||||
|  |  | |||
|  | @ -9,27 +9,48 @@ | |||
| #include <boost/serialization/export.hpp> | ||||
| #include <boost/serialization/string.hpp> | ||||
| #include "core/file_sys/archive_backend.h" | ||||
| #include "core/file_sys/artic_cache.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "network/artic_base/artic_base_client.h" | ||||
| 
 | ||||
| namespace Service::FS { | ||||
| enum class ArchiveIdCode : u32; | ||||
| } // namespace Service::FS
 | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| /// A common source of SD save data archive
 | ||||
| class ArchiveSource_SDSaveData { | ||||
| class ArchiveSource_SDSaveData : public ArticCacheProvider { | ||||
| public: | ||||
|     explicit ArchiveSource_SDSaveData(const std::string& mount_point); | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(u64 program_id); | ||||
|     Result Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info); | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id) const; | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(Service::FS::ArchiveIdCode archive_id, | ||||
|                                                     const Path& path, u64 program_id); | ||||
|     Result Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   Service::FS::ArchiveIdCode archive_id, const Path& path, u32 directory_buckets, | ||||
|                   u32 file_buckets); | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id, | ||||
|                                                Service::FS::ArchiveIdCode archive_id, | ||||
|                                                const Path& path) const; | ||||
| 
 | ||||
|     static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id); | ||||
| 
 | ||||
|     void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) { | ||||
|         artic_client = client; | ||||
|     } | ||||
| 
 | ||||
|     bool IsUsingArtic() const { | ||||
|         return artic_client.get() != nullptr; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::string mount_point; | ||||
|     std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr; | ||||
| 
 | ||||
|     ArchiveSource_SDSaveData() = default; | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<ArticCacheProvider>(*this); | ||||
|         ar& mount_point; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
|  |  | |||
|  | @ -64,7 +64,8 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c | |||
| 
 | ||||
| Result ArchiveFactory_SystemSaveData::Format(const Path& path, | ||||
|                                              const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                              u64 program_id) { | ||||
|                                              u64 program_id, u32 directory_buckets, | ||||
|                                              u32 file_buckets) { | ||||
|     std::string fullpath = GetSystemSaveDataPath(base_path, path); | ||||
|     FileUtil::DeleteDirRecursively(fullpath); | ||||
|     FileUtil::CreateFullPath(fullpath); | ||||
|  |  | |||
|  | @ -20,8 +20,8 @@ public: | |||
|     explicit ArchiveFactory_SystemSaveData(const std::string& mount_point); | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                   u64 program_id) override; | ||||
|     Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                   u32 directory_buckets, u32 file_buckets) override; | ||||
|     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; | ||||
| 
 | ||||
|     std::string GetName() const override { | ||||
|  |  | |||
							
								
								
									
										235
									
								
								src/core/file_sys/artic_cache.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								src/core/file_sys/artic_cache.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,235 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "artic_cache.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| ResultVal<std::size_t> ArticCache::Read(s32 file_handle, std::size_t offset, std::size_t length, | ||||
|                                         u8* buffer) { | ||||
|     if (length == 0) | ||||
|         return size_t(); | ||||
| 
 | ||||
|     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) { | ||||
|         if (segments[0].second < big_cache_skip) { | ||||
|             std::unique_lock big_read_guard(big_cache_mutex); | ||||
|             auto big_cache_entry = big_cache.request(std::make_pair(offset, length)); | ||||
|             if (!big_cache_entry.first) { | ||||
|                 LOG_TRACE(Service_FS, "ArticCache BMISS: offset={}, length={}", offset, length); | ||||
|                 big_cache_entry.second.clear(); | ||||
|                 big_cache_entry.second.resize(length); | ||||
|                 auto res = | ||||
|                     ReadFromArtic(file_handle, reinterpret_cast<u8*>(big_cache_entry.second.data()), | ||||
|                                   length, offset); | ||||
|                 if (res.Failed()) | ||||
|                     return res; | ||||
|                 length = res.Unwrap(); | ||||
|             } else { | ||||
|                 LOG_TRACE(Service_FS, "ArticCache BHIT: offset={}, length={}", offset, length); | ||||
|             } | ||||
|             memcpy(buffer, big_cache_entry.second.data(), length); | ||||
|         } else { | ||||
|             if (segments[0].second < very_big_cache_skip) { | ||||
|                 std::unique_lock very_big_read_guard(very_big_cache_mutex); | ||||
|                 auto very_big_cache_entry = very_big_cache.request(std::make_pair(offset, length)); | ||||
|                 if (!very_big_cache_entry.first) { | ||||
|                     LOG_TRACE(Service_FS, "ArticCache VBMISS: offset={}, length={}", offset, | ||||
|                               length); | ||||
|                     very_big_cache_entry.second.clear(); | ||||
|                     very_big_cache_entry.second.resize(length); | ||||
|                     auto res = ReadFromArtic( | ||||
|                         file_handle, reinterpret_cast<u8*>(very_big_cache_entry.second.data()), | ||||
|                         length, offset); | ||||
|                     if (res.Failed()) | ||||
|                         return res; | ||||
|                     length = res.Unwrap(); | ||||
|                 } else { | ||||
|                     LOG_TRACE(Service_FS, "ArticCache VBHIT: offset={}, length={}", offset, length); | ||||
|                 } | ||||
|                 memcpy(buffer, very_big_cache_entry.second.data(), length); | ||||
|             } else { | ||||
|                 LOG_TRACE(Service_FS, "ArticCache SKIP: offset={}, length={}", offset, length); | ||||
| 
 | ||||
|                 auto res = ReadFromArtic(file_handle, buffer, length, offset); | ||||
|                 if (res.Failed()) | ||||
|                     return res; | ||||
|                 length = res.Unwrap(); | ||||
|             } | ||||
|         } | ||||
|         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 artic and cache the data
 | ||||
|             auto res = ReadFromArtic(file_handle, cache_entry.second.data(), read_size, page); | ||||
|             if (res.Failed()) | ||||
|                 return res; | ||||
|             read_size = res.Unwrap(); | ||||
|             LOG_TRACE(Service_FS, "ArticCache MISS: page={}, length={}, into={}", page, seg.second, | ||||
|                       (seg.first - page)); | ||||
|         } else { | ||||
|             LOG_TRACE(Service_FS, "ArticCache 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 ArticCache::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 { | ||||
|         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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ArticCache::Clear() { | ||||
|     std::unique_lock l1(cache_mutex), l2(big_cache_mutex), l3(very_big_cache_mutex); | ||||
|     cache.clear(); | ||||
|     big_cache.clear(); | ||||
|     very_big_cache.clear(); | ||||
|     data_size = std::nullopt; | ||||
| } | ||||
| 
 | ||||
| ResultVal<size_t> ArticCache::Write(s32 file_handle, std::size_t offset, std::size_t length, | ||||
|                                     const u8* buffer, u32 flags) { | ||||
|     // Can probably do better, but write operations are usually done at the end, so it doesn't
 | ||||
|     // matter much
 | ||||
|     Clear(); | ||||
| 
 | ||||
|     size_t written_amount = 0; | ||||
|     while (written_amount != length) { | ||||
|         size_t to_write = | ||||
|             std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, length - written_amount); | ||||
| 
 | ||||
|         auto req = client->NewRequest("FSFILE_Write"); | ||||
|         req.AddParameterS32(file_handle); | ||||
|         req.AddParameterS64(static_cast<s64>(offset + written_amount)); | ||||
|         req.AddParameterS32(static_cast<s32>(to_write)); | ||||
|         req.AddParameterS32(static_cast<s32>(flags)); | ||||
|         req.AddParameterBuffer(buffer + written_amount, to_write); | ||||
|         auto resp = client->Send(req); | ||||
|         if (!resp.has_value() || !resp->Succeeded()) | ||||
|             return Result(-1); | ||||
| 
 | ||||
|         auto res = Result(static_cast<u32>(resp->GetMethodResult())); | ||||
|         if (res.IsError()) | ||||
|             return res; | ||||
| 
 | ||||
|         auto actually_written_opt = resp->GetResponseS32(0); | ||||
|         if (!actually_written_opt.has_value()) | ||||
|             return Result(-1); | ||||
| 
 | ||||
|         size_t actually_written = static_cast<size_t>(actually_written_opt.value()); | ||||
| 
 | ||||
|         written_amount += actually_written; | ||||
|         if (actually_written != to_write) | ||||
|             break; | ||||
|     } | ||||
|     return written_amount; | ||||
| } | ||||
| 
 | ||||
| ResultVal<size_t> ArticCache::GetSize(s32 file_handle) { | ||||
|     std::unique_lock l1(cache_mutex); | ||||
| 
 | ||||
|     if (data_size.has_value()) | ||||
|         return data_size.value(); | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSFILE_GetSize"); | ||||
| 
 | ||||
|     req.AddParameterS32(file_handle); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     if (!resp.has_value() || !resp->Succeeded()) | ||||
|         return Result(-1); | ||||
| 
 | ||||
|     auto res = Result(static_cast<u32>(resp->GetMethodResult())); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     auto size_buf = resp->GetResponseS64(0); | ||||
|     if (!size_buf) { | ||||
|         return Result(-1); | ||||
|     } | ||||
| 
 | ||||
|     data_size = static_cast<size_t>(*size_buf); | ||||
|     return data_size.value(); | ||||
| } | ||||
| 
 | ||||
| ResultVal<size_t> ArticCache::ReadFromArtic(s32 file_handle, u8* buffer, size_t len, | ||||
|                                             size_t offset) { | ||||
|     size_t read_amount = 0; | ||||
|     while (read_amount != len) { | ||||
|         size_t to_read = | ||||
|             std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, len - read_amount); | ||||
| 
 | ||||
|         auto req = client->NewRequest("FSFILE_Read"); | ||||
|         req.AddParameterS32(file_handle); | ||||
|         req.AddParameterS64(static_cast<s64>(offset + read_amount)); | ||||
|         req.AddParameterS32(static_cast<s32>(to_read)); | ||||
|         auto resp = client->Send(req); | ||||
|         if (!resp.has_value() || !resp->Succeeded()) | ||||
|             return Result(-1); | ||||
| 
 | ||||
|         auto res = Result(static_cast<u32>(resp->GetMethodResult())); | ||||
|         if (res.IsError()) | ||||
|             return res; | ||||
| 
 | ||||
|         auto read_buff = resp->GetResponseBuffer(0); | ||||
|         if (!read_buff.has_value()) | ||||
|             return Result(-1); | ||||
|         size_t actually_read = read_buff->second; | ||||
| 
 | ||||
|         memcpy(buffer + read_amount, read_buff->first, actually_read); | ||||
|         read_amount += actually_read; | ||||
|         if (actually_read != to_read) | ||||
|             break; | ||||
|     } | ||||
|     return read_amount; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::pair<std::size_t, std::size_t>> ArticCache::BreakupRead(std::size_t offset, | ||||
|                                                                          std::size_t length) { | ||||
|     std::vector<std::pair<std::size_t, std::size_t>> ret; | ||||
| 
 | ||||
|     // Reads bigger than the cache line size will probably never hit again
 | ||||
|     if (length > max_breakup_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; | ||||
| } | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										154
									
								
								src/core/file_sys/artic_cache.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								src/core/file_sys/artic_cache.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,154 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <shared_mutex> | ||||
| #include "vector" | ||||
| 
 | ||||
| #include <boost/serialization/array.hpp> | ||||
| #include <boost/serialization/base_object.hpp> | ||||
| #include <boost/serialization/export.hpp> | ||||
| #include "common/alignment.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/static_lru_cache.h" | ||||
| #include "core/file_sys/archive_backend.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "network/artic_base/artic_base_client.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| class ArticCache { | ||||
| public: | ||||
|     ArticCache() = default; | ||||
| 
 | ||||
|     ArticCache(const std::shared_ptr<Network::ArticBase::Client>& cli) : client(cli) {} | ||||
| 
 | ||||
|     ResultVal<std::size_t> Read(s32 file_handle, std::size_t offset, std::size_t length, | ||||
|                                 u8* buffer); | ||||
| 
 | ||||
|     bool CacheReady(std::size_t file_offset, std::size_t length); | ||||
| 
 | ||||
|     void Clear(); | ||||
| 
 | ||||
|     ResultVal<std::size_t> Write(s32 file_handle, std::size_t offset, std::size_t length, | ||||
|                                  const u8* buffer, u32 flags); | ||||
| 
 | ||||
|     ResultVal<size_t> GetSize(s32 file_handle); | ||||
| 
 | ||||
|     void ForceSetSize(const std::optional<size_t>& size) { | ||||
|         data_size = size; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<Network::ArticBase::Client> client; | ||||
|     std::optional<size_t> data_size; | ||||
| 
 | ||||
|     // Total cache size: 32MB small, 512MB big (worst case), 160MB very big (worst case).
 | ||||
|     // The worst case values are unrealistic, they will never happen in any real game.
 | ||||
|     static constexpr std::size_t cache_line_size = 4 * 1024; | ||||
|     static constexpr std::size_t cache_line_count = 256; | ||||
|     static constexpr std::size_t max_breakup_size = 8 * 1024; | ||||
| 
 | ||||
|     static constexpr std::size_t big_cache_skip = 1 * 1024 * 1024; | ||||
|     static constexpr std::size_t big_cache_lines = 1024; | ||||
| 
 | ||||
|     static constexpr std::size_t very_big_cache_skip = 10 * 1024 * 1024; | ||||
|     static constexpr std::size_t very_big_cache_lines = 24; | ||||
| 
 | ||||
|     Common::StaticLRUCache<std::size_t, std::array<u8, cache_line_size>, cache_line_count> cache; | ||||
|     std::shared_mutex cache_mutex; | ||||
| 
 | ||||
|     struct NoInitChar { | ||||
|         u8 value; | ||||
|         NoInitChar() noexcept { | ||||
|             // do nothing
 | ||||
|             static_assert(sizeof *this == sizeof value, "invalid size"); | ||||
|         } | ||||
|     }; | ||||
|     Common::StaticLRUCache<std::pair<std::size_t, std::size_t>, std::vector<NoInitChar>, | ||||
|                            big_cache_lines> | ||||
|         big_cache; | ||||
|     std::shared_mutex big_cache_mutex; | ||||
|     Common::StaticLRUCache<std::pair<std::size_t, std::size_t>, std::vector<NoInitChar>, | ||||
|                            very_big_cache_lines> | ||||
|         very_big_cache; | ||||
|     std::shared_mutex very_big_cache_mutex; | ||||
| 
 | ||||
|     ResultVal<std::size_t> ReadFromArtic(s32 file_handle, u8* buffer, size_t len, size_t offset); | ||||
| 
 | ||||
|     std::size_t OffsetToPage(std::size_t offset) { | ||||
|         return Common::AlignDown<std::size_t>(offset, cache_line_size); | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::pair<std::size_t, std::size_t>> BreakupRead(std::size_t offset, | ||||
|                                                                  std::size_t length); | ||||
| 
 | ||||
| protected: | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) {} | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| 
 | ||||
| class ArticCacheProvider { | ||||
| public: | ||||
|     virtual ~ArticCacheProvider() {} | ||||
| 
 | ||||
|     std::vector<u8> PathsToVector(const Path& archive_path, const Path& file_path) { | ||||
|         auto archive_path_binary = archive_path.AsBinary(); | ||||
|         auto file_path_binary = file_path.AsBinary(); | ||||
| 
 | ||||
|         std::vector<u8> ret; | ||||
|         ret.push_back(static_cast<u8>(file_path.GetType())); | ||||
|         ret.insert(ret.end(), archive_path_binary.begin(), archive_path_binary.end()); | ||||
|         ret.push_back(static_cast<u8>(archive_path.GetType())); | ||||
|         ret.insert(ret.end(), file_path_binary.begin(), file_path_binary.end()); | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     virtual std::shared_ptr<ArticCache> ProvideCache( | ||||
|         const std::shared_ptr<Network::ArticBase::Client>& cli, const std::vector<u8>& path, | ||||
|         bool create) { | ||||
|         if (file_caches == nullptr) | ||||
|             return nullptr; | ||||
| 
 | ||||
|         auto it = file_caches->find(path); | ||||
|         if (it == file_caches->end()) { | ||||
|             if (!create) { | ||||
|                 return nullptr; | ||||
|             } | ||||
|             auto res = std::make_shared<ArticCache>(cli); | ||||
|             file_caches->insert({path, res}); | ||||
|             return res; | ||||
|         } | ||||
|         return it->second; | ||||
|     } | ||||
| 
 | ||||
|     virtual void ClearAllCache() { | ||||
|         if (file_caches != nullptr) { | ||||
|             file_caches->clear(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     virtual void EnsureCacheCreated() { | ||||
|         if (file_caches == nullptr) { | ||||
|             file_caches = | ||||
|                 std::make_unique<std::map<std::vector<u8>, std::shared_ptr<ArticCache>>>(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) {} | ||||
|     friend class boost::serialization::access; | ||||
| 
 | ||||
| private: | ||||
|     std::unique_ptr<std::map<std::vector<u8>, std::shared_ptr<ArticCache>>> file_caches = nullptr; | ||||
|     std::shared_ptr<Network::ArticBase::Client> client; | ||||
| }; | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| BOOST_CLASS_EXPORT_KEY(FileSys::ArticCache) | ||||
| BOOST_CLASS_EXPORT_KEY(FileSys::ArticCacheProvider) | ||||
|  | @ -49,7 +49,11 @@ public: | |||
|      * Close the directory | ||||
|      * @return true if the directory closed correctly | ||||
|      */ | ||||
|     virtual bool Close() const = 0; | ||||
|     virtual bool Close() = 0; | ||||
| 
 | ||||
|     virtual bool IsSlow() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     template <class Archive> | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ ResultVal<std::size_t> DiskFile::Read(const u64 offset, const std::size_t length | |||
| } | ||||
| 
 | ||||
| ResultVal<std::size_t> DiskFile::Write(const u64 offset, const std::size_t length, const bool flush, | ||||
|                                        const u8* buffer) { | ||||
|                                        const bool update_timestamp, const u8* buffer) { | ||||
|     if (!mode.write_flag) | ||||
|         return ResultInvalidOpenFlags; | ||||
| 
 | ||||
|  | @ -47,7 +47,7 @@ bool DiskFile::SetSize(const u64 size) const { | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool DiskFile::Close() const { | ||||
| bool DiskFile::Close() { | ||||
|     return file->Close(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,11 +30,11 @@ public: | |||
|     } | ||||
| 
 | ||||
|     ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override; | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override; | ||||
|     u64 GetSize() const override; | ||||
|     bool SetSize(u64 size) const override; | ||||
|     bool Close() const override; | ||||
|     bool Close() override; | ||||
| 
 | ||||
|     void Flush() const override { | ||||
|         file->Flush(); | ||||
|  | @ -66,7 +66,7 @@ public: | |||
| 
 | ||||
|     u32 Read(u32 count, Entry* entries) override; | ||||
| 
 | ||||
|     bool Close() const override { | ||||
|     bool Close() override { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -37,7 +37,7 @@ public: | |||
|      * @return Number of bytes written, or error code | ||||
|      */ | ||||
|     virtual ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|                                          const u8* buffer) = 0; | ||||
|                                          bool update_timestamp, const u8* buffer) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Get the amount of time a 3ds needs to read those data | ||||
|  | @ -79,7 +79,7 @@ public: | |||
|      * Close the file | ||||
|      * @return true if the file closed correctly | ||||
|      */ | ||||
|     virtual bool Close() const = 0; | ||||
|     virtual bool Close() = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Flushes the file | ||||
|  |  | |||
|  | @ -28,8 +28,8 @@ std::string IVFCArchive::GetName() const { | |||
|     return "IVFC"; | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, | ||||
|                                                               const Mode& mode) const { | ||||
| ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, const Mode& mode, | ||||
|                                                               u32 attributes) { | ||||
|     std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<IVFCDelayGenerator>(); | ||||
|     return std::make_unique<IVFCFile>(romfs_file, std::move(delay_generator)); | ||||
| } | ||||
|  | @ -61,14 +61,14 @@ Result IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { | |||
|     return ResultUnknown; | ||||
| } | ||||
| 
 | ||||
| Result IVFCArchive::CreateFile(const Path& path, u64 size) const { | ||||
| Result IVFCArchive::CreateFile(const Path& path, u64 size, u32 attributes) const { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to create a file in an IVFC archive ({}).", GetName()); | ||||
|     // TODO: Verify error code
 | ||||
|     return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, | ||||
|                   ErrorLevel::Permanent); | ||||
| } | ||||
| 
 | ||||
| Result IVFCArchive::CreateDirectory(const Path& path) const { | ||||
| Result IVFCArchive::CreateDirectory(const Path& path, u32 attributes) const { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive ({}).", GetName()); | ||||
|     // TODO(wwylele): Use correct error code
 | ||||
|     return ResultUnknown; | ||||
|  | @ -80,7 +80,7 @@ Result IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) | |||
|     return ResultUnknown; | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const { | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) { | ||||
|     return std::make_unique<IVFCDirectory>(); | ||||
| } | ||||
| 
 | ||||
|  | @ -102,7 +102,7 @@ ResultVal<std::size_t> IVFCFile::Read(const u64 offset, const std::size_t length | |||
| } | ||||
| 
 | ||||
| ResultVal<std::size_t> IVFCFile::Write(const u64 offset, const std::size_t length, const bool flush, | ||||
|                                        const u8* buffer) { | ||||
|                                        const bool update_timestamp, const u8* buffer) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to write to IVFC file"); | ||||
|     // TODO(Subv): Find error code
 | ||||
|     return 0ULL; | ||||
|  | @ -133,7 +133,8 @@ ResultVal<std::size_t> IVFCFileInMemory::Read(const u64 offset, const std::size_ | |||
| } | ||||
| 
 | ||||
| ResultVal<std::size_t> IVFCFileInMemory::Write(const u64 offset, const std::size_t length, | ||||
|                                                const bool flush, const u8* buffer) { | ||||
|                                                const bool flush, const bool update_timestamp, | ||||
|                                                const u8* buffer) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to write to IVFC file"); | ||||
|     // TODO(Subv): Find error code
 | ||||
|     return 0ULL; | ||||
|  |  | |||
|  | @ -101,16 +101,16 @@ public: | |||
| 
 | ||||
|     std::string GetName() const override; | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||
|                                                      const Mode& mode) const override; | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode, | ||||
|                                                      u32 attributes) override; | ||||
|     Result DeleteFile(const Path& path) const override; | ||||
|     Result RenameFile(const Path& src_path, const Path& dest_path) const override; | ||||
|     Result DeleteDirectory(const Path& path) const override; | ||||
|     Result DeleteDirectoryRecursively(const Path& path) const override; | ||||
|     Result CreateFile(const Path& path, u64 size) const override; | ||||
|     Result CreateDirectory(const Path& path) const override; | ||||
|     Result CreateFile(const Path& path, u64 size, u32 attributes) const override; | ||||
|     Result CreateDirectory(const Path& path, u32 attributes) const override; | ||||
|     Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override; | ||||
|     u64 GetFreeBytes() const override; | ||||
| 
 | ||||
| protected: | ||||
|  | @ -122,11 +122,11 @@ public: | |||
|     IVFCFile(std::shared_ptr<RomFSReader> file, std::unique_ptr<DelayGenerator> delay_generator_); | ||||
| 
 | ||||
|     ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override; | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override; | ||||
|     u64 GetSize() const override; | ||||
|     bool SetSize(u64 size) const override; | ||||
|     bool Close() const override { | ||||
|     bool Close() override { | ||||
|         return false; | ||||
|     } | ||||
|     void Flush() const override {} | ||||
|  | @ -157,7 +157,7 @@ public: | |||
|     u32 Read(const u32 count, Entry* entries) override { | ||||
|         return 0; | ||||
|     } | ||||
|     bool Close() const override { | ||||
|     bool Close() override { | ||||
|         return false; | ||||
|     } | ||||
| }; | ||||
|  | @ -168,11 +168,11 @@ public: | |||
|                      std::unique_ptr<DelayGenerator> delay_generator_); | ||||
| 
 | ||||
|     ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override; | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override; | ||||
|     u64 GetSize() const override; | ||||
|     bool SetSize(u64 size) const override; | ||||
|     bool Close() const override { | ||||
|     bool Close() override { | ||||
|         return false; | ||||
|     } | ||||
|     void Flush() const override {} | ||||
|  |  | |||
|  | @ -4,7 +4,11 @@ | |||
| #include <cryptopp/modes.h> | ||||
| #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) | ||||
| 
 | ||||
|  | @ -109,4 +113,102 @@ std::vector<std::pair<std::size_t, std::size_t>> DirectRomFSReader::BreakupRead( | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| ArticRomFSReader::ArticRomFSReader(std::shared_ptr<Network::ArticBase::Client>& cli, | ||||
|                                    bool is_update_romfs) | ||||
|     : client(cli), cache(cli) { | ||||
|     auto req = client->NewRequest("FSUSER_OpenFileDirectly"); | ||||
| 
 | ||||
|     FileSys::Path archive(FileSys::LowPathType::Empty, {}); | ||||
|     std::vector<u8> fileVec(0xC); | ||||
|     fileVec[0] = static_cast<u8>(is_update_romfs ? 5 : 0); | ||||
|     FileSys::Path file(FileSys::LowPathType::Binary, fileVec); | ||||
| 
 | ||||
|     req.AddParameterS32(static_cast<s32>(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<s32*>(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<size_t>(*reinterpret_cast<u64*>(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<std::size_t>(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
 | ||||
|  |  | |||
|  | @ -9,6 +9,12 @@ | |||
| #include "common/common_types.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/static_lru_cache.h" | ||||
| #include "core/file_sys/artic_cache.h" | ||||
| #include "network/artic_base/artic_base_client.h" | ||||
| 
 | ||||
| namespace Loader { | ||||
| enum class ResultStatus; | ||||
| } | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
|  | @ -97,6 +103,53 @@ private: | |||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * A RomFS reader that reads from an artic base server. | ||||
|  */ | ||||
| class ArticRomFSReader : public RomFSReader { | ||||
| public: | ||||
|     ArticRomFSReader() = default; | ||||
|     ArticRomFSReader(std::shared_ptr<Network::ArticBase::Client>& cli, bool is_update_romfs); | ||||
| 
 | ||||
|     ~ArticRomFSReader() override; | ||||
| 
 | ||||
|     std::size_t GetSize() const override { | ||||
|         return data_size; | ||||
|     } | ||||
| 
 | ||||
|     std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override; | ||||
| 
 | ||||
|     bool AllowsCachedReads() const override; | ||||
| 
 | ||||
|     bool CacheReady(std::size_t file_offset, std::size_t length) override; | ||||
| 
 | ||||
|     Loader::ResultStatus OpenStatus() { | ||||
|         return load_status; | ||||
|     } | ||||
| 
 | ||||
|     void ClearCache() { | ||||
|         cache.Clear(); | ||||
|     } | ||||
| 
 | ||||
|     void CloseFile(); | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<Network::ArticBase::Client> client; | ||||
|     size_t data_size = 0; | ||||
|     s32 romfs_handle = -1; | ||||
|     Loader::ResultStatus load_status; | ||||
| 
 | ||||
|     ArticCache cache; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<RomFSReader>(*this); | ||||
|         ar& data_size; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| BOOST_CLASS_EXPORT_KEY(FileSys::DirectRomFSReader) | ||||
| BOOST_CLASS_EXPORT_KEY(FileSys::ArticRomFSReader) | ||||
|  |  | |||
|  | @ -36,7 +36,8 @@ public: | |||
| }; | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path, | ||||
|                                                                   const Mode& mode) const { | ||||
|                                                                   const Mode& mode, | ||||
|                                                                   u32 attributes) { | ||||
|     LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex); | ||||
| 
 | ||||
|     const PathParser path_parser(path); | ||||
|  | @ -203,7 +204,7 @@ Result SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const { | |||
|         path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); | ||||
| } | ||||
| 
 | ||||
| Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const { | ||||
| Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size, u32 attributes) const { | ||||
|     const PathParser path_parser(path); | ||||
| 
 | ||||
|     if (!path_parser.IsValid()) { | ||||
|  | @ -253,7 +254,7 @@ Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const { | |||
|                   ErrorLevel::Info); | ||||
| } | ||||
| 
 | ||||
| Result SaveDataArchive::CreateDirectory(const Path& path) const { | ||||
| Result SaveDataArchive::CreateDirectory(const Path& path, u32 attributes) const { | ||||
|     const PathParser path_parser(path); | ||||
| 
 | ||||
|     if (!path_parser.IsValid()) { | ||||
|  | @ -319,8 +320,7 @@ Result SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_p | |||
|                   ErrorSummary::NothingHappened, ErrorLevel::Status); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory( | ||||
|     const Path& path) const { | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(const Path& path) { | ||||
|     const PathParser path_parser(path); | ||||
| 
 | ||||
|     if (!path_parser.IsValid()) { | ||||
|  |  | |||
|  | @ -22,16 +22,16 @@ public: | |||
|         return "SaveDataArchive: " + mount_point; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||
|                                                      const Mode& mode) const override; | ||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode, | ||||
|                                                      u32 attributes) override; | ||||
|     Result DeleteFile(const Path& path) const override; | ||||
|     Result RenameFile(const Path& src_path, const Path& dest_path) const override; | ||||
|     Result DeleteDirectory(const Path& path) const override; | ||||
|     Result DeleteDirectoryRecursively(const Path& path) const override; | ||||
|     Result CreateFile(const Path& path, u64 size) const override; | ||||
|     Result CreateDirectory(const Path& path) const override; | ||||
|     Result CreateFile(const Path& path, u64 size, u32 attributes) const override; | ||||
|     Result CreateDirectory(const Path& path, u32 attributes) const override; | ||||
|     Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override; | ||||
|     u64 GetFreeBytes() const override; | ||||
| 
 | ||||
| protected: | ||||
|  |  | |||
							
								
								
									
										74
									
								
								src/core/file_sys/secure_value_backend.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/core/file_sys/secure_value_backend.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/archives.h" | ||||
| #include "secure_value_backend.h" | ||||
| 
 | ||||
| SERIALIZE_EXPORT_IMPL(FileSys::DefaultSecureValueBackend) | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| Result DefaultSecureValueBackend::ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, | ||||
|                                                                   u32 secure_value_slot, | ||||
|                                                                   u64 secure_value) { | ||||
| 
 | ||||
|     // TODO: Generate and Save the Secure Value
 | ||||
| 
 | ||||
|     LOG_WARNING(Service_FS, | ||||
|                 "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:08X} " | ||||
|                 "unqiue_id=0x{:08X} title_variation=0x{:02X}", | ||||
|                 secure_value, secure_value_slot, unique_id, title_variation); | ||||
| 
 | ||||
|     return ResultSuccess; | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::tuple<bool, u64>> DefaultSecureValueBackend::ObsoletedGetSaveDataSecureValue( | ||||
|     u32 unique_id, u8 title_variation, u32 secure_value_slot) { | ||||
| 
 | ||||
|     // TODO: Implement Secure Value Lookup & Generation
 | ||||
| 
 | ||||
|     LOG_WARNING(Service_FS, | ||||
|                 "(STUBBED) called, secure_value_slot=0x{:08X} " | ||||
|                 "unqiue_id=0x{:08X} title_variation=0x{:02X}", | ||||
|                 secure_value_slot, unique_id, title_variation); | ||||
| 
 | ||||
|     return std::make_tuple<bool, u64>(false, 0); | ||||
| } | ||||
| 
 | ||||
| Result DefaultSecureValueBackend::ControlSecureSave(u32 action, u8* input, size_t input_size, | ||||
|                                                     u8* output, size_t output_size) { | ||||
| 
 | ||||
|     LOG_WARNING(Service_FS, | ||||
|                 "(STUBBED) called, action=0x{:08X} " | ||||
|                 "input_size=0x{:016X} output_size=0x{:016X}", | ||||
|                 action, input_size, output_size); | ||||
| 
 | ||||
|     return ResultSuccess; | ||||
| } | ||||
| 
 | ||||
| Result DefaultSecureValueBackend::SetThisSaveDataSecureValue(u32 secure_value_slot, | ||||
|                                                              u64 secure_value) { | ||||
|     // TODO: Generate and Save the Secure Value
 | ||||
| 
 | ||||
|     LOG_WARNING(Service_FS, "(STUBBED) called, secure_value=0x{:016x} secure_value_slot=0x{:08X}", | ||||
|                 secure_value, secure_value_slot); | ||||
| 
 | ||||
|     return ResultSuccess; | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::tuple<bool, bool, u64>> DefaultSecureValueBackend::GetThisSaveDataSecureValue( | ||||
|     u32 secure_value_slot) { | ||||
| 
 | ||||
|     // TODO: Implement Secure Value Lookup & Generation
 | ||||
| 
 | ||||
|     LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X}", secure_value_slot); | ||||
| 
 | ||||
|     return std::make_tuple<bool, bool, u64>(false, true, 0); | ||||
| } | ||||
| 
 | ||||
| template <class Archive> | ||||
| void FileSys::DefaultSecureValueBackend::serialize(Archive& ar, const unsigned int) { | ||||
|     ar& boost::serialization::base_object<SecureValueBackend>(*this); | ||||
| } | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										65
									
								
								src/core/file_sys/secure_value_backend.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/core/file_sys/secure_value_backend.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "tuple" | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| class SecureValueBackend : NonCopyable { | ||||
| public: | ||||
|     virtual ~SecureValueBackend(){}; | ||||
| 
 | ||||
|     virtual Result ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, | ||||
|                                                    u32 secure_value_slot, u64 secure_value) = 0; | ||||
| 
 | ||||
|     virtual ResultVal<std::tuple<bool, u64>> ObsoletedGetSaveDataSecureValue( | ||||
|         u32 unique_id, u8 title_variation, u32 secure_value_slot) = 0; | ||||
| 
 | ||||
|     virtual Result ControlSecureSave(u32 action, u8* input, size_t input_size, u8* output, | ||||
|                                      size_t output_size) = 0; | ||||
| 
 | ||||
|     virtual Result SetThisSaveDataSecureValue(u32 secure_value_slot, u64 secure_value) = 0; | ||||
| 
 | ||||
|     virtual ResultVal<std::tuple<bool, bool, u64>> GetThisSaveDataSecureValue( | ||||
|         u32 secure_value_slot) = 0; | ||||
| 
 | ||||
|     virtual bool BackendIsSlow() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) {} | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| 
 | ||||
| class DefaultSecureValueBackend : public SecureValueBackend { | ||||
| public: | ||||
|     Result ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, u32 secure_value_slot, | ||||
|                                            u64 secure_value) override; | ||||
| 
 | ||||
|     ResultVal<std::tuple<bool, u64>> ObsoletedGetSaveDataSecureValue( | ||||
|         u32 unique_id, u8 title_variation, u32 secure_value_slot) override; | ||||
| 
 | ||||
|     Result ControlSecureSave(u32 action, u8* input, size_t input_size, u8* output, | ||||
|                              size_t output_size) override; | ||||
| 
 | ||||
|     Result SetThisSaveDataSecureValue(u32 secure_value_slot, u64 secure_value) override; | ||||
| 
 | ||||
|     ResultVal<std::tuple<bool, bool, u64>> GetThisSaveDataSecureValue( | ||||
|         u32 secure_value_slot) override; | ||||
| 
 | ||||
| protected: | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int); | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| BOOST_CLASS_EXPORT_KEY(FileSys::DefaultSecureValueBackend) | ||||
							
								
								
									
										119
									
								
								src/core/file_sys/secure_value_backend_artic.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/core/file_sys/secure_value_backend_artic.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,119 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/archives.h" | ||||
| #include "core/file_sys/archive_artic.h" | ||||
| #include "core/file_sys/secure_value_backend_artic.h" | ||||
| 
 | ||||
| SERIALIZE_EXPORT_IMPL(FileSys::ArticSecureValueBackend) | ||||
| 
 | ||||
| namespace FileSys { | ||||
| Result ArticSecureValueBackend::ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, | ||||
|                                                                 u32 secure_value_slot, | ||||
|                                                                 u64 secure_value) { | ||||
|     auto req = client->NewRequest("FSUSER_ObsSetSaveDataSecureVal"); | ||||
| 
 | ||||
|     req.AddParameterU64(secure_value); | ||||
|     req.AddParameterU32(secure_value_slot); | ||||
|     req.AddParameterU32(unique_id); | ||||
|     req.AddParameterU8(title_variation); | ||||
| 
 | ||||
|     return ArticArchive::RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::tuple<bool, u64>> ArticSecureValueBackend::ObsoletedGetSaveDataSecureValue( | ||||
|     u32 unique_id, u8 title_variation, u32 secure_value_slot) { | ||||
| 
 | ||||
|     auto req = client->NewRequest("FSUSER_ObsGetSaveDataSecureVal"); | ||||
| 
 | ||||
|     req.AddParameterU32(secure_value_slot); | ||||
|     req.AddParameterU32(unique_id); | ||||
|     req.AddParameterU8(title_variation); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = ArticArchive::RespResult(resp); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     struct { | ||||
|         bool exists; | ||||
|         u64 secure_value; | ||||
|     } secure_value_result; | ||||
|     static_assert(sizeof(secure_value_result) == 0x10); | ||||
| 
 | ||||
|     auto output_buf = resp->GetResponseBuffer(0); | ||||
|     if (!output_buf.has_value()) | ||||
|         return res; | ||||
| 
 | ||||
|     if (output_buf->second != sizeof(secure_value_result)) | ||||
|         return ResultUnknown; | ||||
| 
 | ||||
|     memcpy(&secure_value_result, output_buf->first, output_buf->second); | ||||
|     return std::make_tuple(secure_value_result.exists, secure_value_result.secure_value); | ||||
| } | ||||
| 
 | ||||
| Result ArticSecureValueBackend::ControlSecureSave(u32 action, u8* input, size_t input_size, | ||||
|                                                   u8* output, size_t output_size) { | ||||
|     auto req = client->NewRequest("FSUSER_ControlSecureSave"); | ||||
| 
 | ||||
|     req.AddParameterU32(action); | ||||
|     req.AddParameterBuffer(input, input_size); | ||||
|     req.AddParameterU32(static_cast<u32>(output_size)); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = ArticArchive::RespResult(resp); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     auto output_buf = resp->GetResponseBuffer(0); | ||||
|     if (!output_buf.has_value()) | ||||
|         return res; | ||||
| 
 | ||||
|     if (output_buf->second != output_size) | ||||
|         return ResultUnknown; | ||||
| 
 | ||||
|     memcpy(output, output_buf->first, output_buf->second); | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| Result ArticSecureValueBackend::SetThisSaveDataSecureValue(u32 secure_value_slot, | ||||
|                                                            u64 secure_value) { | ||||
|     auto req = client->NewRequest("FSUSER_SetThisSaveDataSecVal"); | ||||
| 
 | ||||
|     req.AddParameterU32(secure_value_slot); | ||||
|     req.AddParameterU64(secure_value); | ||||
| 
 | ||||
|     return ArticArchive::RespResult(client->Send(req)); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::tuple<bool, bool, u64>> ArticSecureValueBackend::GetThisSaveDataSecureValue( | ||||
|     u32 secure_value_slot) { | ||||
|     auto req = client->NewRequest("FSUSER_GetThisSaveDataSecVal"); | ||||
| 
 | ||||
|     req.AddParameterU32(secure_value_slot); | ||||
| 
 | ||||
|     auto resp = client->Send(req); | ||||
|     auto res = ArticArchive::RespResult(resp); | ||||
|     if (res.IsError()) | ||||
|         return res; | ||||
| 
 | ||||
|     struct { | ||||
|         bool exists; | ||||
|         bool isGamecard; | ||||
|         u64 secure_value; | ||||
|     } secure_value_result; | ||||
|     static_assert(sizeof(secure_value_result) == 0x10); | ||||
| 
 | ||||
|     auto output_buf = resp->GetResponseBuffer(0); | ||||
|     if (!output_buf.has_value()) | ||||
|         return res; | ||||
| 
 | ||||
|     if (output_buf->second != sizeof(secure_value_result)) | ||||
|         return ResultUnknown; | ||||
| 
 | ||||
|     memcpy(&secure_value_result, output_buf->first, output_buf->second); | ||||
|     return std::make_tuple(secure_value_result.exists, secure_value_result.isGamecard, | ||||
|                            secure_value_result.secure_value); | ||||
| } | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										53
									
								
								src/core/file_sys/secure_value_backend_artic.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/core/file_sys/secure_value_backend_artic.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "tuple" | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/secure_value_backend.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "network/artic_base/artic_base_client.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| class ArticSecureValueBackend : public SecureValueBackend { | ||||
| public: | ||||
|     ArticSecureValueBackend(const std::shared_ptr<Network::ArticBase::Client>& _client) | ||||
|         : client(_client) {} | ||||
| 
 | ||||
|     Result ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, u32 secure_value_slot, | ||||
|                                            u64 secure_value) override; | ||||
| 
 | ||||
|     ResultVal<std::tuple<bool, u64>> ObsoletedGetSaveDataSecureValue( | ||||
|         u32 unique_id, u8 title_variation, u32 secure_value_slot) override; | ||||
| 
 | ||||
|     Result ControlSecureSave(u32 action, u8* input, size_t input_size, u8* output, | ||||
|                              size_t output_size) override; | ||||
| 
 | ||||
|     Result SetThisSaveDataSecureValue(u32 secure_value_slot, u64 secure_value) override; | ||||
| 
 | ||||
|     ResultVal<std::tuple<bool, bool, u64>> GetThisSaveDataSecureValue( | ||||
|         u32 secure_value_slot) override; | ||||
| 
 | ||||
|     bool BackendIsSlow() override { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     ArticSecureValueBackend() = default; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<SecureValueBackend>(*this); | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<Network::ArticBase::Client> client; | ||||
| }; | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| BOOST_CLASS_EXPORT_KEY(FileSys::ArticSecureValueBackend) | ||||
|  | @ -58,6 +58,11 @@ public: | |||
|         : RequestBuilder( | ||||
|               context, Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {} | ||||
| 
 | ||||
|     RequestBuilder(Kernel::HLERequestContext& context, unsigned normal_params_size, | ||||
|                    unsigned translate_params_size) | ||||
|         : RequestBuilder(context, Header{MakeHeader(context.CommandID(), normal_params_size, | ||||
|                                                     translate_params_size)}) {} | ||||
| 
 | ||||
|     // Validate on destruction, as there shouldn't be any case where we don't want it
 | ||||
|     ~RequestBuilder() { | ||||
|         ValidateHeader(); | ||||
|  |  | |||
|  | @ -206,6 +206,11 @@ public: | |||
|         return {cmd_buf[0]}; | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the Command ID from the IPC command buffer.
 | ||||
|     u16 CommandID() const { | ||||
|         return static_cast<u16>(CommandHeader().command_id.Value()); | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Returns the session through which this request was made. This can be used as a map key to | ||||
|      * access per-client data on services. | ||||
|  |  | |||
|  | @ -266,7 +266,7 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length, | |||
| } | ||||
| 
 | ||||
| ResultVal<std::size_t> CIAFile::Write(u64 offset, std::size_t length, bool flush, | ||||
|                                       const u8* buffer) { | ||||
|                                       bool update_timestamp, const u8* buffer) { | ||||
|     written += length; | ||||
| 
 | ||||
|     // TODO(shinyquagsire23): Can we assume that things will only be written in sequence?
 | ||||
|  | @ -347,7 +347,7 @@ bool CIAFile::SetSize(u64 size) const { | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool CIAFile::Close() const { | ||||
| bool CIAFile::Close() { | ||||
|     bool complete = | ||||
|         install_state >= CIAInstallState::TMDLoaded && | ||||
|         content_written.size() == container.GetTitleMetadata().GetContentCount() && | ||||
|  | @ -419,7 +419,7 @@ ResultVal<std::size_t> TicketFile::Read(u64 offset, std::size_t length, u8* buff | |||
| } | ||||
| 
 | ||||
| ResultVal<std::size_t> TicketFile::Write(u64 offset, std::size_t length, bool flush, | ||||
|                                          const u8* buffer) { | ||||
|                                          bool update_timestamp, const u8* buffer) { | ||||
|     written += length; | ||||
|     data.resize(written); | ||||
|     std::memcpy(data.data() + offset, buffer, length); | ||||
|  | @ -434,7 +434,7 @@ bool TicketFile::SetSize(u64 size) const { | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool TicketFile::Close() const { | ||||
| bool TicketFile::Close() { | ||||
|     FileSys::Ticket ticket; | ||||
|     if (ticket.Load(data, 0) == Loader::ResultStatus::Success) { | ||||
|         LOG_WARNING(Service_AM, "Discarding ticket for {:#016X}.", ticket.GetTitleID()); | ||||
|  | @ -480,7 +480,7 @@ InstallStatus InstallCIA(const std::string& path, | |||
|         while (total_bytes_read != file_size) { | ||||
|             std::size_t bytes_read = file.ReadBytes(buffer.data(), buffer.size()); | ||||
|             auto result = installFile.Write(static_cast<u64>(total_bytes_read), bytes_read, true, | ||||
|                                             static_cast<u8*>(buffer.data())); | ||||
|                                             false, static_cast<u8*>(buffer.data())); | ||||
| 
 | ||||
|             if (update_callback) { | ||||
|                 update_callback(total_bytes_read, file_size); | ||||
|  | @ -590,7 +590,8 @@ InstallStatus InstallFromNus(u64 title_id, int version) { | |||
|         const u64 offset = | ||||
|             Common::AlignUp(current_offset + data.size(), FileSys::CIA_SECTION_ALIGNMENT); | ||||
|         data.resize(offset - current_offset, 0); | ||||
|         const auto result = install_file.Write(current_offset, data.size(), true, data.data()); | ||||
|         const auto result = | ||||
|             install_file.Write(current_offset, data.size(), true, false, data.data()); | ||||
|         if (result.Failed()) { | ||||
|             LOG_ERROR(Service_AM, "CIA file installation aborted with error code {:08x}", | ||||
|                       result.Code().raw); | ||||
|  | @ -1464,9 +1465,9 @@ public: | |||
|         return file->backend->Read(offset + file_offset, length, buffer); | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override { | ||||
|         return file->backend->Write(offset + file_offset, length, flush, buffer); | ||||
|         return file->backend->Write(offset + file_offset, length, flush, update_timestamp, buffer); | ||||
|     } | ||||
| 
 | ||||
|     u64 GetSize() const override { | ||||
|  | @ -1475,7 +1476,7 @@ public: | |||
|     bool SetSize(u64 size) const override { | ||||
|         return false; | ||||
|     } | ||||
|     bool Close() const override { | ||||
|     bool Close() override { | ||||
|         return false; | ||||
|     } | ||||
|     void Flush() const override {} | ||||
|  |  | |||
|  | @ -111,11 +111,11 @@ public: | |||
|     Result WriteTicket(); | ||||
|     Result WriteTitleMetadata(); | ||||
|     ResultVal<std::size_t> WriteContentData(u64 offset, std::size_t length, const u8* buffer); | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override; | ||||
|     u64 GetSize() const override; | ||||
|     bool SetSize(u64 size) const override; | ||||
|     bool Close() const override; | ||||
|     bool Close() override; | ||||
|     void Flush() const override; | ||||
| 
 | ||||
| private: | ||||
|  | @ -146,11 +146,11 @@ public: | |||
|     ~TicketFile(); | ||||
| 
 | ||||
|     ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override; | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, | ||||
|                                  const u8* buffer) override; | ||||
|     u64 GetSize() const override; | ||||
|     bool SetSize(u64 size) const override; | ||||
|     bool Close() const override; | ||||
|     bool Close() override; | ||||
|     void Flush() const override; | ||||
| 
 | ||||
| private: | ||||
|  |  | |||
|  | @ -217,7 +217,7 @@ bool Module::LoadSharedFont() { | |||
|     const FileSys::Path file_path(std::vector<u8>(20, 0)); | ||||
|     FileSys::Mode open_mode = {}; | ||||
|     open_mode.read_flag.Assign(1); | ||||
|     auto file_result = archive.OpenFile(file_path, open_mode); | ||||
|     auto file_result = archive.OpenFile(file_path, open_mode, 0); | ||||
|     if (file_result.Failed()) | ||||
|         return false; | ||||
| 
 | ||||
|  |  | |||
|  | @ -75,7 +75,7 @@ Result OnlineService::InitializeSession(u64 init_program_id) { | |||
|         boss_system_save_data_archive = std::move(archive_result).Unwrap(); | ||||
|     } else if (archive_result.Code() == FileSys::ResultNotFound) { | ||||
|         // If the archive didn't exist, create the files inside
 | ||||
|         systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); | ||||
|         systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); | ||||
| 
 | ||||
|         // Open it again to get a valid archive now that the folder exists
 | ||||
|         auto create_archive_result = systemsavedata_factory.Open(archive_path, 0); | ||||
|  |  | |||
|  | @ -116,7 +116,7 @@ void Module::Interface::Open(Kernel::HLERequestContext& ctx) { | |||
|             std::vector<u8> program_id(8); | ||||
|             u64_le le_program_id = cecd->system.Kernel().GetCurrentProcess()->codeset->program_id; | ||||
|             std::memcpy(program_id.data(), &le_program_id, sizeof(u64)); | ||||
|             session_data->file->Write(0, sizeof(u64), true, program_id.data()); | ||||
|             session_data->file->Write(0, sizeof(u64), true, false, program_id.data()); | ||||
|             session_data->file->Close(); | ||||
|         } | ||||
|     } | ||||
|  | @ -373,7 +373,7 @@ void Module::Interface::Write(Kernel::HLERequestContext& ctx) { | |||
|         } | ||||
| 
 | ||||
|         [[maybe_unused]] const u32 bytes_written = static_cast<u32>( | ||||
|             session_data->file->Write(0, buffer.size(), true, buffer.data()).Unwrap()); | ||||
|             session_data->file->Write(0, buffer.size(), true, false, buffer.data()).Unwrap()); | ||||
|         session_data->file->Close(); | ||||
| 
 | ||||
|         rb.Push(ResultSuccess); | ||||
|  | @ -435,7 +435,7 @@ void Module::Interface::WriteMessage(Kernel::HLERequestContext& ctx) { | |||
|                   msg_header.forward_count, msg_header.user_data); | ||||
| 
 | ||||
|         [[maybe_unused]] const u32 bytes_written = | ||||
|             static_cast<u32>(message->Write(0, buffer_size, true, buffer.data()).Unwrap()); | ||||
|             static_cast<u32>(message->Write(0, buffer_size, true, false, buffer.data()).Unwrap()); | ||||
|         message->Close(); | ||||
| 
 | ||||
|         rb.Push(ResultSuccess); | ||||
|  | @ -522,7 +522,7 @@ void Module::Interface::WriteMessageWithHMAC(Kernel::HLERequestContext& ctx) { | |||
|         std::memcpy(buffer.data() + hmac_offset, hmac_digest.data(), hmac_size); | ||||
| 
 | ||||
|         [[maybe_unused]] const u32 bytes_written = | ||||
|             static_cast<u32>(message->Write(0, buffer_size, true, buffer.data()).Unwrap()); | ||||
|             static_cast<u32>(message->Write(0, buffer_size, true, false, buffer.data()).Unwrap()); | ||||
|         message->Close(); | ||||
| 
 | ||||
|         rb.Push(ResultSuccess); | ||||
|  | @ -607,7 +607,7 @@ void Module::Interface::SetData(Kernel::HLERequestContext& ctx) { | |||
| 
 | ||||
|             cecd->CheckAndUpdateFile(CecDataPathType::OutboxIndex, ncch_program_id, buffer); | ||||
| 
 | ||||
|             file->Write(0, buffer.size(), true, buffer.data()); | ||||
|             file->Write(0, buffer.size(), true, false, buffer.data()); | ||||
|             file->Close(); | ||||
|         } | ||||
|     } | ||||
|  | @ -764,8 +764,8 @@ void Module::Interface::OpenAndWrite(Kernel::HLERequestContext& ctx) { | |||
|                 cecd->CheckAndUpdateFile(path_type, ncch_program_id, buffer); | ||||
|             } | ||||
| 
 | ||||
|             [[maybe_unused]] const u32 bytes_written = | ||||
|                 static_cast<u32>(file->Write(0, buffer.size(), true, buffer.data()).Unwrap()); | ||||
|             [[maybe_unused]] const u32 bytes_written = static_cast<u32>( | ||||
|                 file->Write(0, buffer.size(), true, false, buffer.data()).Unwrap()); | ||||
|             file->Close(); | ||||
| 
 | ||||
|             rb.Push(ResultSuccess); | ||||
|  | @ -1409,7 +1409,7 @@ Module::Module(Core::System& system) : system(system) { | |||
|         cecd_system_save_data_archive = std::move(archive_result).Unwrap(); | ||||
|     } else { | ||||
|         // Format the archive to create the directories
 | ||||
|         systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); | ||||
|         systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); | ||||
| 
 | ||||
|         // Open it again to get a valid archive now that the folder exists
 | ||||
|         cecd_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap(); | ||||
|  | @ -1442,7 +1442,7 @@ Module::Module(Core::System& system) : system(system) { | |||
|         eventlog_buffer[1] = 0x41; | ||||
|         eventlog_buffer[2] = 0x12; | ||||
| 
 | ||||
|         eventlog->Write(0, eventlog_size, true, eventlog_buffer.data()); | ||||
|         eventlog->Write(0, eventlog_size, true, false, eventlog_buffer.data()); | ||||
|         eventlog->Close(); | ||||
| 
 | ||||
|         /// MBoxList____ resides within the root CEC/ directory.
 | ||||
|  | @ -1464,7 +1464,7 @@ Module::Module(Core::System& system) : system(system) { | |||
|         // mboxlist_buffer[2-3] are already zeroed
 | ||||
|         mboxlist_buffer[4] = 0x01; | ||||
| 
 | ||||
|         mboxlist->Write(0, mboxlist_size, true, mboxlist_buffer.data()); | ||||
|         mboxlist->Write(0, mboxlist_size, true, false, mboxlist_buffer.data()); | ||||
|         mboxlist->Close(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -565,7 +565,7 @@ Result Module::UpdateConfigNANDSavegame() { | |||
|     ASSERT_MSG(config_result.Succeeded(), "could not open file"); | ||||
| 
 | ||||
|     auto config = std::move(config_result).Unwrap(); | ||||
|     config->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data()); | ||||
|     config->Write(0, CONFIG_SAVEFILE_SIZE, true, false, cfg_config_file_buffer.data()); | ||||
| 
 | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | @ -625,7 +625,7 @@ Result Module::LoadConfigNANDSaveFile() { | |||
|     // If the archive didn't exist, create the files inside
 | ||||
|     if (archive_result.Code() == FileSys::ResultNotFound) { | ||||
|         // Format the archive to create the directories
 | ||||
|         systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); | ||||
|         systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); | ||||
| 
 | ||||
|         // Open it again to get a valid archive now that the folder exists
 | ||||
|         cfg_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap(); | ||||
|  |  | |||
|  | @ -67,10 +67,24 @@ ResultVal<ArchiveHandle> ArchiveManager::OpenArchive(ArchiveIdCode id_code, | |||
| } | ||||
| 
 | ||||
| Result ArchiveManager::CloseArchive(ArchiveHandle handle) { | ||||
|     if (handle_map.erase(handle) == 0) | ||||
|     auto itr = handle_map.find(handle); | ||||
|     if (itr != handle_map.end()) { | ||||
|         itr->second->Close(); | ||||
|     } else { | ||||
|         return FileSys::ResultInvalidArchiveHandle; | ||||
|     else | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|     handle_map.erase(itr); | ||||
|     return ResultSuccess; | ||||
| } | ||||
| 
 | ||||
| Result ArchiveManager::ControlArchive(ArchiveHandle handle, u32 action, u8* input, | ||||
|                                       size_t input_size, u8* output, size_t output_size) { | ||||
|     auto itr = handle_map.find(handle); | ||||
|     if (itr != handle_map.end()) { | ||||
|         return itr->second->Control(action, input, input_size, output, output_size); | ||||
|     } else { | ||||
|         return FileSys::ResultInvalidArchiveHandle; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // TODO(yuriks): This might be what the fs:REG service is for. See the Register/Unregister calls in
 | ||||
|  | @ -90,14 +104,14 @@ Result ArchiveManager::RegisterArchiveType(std::unique_ptr<FileSys::ArchiveFacto | |||
| 
 | ||||
| std::pair<ResultVal<std::shared_ptr<File>>, std::chrono::nanoseconds> | ||||
| ArchiveManager::OpenFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path, | ||||
|                                     const FileSys::Mode mode) { | ||||
|                                     const FileSys::Mode mode, u32 attributes) { | ||||
|     ArchiveBackend* archive = GetArchive(archive_handle); | ||||
|     if (archive == nullptr) { | ||||
|         return std::make_pair(FileSys::ResultInvalidArchiveHandle, std::chrono::nanoseconds{0}); | ||||
|     } | ||||
| 
 | ||||
|     const std::chrono::nanoseconds open_timeout_ns{archive->GetOpenDelayNs()}; | ||||
|     auto backend = archive->OpenFile(path, mode); | ||||
|     auto backend = archive->OpenFile(path, mode, attributes); | ||||
|     if (backend.Failed()) { | ||||
|         return std::make_pair(backend.Code(), open_timeout_ns); | ||||
|     } | ||||
|  | @ -151,21 +165,21 @@ Result ArchiveManager::DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archi | |||
| } | ||||
| 
 | ||||
| Result ArchiveManager::CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, | ||||
|                                            u64 file_size) { | ||||
|                                            u64 file_size, u32 attributes) { | ||||
|     ArchiveBackend* archive = GetArchive(archive_handle); | ||||
|     if (archive == nullptr) | ||||
|         return FileSys::ResultInvalidArchiveHandle; | ||||
| 
 | ||||
|     return archive->CreateFile(path, file_size); | ||||
|     return archive->CreateFile(path, file_size, attributes); | ||||
| } | ||||
| 
 | ||||
| Result ArchiveManager::CreateDirectoryFromArchive(ArchiveHandle archive_handle, | ||||
|                                                   const FileSys::Path& path) { | ||||
|                                                   const FileSys::Path& path, u32 attributes) { | ||||
|     ArchiveBackend* archive = GetArchive(archive_handle); | ||||
|     if (archive == nullptr) | ||||
|         return FileSys::ResultInvalidArchiveHandle; | ||||
| 
 | ||||
|     return archive->CreateDirectory(path); | ||||
|     return archive->CreateDirectory(path, attributes); | ||||
| } | ||||
| 
 | ||||
| Result ArchiveManager::RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, | ||||
|  | @ -210,13 +224,15 @@ ResultVal<u64> ArchiveManager::GetFreeBytesInArchive(ArchiveHandle archive_handl | |||
| 
 | ||||
| Result ArchiveManager::FormatArchive(ArchiveIdCode id_code, | ||||
|                                      const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                      const FileSys::Path& path, u64 program_id) { | ||||
|                                      const FileSys::Path& path, u64 program_id, | ||||
|                                      u32 directory_buckets, u32 file_buckets) { | ||||
|     auto archive_itr = id_code_map.find(id_code); | ||||
|     if (archive_itr == id_code_map.end()) { | ||||
|         return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
 | ||||
|     } | ||||
| 
 | ||||
|     return archive_itr->second->Format(path, format_info, program_id); | ||||
|     return archive_itr->second->Format(path, format_info, program_id, directory_buckets, | ||||
|                                        file_buckets); | ||||
| } | ||||
| 
 | ||||
| ResultVal<FileSys::ArchiveFormatInfo> ArchiveManager::GetArchiveFormatInfo( | ||||
|  | @ -229,10 +245,10 @@ ResultVal<FileSys::ArchiveFormatInfo> ArchiveManager::GetArchiveFormatInfo( | |||
|     return archive->second->GetFormatInfo(archive_path, program_id); | ||||
| } | ||||
| 
 | ||||
| Result ArchiveManager::CreateExtSaveData(MediaType media_type, u32 high, u32 low, | ||||
| Result ArchiveManager::CreateExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low, | ||||
|                                          std::span<const u8> smdh_icon, | ||||
|                                          const FileSys::ArchiveFormatInfo& format_info, | ||||
|                                          u64 program_id) { | ||||
|                                          u64 program_id, u64 total_size) { | ||||
|     // Construct the binary path to the archive first
 | ||||
|     FileSys::Path path = | ||||
|         FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low); | ||||
|  | @ -246,37 +262,26 @@ Result ArchiveManager::CreateExtSaveData(MediaType media_type, u32 high, u32 low | |||
| 
 | ||||
|     auto ext_savedata = static_cast<FileSys::ArchiveFactory_ExtSaveData*>(archive->second.get()); | ||||
| 
 | ||||
|     Result result = ext_savedata->Format(path, format_info, program_id); | ||||
|     Result result = ext_savedata->FormatAsExtData(path, format_info, unknown, program_id, | ||||
|                                                   total_size, smdh_icon); | ||||
|     if (result.IsError()) { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     ext_savedata->WriteIcon(path, smdh_icon); | ||||
|     return ResultSuccess; | ||||
| } | ||||
| 
 | ||||
| Result ArchiveManager::DeleteExtSaveData(MediaType media_type, u32 high, u32 low) { | ||||
|     // Construct the binary path to the archive first
 | ||||
|     FileSys::Path path = | ||||
|         FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low); | ||||
| Result ArchiveManager::DeleteExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low) { | ||||
|     auto archive = id_code_map.find(media_type == MediaType::NAND ? ArchiveIdCode::SharedExtSaveData | ||||
|                                                                   : ArchiveIdCode::ExtSaveData); | ||||
| 
 | ||||
|     std::string media_type_directory; | ||||
|     if (media_type == MediaType::NAND) { | ||||
|         media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); | ||||
|     } else if (media_type == MediaType::SDMC) { | ||||
|         media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); | ||||
|     } else { | ||||
|         LOG_ERROR(Service_FS, "Unsupported media type {}", media_type); | ||||
|         return ResultUnknown; // TODO(Subv): Find the right error code
 | ||||
|     if (archive == id_code_map.end()) { | ||||
|         return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
 | ||||
|     } | ||||
| 
 | ||||
|     // Delete all directories (/user, /boss) and the icon file.
 | ||||
|     std::string base_path = | ||||
|         FileSys::GetExtDataContainerPath(media_type_directory, media_type == MediaType::NAND); | ||||
|     std::string extsavedata_path = FileSys::GetExtSaveDataPath(base_path, path); | ||||
|     if (FileUtil::Exists(extsavedata_path) && !FileUtil::DeleteDirRecursively(extsavedata_path)) | ||||
|         return ResultUnknown; // TODO(Subv): Find the right error code
 | ||||
|     return ResultSuccess; | ||||
|     auto ext_savedata = static_cast<FileSys::ArchiveFactory_ExtSaveData*>(archive->second.get()); | ||||
| 
 | ||||
|     return ext_savedata->DeleteExtData(media_type, unknown, high, low); | ||||
| } | ||||
| 
 | ||||
| Result ArchiveManager::DeleteSystemSaveData(u32 high, u32 low) { | ||||
|  | @ -317,6 +322,24 @@ ResultVal<ArchiveResource> ArchiveManager::GetArchiveResource(MediaType media_ty | |||
|     return resource; | ||||
| } | ||||
| 
 | ||||
| Result ArchiveManager::SetSaveDataSecureValue(ArchiveHandle archive_handle, u32 secure_value_slot, | ||||
|                                               u64 secure_value, bool flush) { | ||||
|     ArchiveBackend* archive = GetArchive(archive_handle); | ||||
|     if (archive == nullptr) { | ||||
|         return FileSys::ResultInvalidArchiveHandle; | ||||
|     } | ||||
|     return archive->SetSaveDataSecureValue(secure_value_slot, secure_value, flush); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::tuple<bool, bool, u64>> ArchiveManager::GetSaveDataSecureValue( | ||||
|     ArchiveHandle archive_handle, u32 secure_value_slot) { | ||||
|     ArchiveBackend* archive = GetArchive(archive_handle); | ||||
|     if (archive == nullptr) { | ||||
|         return FileSys::ResultInvalidArchiveHandle; | ||||
|     } | ||||
|     return archive->GetSaveDataSecureValue(secure_value_slot); | ||||
| } | ||||
| 
 | ||||
| void ArchiveManager::RegisterArchiveTypes() { | ||||
|     // TODO(Subv): Add the other archive types (see here for the known types:
 | ||||
|     // http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
 | ||||
|  | @ -337,7 +360,7 @@ void ArchiveManager::RegisterArchiveTypes() { | |||
|                   sdmc_directory); | ||||
| 
 | ||||
|     // Create the SaveData archive
 | ||||
|     auto sd_savedata_source = std::make_shared<FileSys::ArchiveSource_SDSaveData>(sdmc_directory); | ||||
|     sd_savedata_source = std::make_shared<FileSys::ArchiveSource_SDSaveData>(sdmc_directory); | ||||
|     auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sd_savedata_source); | ||||
|     RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); | ||||
|     auto other_savedata_permitted_factory = | ||||
|  | @ -373,6 +396,23 @@ void ArchiveManager::RegisterArchiveTypes() { | |||
|     RegisterArchiveType(std::move(selfncch_factory), ArchiveIdCode::SelfNCCH); | ||||
| } | ||||
| 
 | ||||
| bool ArchiveManager::ArchiveIsSlow(ArchiveIdCode archive_id) { | ||||
|     auto itr = id_code_map.find(archive_id); | ||||
|     if (itr == id_code_map.end() || itr->second.get() == nullptr) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return itr->second->IsSlow(); | ||||
| } | ||||
| 
 | ||||
| bool ArchiveManager::ArchiveIsSlow(ArchiveHandle archive_handle) { | ||||
|     ArchiveBackend* archive = GetArchive(archive_handle); | ||||
|     if (archive == nullptr) { | ||||
|         return false; | ||||
|     } | ||||
|     return archive->IsSlow(); | ||||
| } | ||||
| 
 | ||||
| void ArchiveManager::RegisterSelfNCCH(Loader::AppLoader& app_loader) { | ||||
|     auto itr = id_code_map.find(ArchiveIdCode::SelfNCCH); | ||||
|     if (itr == id_code_map.end()) { | ||||
|  | @ -385,6 +425,35 @@ void ArchiveManager::RegisterSelfNCCH(Loader::AppLoader& app_loader) { | |||
|     factory->Register(app_loader); | ||||
| } | ||||
| 
 | ||||
| void ArchiveManager::RegisterArticSaveDataSource( | ||||
|     std::shared_ptr<Network::ArticBase::Client>& client) { | ||||
|     if (!sd_savedata_source.get()) { | ||||
|         LOG_ERROR(Service_FS, "Could not register artic save data source."); | ||||
|         return; | ||||
|     } | ||||
|     sd_savedata_source->RegisterArtic(client); | ||||
| } | ||||
| 
 | ||||
| void ArchiveManager::RegisterArticExtData(std::shared_ptr<Network::ArticBase::Client>& client) { | ||||
|     for (auto it : {ArchiveIdCode::ExtSaveData, ArchiveIdCode::SharedExtSaveData, | ||||
|                     ArchiveIdCode::BossExtSaveData}) { | ||||
|         auto itr = id_code_map.find(it); | ||||
|         if (itr == id_code_map.end() || itr->second.get() == nullptr) { | ||||
|             continue; | ||||
|         } | ||||
|         reinterpret_cast<FileSys::ArchiveFactory_ExtSaveData*>(itr->second.get()) | ||||
|             ->RegisterArtic(client); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ArchiveManager::RegisterArticNCCH(std::shared_ptr<Network::ArticBase::Client>& client) { | ||||
|     auto itr = id_code_map.find(ArchiveIdCode::NCCH); | ||||
|     if (itr == id_code_map.end() || itr->second.get() == nullptr) { | ||||
|         return; | ||||
|     } | ||||
|     reinterpret_cast<FileSys::ArchiveFactory_NCCH*>(itr->second.get())->RegisterArtic(client); | ||||
| } | ||||
| 
 | ||||
| ArchiveManager::ArchiveManager(Core::System& system) : system(system) { | ||||
|     RegisterArchiveTypes(); | ||||
| } | ||||
|  |  | |||
|  | @ -12,9 +12,11 @@ | |||
| #include <boost/serialization/unordered_map.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/archive_backend.h" | ||||
| #include "core/file_sys/archive_source_sd_savedata.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/fs/directory.h" | ||||
| #include "core/hle/service/fs/file.h" | ||||
| #include "network/artic_base/artic_base_client.h" | ||||
| 
 | ||||
| /// The unique system identifier hash, also known as ID0
 | ||||
| static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"}; | ||||
|  | @ -67,6 +69,19 @@ struct ArchiveResource { | |||
| }; | ||||
| static_assert(sizeof(ArchiveResource) == 0x10, "ArchiveResource has incorrect size"); | ||||
| 
 | ||||
| struct ExtSaveDataInfo { | ||||
|     u8 media_type; | ||||
|     u8 unknown; | ||||
|     u16 reserved1; | ||||
|     u32 save_id_low; | ||||
|     u32 save_id_high; | ||||
|     u32 reserved2; | ||||
| }; | ||||
| static_assert(sizeof(ExtSaveDataInfo) == 0x10, "ExtSaveDataInfo struct has incorrect size"); | ||||
| static_assert(std::is_trivial<ExtSaveDataInfo>(), "ExtSaveDataInfo should be trivial"); | ||||
| static_assert(std::is_trivially_copyable<ExtSaveDataInfo>(), | ||||
|               "ExtSaveDataInfo should be trivially copyable"); | ||||
| 
 | ||||
| using FileSys::ArchiveBackend; | ||||
| using FileSys::ArchiveFactory; | ||||
| 
 | ||||
|  | @ -90,6 +105,9 @@ public: | |||
|      */ | ||||
|     Result CloseArchive(ArchiveHandle handle); | ||||
| 
 | ||||
|     Result ControlArchive(ArchiveHandle handle, u32 action, u8* input, size_t input_size, | ||||
|                           u8* output, size_t output_size); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Open a File from an Archive | ||||
|      * @param archive_handle Handle to an open Archive object | ||||
|  | @ -98,7 +116,8 @@ public: | |||
|      * @return Pair containing the opened File object and the open delay | ||||
|      */ | ||||
|     std::pair<ResultVal<std::shared_ptr<File>>, std::chrono::nanoseconds> OpenFileFromArchive( | ||||
|         ArchiveHandle archive_handle, const FileSys::Path& path, FileSys::Mode mode); | ||||
|         ArchiveHandle archive_handle, const FileSys::Path& path, FileSys::Mode mode, | ||||
|         u32 attributes); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Delete a File from an Archive | ||||
|  | @ -146,7 +165,7 @@ public: | |||
|      * @return File creation result code | ||||
|      */ | ||||
|     Result CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, | ||||
|                                u64 file_size); | ||||
|                                u64 file_size, u32 attributes); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Create a Directory from an Archive | ||||
|  | @ -154,7 +173,8 @@ public: | |||
|      * @param path Path to the Directory inside of the Archive | ||||
|      * @return Whether creation of directory succeeded | ||||
|      */ | ||||
|     Result CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path); | ||||
|     Result CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path, | ||||
|                                       u32 attributes); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Rename a Directory between two Archives | ||||
|  | @ -195,7 +215,8 @@ public: | |||
|      * @return Result 0 on success or the corresponding code on error | ||||
|      */ | ||||
|     Result FormatArchive(ArchiveIdCode id_code, const FileSys::ArchiveFormatInfo& format_info, | ||||
|                          const FileSys::Path& path, u64 program_id); | ||||
|                          const FileSys::Path& path, u64 program_id, u32 directory_buckets, | ||||
|                          u32 file_buckets); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Retrieves the format info about the archive of the specified type and path. | ||||
|  | @ -219,8 +240,10 @@ public: | |||
|      * @param program_id the program ID of the client that requests the operation | ||||
|      * @return Result 0 on success or the corresponding code on error | ||||
|      */ | ||||
|     Result CreateExtSaveData(MediaType media_type, u32 high, u32 low, std::span<const u8> smdh_icon, | ||||
|                              const FileSys::ArchiveFormatInfo& format_info, u64 program_id); | ||||
|     Result CreateExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low, | ||||
|                              std::span<const u8> smdh_icon, | ||||
|                              const FileSys::ArchiveFormatInfo& format_info, u64 program_id, | ||||
|                              u64 total_size); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Deletes the SharedExtSaveData archive for the specified extdata ID | ||||
|  | @ -229,7 +252,7 @@ public: | |||
|      * @param low The low word of the extdata id to delete | ||||
|      * @return Result 0 on success or the corresponding code on error | ||||
|      */ | ||||
|     Result DeleteExtSaveData(MediaType media_type, u32 high, u32 low); | ||||
|     Result DeleteExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Deletes the SystemSaveData archive folder for the specified save data id | ||||
|  | @ -254,9 +277,25 @@ public: | |||
|      */ | ||||
|     ResultVal<ArchiveResource> GetArchiveResource(MediaType media_type) const; | ||||
| 
 | ||||
|     Result SetSaveDataSecureValue(ArchiveHandle archive_handle, u32 secure_value_slot, | ||||
|                                   u64 secure_value, bool flush); | ||||
| 
 | ||||
|     ResultVal<std::tuple<bool, bool, u64>> GetSaveDataSecureValue(ArchiveHandle archive_handle, | ||||
|                                                                   u32 secure_value_slot); | ||||
| 
 | ||||
|     bool ArchiveIsSlow(ArchiveIdCode archive_id); | ||||
| 
 | ||||
|     bool ArchiveIsSlow(ArchiveHandle archive_handle); | ||||
| 
 | ||||
|     /// Registers a new NCCH file with the SelfNCCH archive factory
 | ||||
|     void RegisterSelfNCCH(Loader::AppLoader& app_loader); | ||||
| 
 | ||||
|     void RegisterArticSaveDataSource(std::shared_ptr<Network::ArticBase::Client>& client); | ||||
| 
 | ||||
|     void RegisterArticExtData(std::shared_ptr<Network::ArticBase::Client>& client); | ||||
| 
 | ||||
|     void RegisterArticNCCH(std::shared_ptr<Network::ArticBase::Client>& client); | ||||
| 
 | ||||
| private: | ||||
|     Core::System& system; | ||||
| 
 | ||||
|  | @ -285,11 +324,17 @@ private: | |||
|     std::unordered_map<ArchiveHandle, std::unique_ptr<ArchiveBackend>> handle_map; | ||||
|     ArchiveHandle next_handle = 1; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Savedata source | ||||
|      */ | ||||
|     std::shared_ptr<FileSys::ArchiveSource_SDSaveData> sd_savedata_source; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& id_code_map; | ||||
|         ar& handle_map; | ||||
|         ar& next_handle; | ||||
|         ar& sd_savedata_source; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
|  |  | |||
|  | @ -169,10 +169,9 @@ void File::Write(Kernel::HLERequestContext& ctx) { | |||
|     IPC::RequestParser rp(ctx); | ||||
|     u64 offset = rp.Pop<u64>(); | ||||
|     u32 length = rp.Pop<u32>(); | ||||
|     u32 flush = rp.Pop<u32>(); | ||||
|     auto& buffer = rp.PopMappedBuffer(); | ||||
|     LOG_TRACE(Service_FS, "Write {}: offset=0x{:x} length={}, flush=0x{:x}", GetName(), offset, | ||||
|               length, flush); | ||||
|     u32 flags = rp.Pop<u32>(); | ||||
|     LOG_TRACE(Service_FS, "Write {}: offset=0x{:x} length={}, flags=0x{:x}", GetName(), offset, | ||||
|               length, flags); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); | ||||
| 
 | ||||
|  | @ -182,25 +181,75 @@ void File::Write(Kernel::HLERequestContext& ctx) { | |||
|     if (file->subfile) { | ||||
|         rb.Push(FileSys::ResultUnsupportedOpenFlags); | ||||
|         rb.Push<u32>(0); | ||||
|         rb.PushMappedBuffer(rp.PopMappedBuffer()); | ||||
|         return; | ||||
|     } | ||||
|     bool flush = (flags & 0xFF) != 0, update_timestamp = (flags & 0xFF00) != 0; | ||||
| 
 | ||||
|     if (!backend->AllowsCachedReads()) { | ||||
|         std::vector<u8> data(length); | ||||
|         auto& buffer = rp.PopMappedBuffer(); | ||||
|         buffer.Read(data.data(), 0, data.size()); | ||||
|         ResultVal<std::size_t> written = | ||||
|             backend->Write(offset, data.size(), flush, update_timestamp, data.data()); | ||||
| 
 | ||||
|         // Update file size
 | ||||
|         file->size = backend->GetSize(); | ||||
| 
 | ||||
|         if (written.Failed()) { | ||||
|             rb.Push(written.Code()); | ||||
|             rb.Push<u32>(0); | ||||
|         } else { | ||||
|             rb.Push(ResultSuccess); | ||||
|             rb.Push<u32>(static_cast<u32>(*written)); | ||||
|         } | ||||
|         rb.PushMappedBuffer(buffer); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<u8> data(length); | ||||
|     buffer.Read(data.data(), 0, data.size()); | ||||
|     ResultVal<std::size_t> written = backend->Write(offset, data.size(), flush != 0, data.data()); | ||||
|     struct AsyncData { | ||||
|         // Input
 | ||||
|         u32 length; | ||||
|         u64 offset; | ||||
|         bool flush; | ||||
|         bool update_timestamp; | ||||
|         Kernel::MappedBuffer* buffer; | ||||
|         FileSessionSlot* file; | ||||
| 
 | ||||
|     // Update file size
 | ||||
|     file->size = backend->GetSize(); | ||||
|         // Output
 | ||||
|         ResultVal<std::size_t> written; | ||||
|     }; | ||||
|     auto async_data = std::make_shared<AsyncData>(); | ||||
|     async_data->length = length; | ||||
|     async_data->offset = offset; | ||||
|     async_data->flush = flush; | ||||
|     async_data->update_timestamp = update_timestamp; | ||||
|     async_data->buffer = &rp.PopMappedBuffer(); | ||||
|     async_data->file = file; | ||||
| 
 | ||||
|     if (written.Failed()) { | ||||
|         rb.Push(written.Code()); | ||||
|         rb.Push<u32>(0); | ||||
|     } else { | ||||
|         rb.Push(ResultSuccess); | ||||
|         rb.Push<u32>(static_cast<u32>(*written)); | ||||
|     } | ||||
|     rb.PushMappedBuffer(buffer); | ||||
|     ctx.RunAsync( | ||||
|         [this, async_data](Kernel::HLERequestContext& ctx) { | ||||
|             std::vector<u8> data(async_data->length); | ||||
|             async_data->buffer->Read(data.data(), 0, data.size()); | ||||
|             async_data->written = backend->Write(async_data->offset, data.size(), async_data->flush, | ||||
|                                                  async_data->update_timestamp, data.data()); | ||||
| 
 | ||||
|             // Update file size
 | ||||
|             async_data->file->size = backend->GetSize(); | ||||
|             return 0; | ||||
|         }, | ||||
|         [async_data](Kernel::HLERequestContext& ctx) { | ||||
|             IPC::RequestBuilder rb(ctx, 2, 2); | ||||
|             if (async_data->written.Failed()) { | ||||
|                 rb.Push(async_data->written.Code()); | ||||
|                 rb.Push<u32>(0); | ||||
|             } else { | ||||
|                 rb.Push(ResultSuccess); | ||||
|                 rb.Push<u32>(static_cast<u32>(*async_data->written)); | ||||
|             } | ||||
|             rb.PushMappedBuffer(*async_data->buffer); | ||||
|         }, | ||||
|         true); | ||||
| } | ||||
| 
 | ||||
| void File::GetSize(Kernel::HLERequestContext& ctx) { | ||||
|  | @ -219,17 +268,32 @@ void File::SetSize(Kernel::HLERequestContext& ctx) { | |||
| 
 | ||||
|     FileSessionSlot* file = GetSessionData(ctx.Session()); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
| 
 | ||||
|     // SetSize can not be called on subfiles.
 | ||||
|     if (file->subfile) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(FileSys::ResultUnsupportedOpenFlags); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     file->size = size; | ||||
|     backend->SetSize(size); | ||||
|     rb.Push(ResultSuccess); | ||||
|     if (!backend->AllowsCachedReads()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         file->size = size; | ||||
|         backend->SetSize(size); | ||||
|         rb.Push(ResultSuccess); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ctx.RunAsync( | ||||
|         [file, size, this](Kernel::HLERequestContext& ctx) { | ||||
|             file->size = size; | ||||
|             backend->SetSize(size); | ||||
|             return 0; | ||||
|         }, | ||||
|         [](Kernel::HLERequestContext& ctx) { | ||||
|             IPC::RequestBuilder rb(ctx, 1, 0); | ||||
|             rb.Push(ResultSuccess); | ||||
|         }, | ||||
|         true); | ||||
| } | ||||
| 
 | ||||
| void File::Close(Kernel::HLERequestContext& ctx) { | ||||
|  | @ -240,26 +304,53 @@ void File::Close(Kernel::HLERequestContext& ctx) { | |||
|         LOG_WARNING(Service_FS, "Closing File backend but {} clients still connected", | ||||
|                     connected_sessions.size()); | ||||
| 
 | ||||
|     backend->Close(); | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(ResultSuccess); | ||||
|     if (!backend->AllowsCachedReads()) { | ||||
|         backend->Close(); | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultSuccess); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ctx.RunAsync( | ||||
|         [this](Kernel::HLERequestContext& ctx) { | ||||
|             backend->Close(); | ||||
|             return 0; | ||||
|         }, | ||||
|         [](Kernel::HLERequestContext& ctx) { | ||||
|             IPC::RequestBuilder rb(ctx, 1, 0); | ||||
|             rb.Push(ResultSuccess); | ||||
|         }, | ||||
|         true); | ||||
| } | ||||
| 
 | ||||
| void File::Flush(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
| 
 | ||||
|     const FileSessionSlot* file = GetSessionData(ctx.Session()); | ||||
| 
 | ||||
|     // Subfiles can not be flushed.
 | ||||
|     if (file->subfile) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(FileSys::ResultUnsupportedOpenFlags); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     backend->Flush(); | ||||
|     rb.Push(ResultSuccess); | ||||
|     if (!backend->AllowsCachedReads()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         backend->Flush(); | ||||
|         rb.Push(ResultSuccess); | ||||
|     } | ||||
| 
 | ||||
|     ctx.RunAsync( | ||||
|         [this](Kernel::HLERequestContext& ctx) { | ||||
|             backend->Flush(); | ||||
|             return 0; | ||||
|         }, | ||||
|         [](Kernel::HLERequestContext& ctx) { | ||||
|             IPC::RequestBuilder rb(ctx, 1, 0); | ||||
|             rb.Push(ResultSuccess); | ||||
|         }, | ||||
|         true); | ||||
| } | ||||
| 
 | ||||
| void File::SetPriority(Kernel::HLERequestContext& ctx) { | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -9,6 +9,7 @@ | |||
| #include <boost/serialization/base_object.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/errors.h" | ||||
| #include "core/file_sys/secure_value_backend.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
|  | @ -77,6 +78,10 @@ public: | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void RegisterSecureValueBackend(const std::shared_ptr<FileSys::SecureValueBackend>& backend) { | ||||
|         secure_value_backend = backend; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     void Initialize(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|  | @ -657,6 +662,21 @@ private: | |||
|      */ | ||||
|     void ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|     /**
 | ||||
|      * FS_User::ControlSecureSave service function | ||||
|      *  Inputs: | ||||
|      *      1 : Action | ||||
|      *      2 : Input Size | ||||
|      *      3 : Output Size | ||||
|      *      4 : (Input Size << 4) | 0xA | ||||
|      *      5 : Input Pointer | ||||
|      *      6 : (Output Size << 4) | 0xC | ||||
|      *      7 : Output Pointer | ||||
|      *  Outputs: | ||||
|      *      1 : Result of function, 0 on success, otherwise error code | ||||
|      */ | ||||
|     void ControlSecureSave(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|     /**
 | ||||
|      * FS_User::SetThisSaveDataSecureValue service function. | ||||
|      *  Inputs: | ||||
|  | @ -722,11 +742,10 @@ private: | |||
|     Core::System& system; | ||||
|     ArchiveManager& archives; | ||||
| 
 | ||||
|     std::shared_ptr<FileSys::SecureValueBackend> secure_value_backend; | ||||
| 
 | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this); | ||||
|         ar& priority; | ||||
|     } | ||||
|     void serialize(Archive& ar, const unsigned int); | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1969,7 +1969,7 @@ void HTTP_C::DecryptClCertA() { | |||
|         FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::RomFS, exefs_filepath); | ||||
|     FileSys::Mode open_mode = {}; | ||||
|     open_mode.read_flag.Assign(1); | ||||
|     auto file_result = archive.OpenFile(file_path, open_mode); | ||||
|     auto file_result = archive.OpenFile(file_path, open_mode, 0); | ||||
|     if (file_result.Failed()) { | ||||
|         LOG_ERROR(Service_HTTP, "ClCertA file missing"); | ||||
|         return; | ||||
|  |  | |||
|  | @ -151,7 +151,7 @@ void Module::Interface::ResetNotifications(Kernel::HLERequestContext& ctx) { | |||
|     FileSys::Path archive_path(news_system_savedata_id); | ||||
| 
 | ||||
|     // Format the SystemSaveData archive 0x00010035
 | ||||
|     systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); | ||||
|     systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); | ||||
| 
 | ||||
|     news->news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap(); | ||||
| 
 | ||||
|  | @ -655,7 +655,7 @@ Result Module::LoadNewsDBSavedata() { | |||
|     // If the archive didn't exist, create the files inside
 | ||||
|     if (archive_result.Code() == FileSys::ResultNotFound) { | ||||
|         // Format the archive to create the directories
 | ||||
|         systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); | ||||
|         systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); | ||||
| 
 | ||||
|         // Open it again to get a valid archive now that the folder exists
 | ||||
|         news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap(); | ||||
|  | @ -722,7 +722,7 @@ Result Module::SaveFileToSavedata(std::string filename, std::span<const u8> buff | |||
|     ASSERT_MSG(result.Succeeded(), "could not open file"); | ||||
| 
 | ||||
|     auto file = std::move(result).Unwrap(); | ||||
|     file->Write(0, buffer.size(), 1, buffer.data()); | ||||
|     file->Write(0, buffer.size(), true, false, buffer.data()); | ||||
|     file->Close(); | ||||
| 
 | ||||
|     return ResultSuccess; | ||||
|  |  | |||
|  | @ -158,7 +158,7 @@ static void WriteGameCoinData(GameCoin gamecoin_data) { | |||
|     // If the archive didn't exist, create the files inside
 | ||||
|     if (archive_result.Code() == FileSys::ResultNotFormatted) { | ||||
|         // Format the archive to create the directories
 | ||||
|         extdata_archive_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); | ||||
|         extdata_archive_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); | ||||
|         // Open it again to get a valid archive now that the folder exists
 | ||||
|         archive = extdata_archive_factory.Open(archive_path, 0).Unwrap(); | ||||
|         // Create the game coin file
 | ||||
|  | @ -174,7 +174,8 @@ static void WriteGameCoinData(GameCoin gamecoin_data) { | |||
|     auto gamecoin_result = archive->OpenFile(gamecoin_path, open_mode); | ||||
|     if (gamecoin_result.Succeeded()) { | ||||
|         auto gamecoin = std::move(gamecoin_result).Unwrap(); | ||||
|         gamecoin->Write(0, sizeof(GameCoin), true, reinterpret_cast<const u8*>(&gamecoin_data)); | ||||
|         gamecoin->Write(0, sizeof(GameCoin), true, false, | ||||
|                         reinterpret_cast<const u8*>(&gamecoin_data)); | ||||
|         gamecoin->Close(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ | |||
| #include "core/hle/kernel/shared_memory.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/soc/soc_u.h" | ||||
| #include "network/socket_manager.h" | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| #include <winsock2.h> | ||||
|  | @ -2221,17 +2222,12 @@ SOC_U::SOC_U() : ServiceFramework("soc:U", 18) { | |||
| 
 | ||||
|     RegisterHandlers(functions); | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
|     WSADATA data; | ||||
|     WSAStartup(MAKEWORD(2, 2), &data); | ||||
| #endif | ||||
|     Network::SocketManager::EnableSockets(); | ||||
| } | ||||
| 
 | ||||
| SOC_U::~SOC_U() { | ||||
|     CloseAndDeleteAllSockets(); | ||||
| #ifdef _WIN32 | ||||
|     WSACleanup(); | ||||
| #endif | ||||
|     Network::SocketManager::DisableSockets(); | ||||
| } | ||||
| 
 | ||||
| std::optional<SOC_U::InterfaceInfo> SOC_U::GetDefaultInterfaceInfo() { | ||||
|  |  | |||
							
								
								
									
										564
									
								
								src/core/loader/artic.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										564
									
								
								src/core/loader/artic.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,564 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cstring> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <fmt/format.h> | ||||
| #include "common/literals.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
| #include "common/string_util.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/core.h" | ||||
| #include "core/file_sys/ncch_container.h" | ||||
| #include "core/file_sys/romfs_reader.h" | ||||
| #include "core/file_sys/secure_value_backend_artic.h" | ||||
| #include "core/file_sys/title_metadata.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "core/hle/kernel/resource_limit.h" | ||||
| #include "core/hle/service/am/am.h" | ||||
| #include "core/hle/service/cfg/cfg.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "core/hle/service/fs/fs_user.h" | ||||
| #include "core/loader/artic.h" | ||||
| #include "core/loader/smdh.h" | ||||
| #include "core/memory.h" | ||||
| #include "core/system_titles.h" | ||||
| #include "network/network.h" | ||||
| 
 | ||||
| namespace Loader { | ||||
| 
 | ||||
| using namespace Common::Literals; | ||||
| 
 | ||||
| Apploader_Artic::~Apploader_Artic() { | ||||
|     // TODO(PabloMK7) Find memory leak that prevents the romfs readers being destroyed
 | ||||
|     // when emulation stops. Looks like the mem leak comes from IVFCFile objects
 | ||||
|     // not being destroyed...
 | ||||
|     if (main_romfs_reader) { | ||||
|         static_cast<FileSys::ArticRomFSReader*>(main_romfs_reader.get())->ClearCache(); | ||||
|         static_cast<FileSys::ArticRomFSReader*>(main_romfs_reader.get())->CloseFile(); | ||||
|         main_romfs_reader.reset(); | ||||
|     } | ||||
|     if (update_romfs_reader) { | ||||
|         static_cast<FileSys::ArticRomFSReader*>(update_romfs_reader.get())->ClearCache(); | ||||
|         static_cast<FileSys::ArticRomFSReader*>(update_romfs_reader.get())->CloseFile(); | ||||
|         update_romfs_reader.reset(); | ||||
|     } | ||||
|     client->Stop(); | ||||
| } | ||||
| 
 | ||||
| FileType Apploader_Artic::IdentifyType(FileUtil::IOFile& file) { | ||||
|     return FileType::ARTIC; | ||||
| } | ||||
| 
 | ||||
| std::pair<std::optional<u32>, ResultStatus> Apploader_Artic::LoadCoreVersion() { | ||||
|     if (!is_loaded) { | ||||
|         bool success = LoadExheader(); | ||||
|         if (!success) { | ||||
|             return std::make_pair(std::nullopt, ResultStatus::ErrorArtic); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Provide the core version from the exheader.
 | ||||
|     auto& ncch_caps = program_exheader.arm11_system_local_caps; | ||||
|     return std::make_pair(ncch_caps.core_version, ResultStatus::Success); | ||||
| } | ||||
| 
 | ||||
| std::pair<std::optional<Kernel::MemoryMode>, ResultStatus> Apploader_Artic::LoadKernelMemoryMode() { | ||||
|     if (!is_loaded) { | ||||
|         bool success = LoadExheader(); | ||||
|         if (!success) { | ||||
|             return std::make_pair(std::nullopt, ResultStatus::ErrorArtic); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (memory_mode_override.has_value()) { | ||||
|         return std::make_pair(memory_mode_override, ResultStatus::Success); | ||||
|     } | ||||
| 
 | ||||
|     // Provide the memory mode from the exheader.
 | ||||
|     auto& ncch_caps = program_exheader.arm11_system_local_caps; | ||||
|     auto mode = static_cast<Kernel::MemoryMode>(ncch_caps.system_mode.Value()); | ||||
|     return std::make_pair(mode, ResultStatus::Success); | ||||
| } | ||||
| 
 | ||||
| std::pair<std::optional<Kernel::New3dsHwCapabilities>, ResultStatus> | ||||
| Apploader_Artic::LoadNew3dsHwCapabilities() { | ||||
|     if (!is_loaded) { | ||||
|         bool success = LoadExheader(); | ||||
|         if (!success) { | ||||
|             return std::make_pair(std::nullopt, ResultStatus::ErrorArtic); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Provide the capabilities from the exheader.
 | ||||
|     auto& ncch_caps = program_exheader.arm11_system_local_caps; | ||||
|     auto caps = Kernel::New3dsHwCapabilities{ | ||||
|         ncch_caps.enable_l2_cache != 0, | ||||
|         ncch_caps.enable_804MHz_cpu != 0, | ||||
|         static_cast<Kernel::New3dsMemoryMode>(ncch_caps.n3ds_mode), | ||||
|     }; | ||||
|     return std::make_pair(std::move(caps), ResultStatus::Success); | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process) { | ||||
|     using Kernel::CodeSet; | ||||
| 
 | ||||
|     if (!is_loaded) | ||||
|         return ResultStatus::ErrorNotLoaded; | ||||
| 
 | ||||
|     std::vector<u8> code; | ||||
|     u64_le program_id; | ||||
|     if (ResultStatus::Success == ReadCode(code) && | ||||
|         ResultStatus::Success == ReadProgramId(program_id)) { | ||||
| 
 | ||||
|         std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( | ||||
|             (const char*)program_exheader.codeset_info.name, 8); | ||||
| 
 | ||||
|         std::shared_ptr<CodeSet> codeset = system.Kernel().CreateCodeSet(process_name, program_id); | ||||
| 
 | ||||
|         codeset->CodeSegment().offset = 0; | ||||
|         codeset->CodeSegment().addr = program_exheader.codeset_info.text.address; | ||||
|         codeset->CodeSegment().size = | ||||
|             program_exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE; | ||||
| 
 | ||||
|         codeset->RODataSegment().offset = | ||||
|             codeset->CodeSegment().offset + codeset->CodeSegment().size; | ||||
|         codeset->RODataSegment().addr = program_exheader.codeset_info.ro.address; | ||||
|         codeset->RODataSegment().size = | ||||
|             program_exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE; | ||||
| 
 | ||||
|         // TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just
 | ||||
|         //               to the regular size. Playing it safe for now.
 | ||||
|         u32 bss_page_size = (program_exheader.codeset_info.bss_size + 0xFFF) & ~0xFFF; | ||||
|         code.resize(code.size() + bss_page_size, 0); | ||||
| 
 | ||||
|         codeset->DataSegment().offset = | ||||
|             codeset->RODataSegment().offset + codeset->RODataSegment().size; | ||||
|         codeset->DataSegment().addr = program_exheader.codeset_info.data.address; | ||||
|         codeset->DataSegment().size = | ||||
|             program_exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE + | ||||
|             bss_page_size; | ||||
| 
 | ||||
|         // Apply patches now that the entire codeset (including .bss) has been allocated
 | ||||
|         // const ResultStatus patch_result = overlay_ncch->ApplyCodePatch(code);
 | ||||
|         // if (patch_result != ResultStatus::Success && patch_result != ResultStatus::ErrorNotUsed)
 | ||||
|         //    return patch_result;
 | ||||
| 
 | ||||
|         codeset->entrypoint = codeset->CodeSegment().addr; | ||||
|         codeset->memory = std::move(code); | ||||
| 
 | ||||
|         process = system.Kernel().CreateProcess(std::move(codeset)); | ||||
| 
 | ||||
|         // Attach a resource limit to the process based on the resource limit category
 | ||||
|         const auto category = static_cast<Kernel::ResourceLimitCategory>( | ||||
|             program_exheader.arm11_system_local_caps.resource_limit_category); | ||||
|         process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category); | ||||
| 
 | ||||
|         // When running N3DS-unaware titles pm will lie about the amount of memory available.
 | ||||
|         // This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of
 | ||||
|         // APPLICATION. See:
 | ||||
|         // https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/launch.c#L237
 | ||||
|         auto& ncch_caps = program_exheader.arm11_system_local_caps; | ||||
|         const auto o3ds_mode = *LoadKernelMemoryMode().first; | ||||
|         const auto n3ds_mode = static_cast<Kernel::New3dsMemoryMode>(ncch_caps.n3ds_mode); | ||||
|         const bool is_new_3ds = Settings::values.is_new_3ds.GetValue(); | ||||
|         if (is_new_3ds && n3ds_mode == Kernel::New3dsMemoryMode::Legacy && | ||||
|             category == Kernel::ResourceLimitCategory::Application) { | ||||
|             u64 new_limit = 0; | ||||
|             switch (o3ds_mode) { | ||||
|             case Kernel::MemoryMode::Prod: | ||||
|                 new_limit = 64_MiB; | ||||
|                 break; | ||||
|             case Kernel::MemoryMode::Dev1: | ||||
|                 new_limit = 96_MiB; | ||||
|                 break; | ||||
|             case Kernel::MemoryMode::Dev2: | ||||
|                 new_limit = 80_MiB; | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|             } | ||||
|             process->resource_limit->SetLimitValue(Kernel::ResourceLimitType::Commit, | ||||
|                                                    static_cast<s32>(new_limit)); | ||||
|         } | ||||
| 
 | ||||
|         // Set the default CPU core for this process
 | ||||
|         process->ideal_processor = program_exheader.arm11_system_local_caps.ideal_processor; | ||||
| 
 | ||||
|         // Copy data while converting endianness
 | ||||
|         using KernelCaps = std::array<u32, ExHeader_ARM11_KernelCaps::NUM_DESCRIPTORS>; | ||||
|         KernelCaps kernel_caps; | ||||
|         std::copy_n(program_exheader.arm11_kernel_caps.descriptors, kernel_caps.size(), | ||||
|                     begin(kernel_caps)); | ||||
|         process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); | ||||
| 
 | ||||
|         s32 priority = program_exheader.arm11_system_local_caps.priority; | ||||
|         u32 stack_size = program_exheader.codeset_info.stack_size; | ||||
| 
 | ||||
|         // On real HW this is done with FS:Reg, but we can be lazy
 | ||||
|         auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER"); | ||||
|         fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id, | ||||
|                                      "articbase://"); | ||||
| 
 | ||||
|         Service::FS::FS_USER::ProductInfo product_info{}; | ||||
|         if (LoadProductInfo(product_info) != ResultStatus::Success) { | ||||
|             return ResultStatus::ErrorArtic; | ||||
|         } | ||||
|         fs_user->RegisterProductInfo(process->process_id, product_info); | ||||
| 
 | ||||
|         process->Run(priority, stack_size); | ||||
|         return ResultStatus::Success; | ||||
|     } | ||||
|     return ResultStatus::ErrorArtic; | ||||
| } | ||||
| 
 | ||||
| void Apploader_Artic::ParseRegionLockoutInfo(u64 program_id) { | ||||
|     if (Settings::values.region_value.GetValue() != Settings::REGION_VALUE_AUTO_SELECT) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     preferred_regions.clear(); | ||||
| 
 | ||||
|     std::vector<u8> smdh_buffer; | ||||
|     if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { | ||||
|         SMDH smdh; | ||||
|         std::memcpy(&smdh, smdh_buffer.data(), sizeof(SMDH)); | ||||
|         u32 region_lockout = smdh.region_lockout; | ||||
|         constexpr u32 REGION_COUNT = 7; | ||||
|         for (u32 region = 0; region < REGION_COUNT; ++region) { | ||||
|             if (region_lockout & 1) { | ||||
|                 preferred_regions.push_back(region); | ||||
|             } | ||||
|             region_lockout >>= 1; | ||||
|         } | ||||
|     } else { | ||||
|         const auto region = Core::GetSystemTitleRegion(program_id); | ||||
|         if (region.has_value()) { | ||||
|             preferred_regions.push_back(region.value()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Apploader_Artic::LoadExheader() { | ||||
|     if (program_exheader_loaded) | ||||
|         return true; | ||||
| 
 | ||||
|     if (!client_connected) | ||||
|         client_connected = client->Connect(); | ||||
|     if (!client_connected) | ||||
|         return false; | ||||
| 
 | ||||
|     auto req = client->NewRequest("Process_GetExheader"); | ||||
|     auto resp = client->Send(req); | ||||
|     if (!resp.has_value()) | ||||
|         return false; | ||||
| 
 | ||||
|     auto exheader_buf = resp->GetResponseBuffer(0); | ||||
|     if (!exheader_buf.has_value()) | ||||
|         return false; | ||||
| 
 | ||||
|     if (exheader_buf->second != sizeof(ExHeader_Header) - sizeof(ExHeader_Header::access_desc)) | ||||
|         return false; | ||||
| 
 | ||||
|     u8* prg_exh = reinterpret_cast<u8*>(&program_exheader); | ||||
|     memcpy(prg_exh, exheader_buf->first, | ||||
|            sizeof(ExHeader_Header) - sizeof(ExHeader_Header::access_desc)); | ||||
|     memcpy(prg_exh + offsetof(ExHeader_Header, access_desc.arm11_system_local_caps), | ||||
|            reinterpret_cast<u8*>(exheader_buf->first) + | ||||
|                offsetof(ExHeader_Header, arm11_system_local_caps), | ||||
|            offsetof(ExHeader_Header, access_desc) - | ||||
|                offsetof(ExHeader_Header, arm11_system_local_caps)); | ||||
|     program_exheader_loaded = true; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::LoadProductInfo(Service::FS::FS_USER::ProductInfo& out_product_info) { | ||||
|     if (cached_product_info.has_value()) { | ||||
|         out_product_info = *cached_product_info; | ||||
|         return ResultStatus::Success; | ||||
|     } | ||||
| 
 | ||||
|     if (!client_connected) | ||||
|         client_connected = client->Connect(); | ||||
|     if (!client_connected) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto req = client->NewRequest("Process_GetProductInfo"); | ||||
|     auto resp = client->Send(req); | ||||
|     if (!resp.has_value()) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto pinfo_buf = resp->GetResponseBuffer(0); | ||||
|     if (!pinfo_buf.has_value() || pinfo_buf->second != sizeof(Service::FS::FS_USER::ProductInfo)) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     out_product_info = *reinterpret_cast<Service::FS::FS_USER::ProductInfo*>(pinfo_buf->first); | ||||
|     cached_product_info = out_product_info; | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) { | ||||
|     u64_le ncch_program_id; | ||||
| 
 | ||||
|     if (is_loaded) | ||||
|         return ResultStatus::ErrorAlreadyLoaded; | ||||
| 
 | ||||
|     ResultStatus result = ReadProgramId(ncch_program_id); | ||||
|     if (result != ResultStatus::Success) { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     std::string program_id{fmt::format("{:016X}", ncch_program_id)}; | ||||
| 
 | ||||
|     LOG_INFO(Loader, "Program ID: {}", program_id); | ||||
| 
 | ||||
|     if (auto room_member = Network::GetRoomMember().lock()) { | ||||
|         Network::GameInfo game_info; | ||||
|         ReadTitle(game_info.name); | ||||
|         game_info.id = ncch_program_id; | ||||
|         room_member->SendGameInfo(game_info); | ||||
|     } | ||||
| 
 | ||||
|     is_loaded = true; // Set state to loaded
 | ||||
| 
 | ||||
|     result = LoadExec(process); // Load the executable into memory for booting
 | ||||
|     if (ResultStatus::Success != result) | ||||
|         return result; | ||||
| 
 | ||||
|     system.ArchiveManager().RegisterSelfNCCH(*this); | ||||
|     system.ArchiveManager().RegisterArticSaveDataSource(client); | ||||
|     system.ArchiveManager().RegisterArticExtData(client); | ||||
|     system.ArchiveManager().RegisterArticNCCH(client); | ||||
| 
 | ||||
|     auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER"); | ||||
|     fs_user->RegisterSecureValueBackend(std::make_shared<FileSys::ArticSecureValueBackend>(client)); | ||||
| 
 | ||||
|     ParseRegionLockoutInfo(ncch_program_id); | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::IsExecutable(bool& out_executable) { | ||||
|     out_executable = true; | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::ReadCode(std::vector<u8>& buffer) { | ||||
|     // Code is only read once, there is no need to cache it.
 | ||||
| 
 | ||||
|     if (!client_connected) | ||||
|         client_connected = client->Connect(); | ||||
|     if (!client_connected) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     size_t code_size = program_exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE; | ||||
|     code_size += program_exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE; | ||||
|     code_size += program_exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE; | ||||
| 
 | ||||
|     size_t read_amount = 0; | ||||
|     buffer.clear(); | ||||
| 
 | ||||
|     while (read_amount != code_size) { | ||||
|         size_t to_read = | ||||
|             std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, code_size - read_amount); | ||||
| 
 | ||||
|         auto req = client->NewRequest("Process_ReadCode"); | ||||
|         req.AddParameterS32(static_cast<s32>(read_amount)); | ||||
|         req.AddParameterS32(static_cast<s32>(to_read)); | ||||
|         auto resp = client->Send(req); | ||||
|         if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0) | ||||
|             return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|         auto code_buff = resp->GetResponseBuffer(0); | ||||
|         if (!code_buff.has_value() || code_buff->second != to_read) | ||||
|             return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|         buffer.resize(read_amount + to_read); | ||||
|         memcpy(buffer.data() + read_amount, code_buff->first, to_read); | ||||
|         read_amount += to_read; | ||||
|     } | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::ReadIcon(std::vector<u8>& buffer) { | ||||
|     if (!cached_icon.empty()) { | ||||
|         buffer = cached_icon; | ||||
|         return ResultStatus::Success; | ||||
|     } | ||||
| 
 | ||||
|     if (!client_connected) | ||||
|         client_connected = client->Connect(); | ||||
|     if (!client_connected) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto req = client->NewRequest("Process_ReadIcon"); | ||||
|     auto resp = client->Send(req); | ||||
|     if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto icon_buf = resp->GetResponseBuffer(0); | ||||
|     if (!icon_buf.has_value()) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     cached_icon.resize(icon_buf->second); | ||||
|     memcpy(cached_icon.data(), icon_buf->first, icon_buf->second); | ||||
|     buffer = cached_icon; | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::ReadBanner(std::vector<u8>& buffer) { | ||||
|     if (!cached_banner.empty()) { | ||||
|         buffer = cached_banner; | ||||
|         return ResultStatus::Success; | ||||
|     } | ||||
| 
 | ||||
|     if (!client_connected) | ||||
|         client_connected = client->Connect(); | ||||
|     if (!client_connected) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto req = client->NewRequest("Process_ReadBanner"); | ||||
|     auto resp = client->Send(req); | ||||
|     if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto banner_buf = resp->GetResponseBuffer(0); | ||||
|     if (!banner_buf.has_value()) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     cached_banner.resize(banner_buf->second); | ||||
|     memcpy(cached_banner.data(), banner_buf->first, banner_buf->second); | ||||
|     buffer = cached_banner; | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::ReadLogo(std::vector<u8>& buffer) { | ||||
|     if (!cached_logo.empty()) { | ||||
|         buffer = cached_logo; | ||||
|         return ResultStatus::Success; | ||||
|     } | ||||
| 
 | ||||
|     if (!client_connected) | ||||
|         client_connected = client->Connect(); | ||||
|     if (!client_connected) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto req = client->NewRequest("Process_ReadLogo"); | ||||
|     auto resp = client->Send(req); | ||||
|     if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto logo_buf = resp->GetResponseBuffer(0); | ||||
|     if (!logo_buf.has_value()) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     cached_logo.resize(logo_buf->second); | ||||
|     memcpy(cached_logo.data(), logo_buf->first, logo_buf->second); | ||||
|     buffer = cached_logo; | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::ReadProgramId(u64& out_program_id) { | ||||
|     if (cached_title_id.has_value()) { | ||||
|         out_program_id = *cached_title_id; | ||||
|         return ResultStatus::Success; | ||||
|     } | ||||
| 
 | ||||
|     if (!client_connected) | ||||
|         client_connected = client->Connect(); | ||||
|     if (!client_connected) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto req = client->NewRequest("Process_GetTitleID"); | ||||
|     auto resp = client->Send(req); | ||||
|     if (!resp.has_value()) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     auto tid_buf = resp->GetResponseBuffer(0); | ||||
|     if (!tid_buf.has_value() || tid_buf->second != sizeof(u64)) | ||||
|         return ResultStatus::ErrorArtic; | ||||
| 
 | ||||
|     out_program_id = *reinterpret_cast<u64*>(tid_buf->first); | ||||
|     cached_title_id = out_program_id; | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::ReadExtdataId(u64& out_extdata_id) { | ||||
|     if (program_exheader.arm11_system_local_caps.storage_info.other_attributes >> 1) { | ||||
|         // Using extended save data access
 | ||||
|         // There would be multiple possible extdata IDs in this case. The best we can do for now is
 | ||||
|         // guessing that the first one would be the main save.
 | ||||
|         const std::array<u64, 6> extdata_ids{{ | ||||
|             program_exheader.arm11_system_local_caps.storage_info.extdata_id0.Value(), | ||||
|             program_exheader.arm11_system_local_caps.storage_info.extdata_id1.Value(), | ||||
|             program_exheader.arm11_system_local_caps.storage_info.extdata_id2.Value(), | ||||
|             program_exheader.arm11_system_local_caps.storage_info.extdata_id3.Value(), | ||||
|             program_exheader.arm11_system_local_caps.storage_info.extdata_id4.Value(), | ||||
|             program_exheader.arm11_system_local_caps.storage_info.extdata_id5.Value(), | ||||
|         }}; | ||||
|         for (u64 id : extdata_ids) { | ||||
|             if (id) { | ||||
|                 // Found a non-zero ID, use it
 | ||||
|                 out_extdata_id = id; | ||||
|                 return ResultStatus::Success; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return ResultStatus::ErrorNotUsed; | ||||
|     } | ||||
| 
 | ||||
|     out_extdata_id = program_exheader.arm11_system_local_caps.storage_info.ext_save_data_id; | ||||
|     return Loader::ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) { | ||||
|     main_romfs_reader = romfs_file = std::make_shared<FileSys::ArticRomFSReader>(client, false); | ||||
|     return static_cast<FileSys::ArticRomFSReader*>(romfs_file.get())->OpenStatus(); | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) { | ||||
|     update_romfs_reader = romfs_file = std::make_shared<FileSys::ArticRomFSReader>(client, true); | ||||
|     return static_cast<FileSys::ArticRomFSReader*>(romfs_file.get())->OpenStatus(); | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::DumpRomFS(const std::string& target_path) { | ||||
|     return ResultStatus::ErrorNotImplemented; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::DumpUpdateRomFS(const std::string& target_path) { | ||||
|     return ResultStatus::ErrorNotImplemented; | ||||
| } | ||||
| 
 | ||||
| ResultStatus Apploader_Artic::ReadTitle(std::string& title) { | ||||
|     std::vector<u8> data; | ||||
|     Loader::SMDH smdh; | ||||
|     ResultStatus result = ReadIcon(data); | ||||
|     if (result != ResultStatus::Success) { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     if (!Loader::IsValidSMDH(data)) { | ||||
|         return ResultStatus::ErrorInvalidFormat; | ||||
|     } | ||||
| 
 | ||||
|     std::memcpy(&smdh, data.data(), sizeof(Loader::SMDH)); | ||||
| 
 | ||||
|     const auto& short_title = smdh.GetShortTitle(SMDH::TitleLanguage::English); | ||||
|     auto title_end = std::find(short_title.begin(), short_title.end(), u'\0'); | ||||
|     title = Common::UTF16ToUTF8(std::u16string{short_title.begin(), title_end}); | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| } // namespace Loader
 | ||||
							
								
								
									
										135
									
								
								src/core/loader/artic.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/core/loader/artic.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,135 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/core.h" | ||||
| #include "core/file_sys/ncch_container.h" | ||||
| #include "core/hle/service/fs/fs_user.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "network/artic_base/artic_base_client.h" | ||||
| 
 | ||||
| namespace Loader { | ||||
| 
 | ||||
| /// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI)
 | ||||
| class Apploader_Artic final : public AppLoader { | ||||
| public: | ||||
|     Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port) | ||||
|         : AppLoader(system_, FileUtil::IOFile()) { | ||||
|         client = std::make_shared<Network::ArticBase::Client>(server_addr, server_port); | ||||
|         client->SetCommunicationErrorCallback([&system_]() { | ||||
|             system_.SetStatus(Core::System::ResultStatus::ErrorArticDisconnected); | ||||
|         }); | ||||
|         client->SetArticReportTrafficCallback( | ||||
|             [&system_](u32 bytes) { system_.ReportArticTraffic(bytes); }); | ||||
|         client->SetReportArticEventCallback([&system_](u64 event) { | ||||
|             Core::PerfStats::PerfArticEventBits ev = | ||||
|                 static_cast<Core::PerfStats::PerfArticEventBits>(event & 0xFFFFFFFF); | ||||
|             bool set = (event > 32) != 0; | ||||
|             system_.ReportPerfArticEvent(ev, set); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     ~Apploader_Artic() override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Returns the type of the file | ||||
|      * @param file FileUtil::IOFile open file | ||||
|      * @return FileType found, or FileType::Error if this loader doesn't know it | ||||
|      */ | ||||
|     static FileType IdentifyType(FileUtil::IOFile& file); | ||||
| 
 | ||||
|     FileType GetFileType() override { | ||||
|         return IdentifyType(file); | ||||
|     } | ||||
| 
 | ||||
|     [[nodiscard]] std::span<const u32> GetPreferredRegions() const override { | ||||
|         return preferred_regions; | ||||
|     } | ||||
| 
 | ||||
|     ResultStatus Load(std::shared_ptr<Kernel::Process>& process) override; | ||||
| 
 | ||||
|     std::pair<std::optional<u32>, ResultStatus> LoadCoreVersion() override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Loads the Exheader and returns the system mode for this application. | ||||
|      * @returns A pair with the optional system mode, and and the status. | ||||
|      */ | ||||
|     std::pair<std::optional<Kernel::MemoryMode>, ResultStatus> LoadKernelMemoryMode() override; | ||||
| 
 | ||||
|     std::pair<std::optional<Kernel::New3dsHwCapabilities>, ResultStatus> LoadNew3dsHwCapabilities() | ||||
|         override; | ||||
| 
 | ||||
|     ResultStatus IsExecutable(bool& out_executable) override; | ||||
| 
 | ||||
|     ResultStatus ReadCode(std::vector<u8>& buffer) override; | ||||
| 
 | ||||
|     ResultStatus ReadIcon(std::vector<u8>& buffer) override; | ||||
| 
 | ||||
|     ResultStatus ReadBanner(std::vector<u8>& buffer) override; | ||||
| 
 | ||||
|     ResultStatus ReadLogo(std::vector<u8>& buffer) override; | ||||
| 
 | ||||
|     ResultStatus ReadProgramId(u64& out_program_id) override; | ||||
| 
 | ||||
|     ResultStatus ReadExtdataId(u64& out_extdata_id) override; | ||||
| 
 | ||||
|     ResultStatus ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override; | ||||
| 
 | ||||
|     ResultStatus ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override; | ||||
| 
 | ||||
|     ResultStatus DumpRomFS(const std::string& target_path) override; | ||||
| 
 | ||||
|     ResultStatus DumpUpdateRomFS(const std::string& target_path) override; | ||||
| 
 | ||||
|     ResultStatus ReadTitle(std::string& title) override; | ||||
| 
 | ||||
|     bool SupportsSaveStates() override { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool SupportsMultipleInstancesForSameFile() override { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     /**
 | ||||
|      * Loads .code section into memory for booting | ||||
|      * @param process The newly created process | ||||
|      * @return ResultStatus result of function | ||||
|      */ | ||||
|     ResultStatus LoadExec(std::shared_ptr<Kernel::Process>& process); | ||||
| 
 | ||||
|     /// Reads the region lockout info in the SMDH and send it to CFG service
 | ||||
|     /// If an SMDH is not present, the program ID is compared against a list
 | ||||
|     /// of known system titles to determine the region.
 | ||||
|     void ParseRegionLockoutInfo(u64 program_id); | ||||
| 
 | ||||
|     bool LoadExheader(); | ||||
| 
 | ||||
|     ResultStatus LoadProductInfo(Service::FS::FS_USER::ProductInfo& out); | ||||
| 
 | ||||
|     ExHeader_Header program_exheader{}; | ||||
|     bool program_exheader_loaded = false; | ||||
| 
 | ||||
|     std::optional<u64> cached_title_id = std::nullopt; | ||||
|     std::optional<Service::FS::FS_USER::ProductInfo> cached_product_info = std::nullopt; | ||||
|     std::vector<u8> cached_icon; | ||||
|     std::vector<u8> cached_banner; | ||||
|     std::vector<u8> cached_logo; | ||||
| 
 | ||||
|     std::vector<u32> preferred_regions; | ||||
| 
 | ||||
|     std::string server_address; | ||||
|     std::shared_ptr<Network::ArticBase::Client> client; | ||||
|     bool client_connected = false; | ||||
| 
 | ||||
|     std::shared_ptr<FileSys::RomFSReader> main_romfs_reader = nullptr; | ||||
|     std::shared_ptr<FileSys::RomFSReader> update_romfs_reader = nullptr; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Loader
 | ||||
|  | @ -9,6 +9,7 @@ | |||
| #include "core/core.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "core/loader/3dsx.h" | ||||
| #include "core/loader/artic.h" | ||||
| #include "core/loader/elf.h" | ||||
| #include "core/loader/ncch.h" | ||||
| 
 | ||||
|  | @ -74,6 +75,8 @@ const char* GetFileTypeString(FileType type) { | |||
|         return "ELF"; | ||||
|     case FileType::THREEDSX: | ||||
|         return "3DSX"; | ||||
|     case FileType::ARTIC: | ||||
|         return "ARTIC"; | ||||
|     case FileType::Error: | ||||
|     case FileType::Unknown: | ||||
|         break; | ||||
|  | @ -108,12 +111,39 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil:: | |||
|     case FileType::CCI: | ||||
|         return std::make_unique<AppLoader_NCCH>(system, std::move(file), filepath); | ||||
| 
 | ||||
|     case FileType::ARTIC: { | ||||
|         auto strToUInt = [](const std::string& str) -> int { | ||||
|             char* pEnd = NULL; | ||||
|             unsigned long ul = ::strtoul(str.c_str(), &pEnd, 10); | ||||
|             if (*pEnd) | ||||
|                 return -1; | ||||
|             return static_cast<int>(ul); | ||||
|         }; | ||||
| 
 | ||||
|         u16 port = 5543; | ||||
|         std::string server_addr = filename; | ||||
|         auto pos = server_addr.find(":"); | ||||
|         if (pos != server_addr.npos) { | ||||
|             int newVal = strToUInt(server_addr.substr(pos + 1)); | ||||
|             if (newVal >= 0 && newVal <= 0xFFFF) { | ||||
|                 port = static_cast<u16>(newVal); | ||||
|                 server_addr = server_addr.substr(0, pos); | ||||
|             } | ||||
|         } | ||||
|         return std::make_unique<Apploader_Artic>(system, server_addr, port); | ||||
|     } | ||||
| 
 | ||||
|     default: | ||||
|         return nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<AppLoader> GetLoader(const std::string& filename) { | ||||
|     if (filename.starts_with("articbase://")) { | ||||
|         return GetFileLoader(Core::System::GetInstance(), FileUtil::IOFile(), FileType::ARTIC, | ||||
|                              filename.substr(12), ""); | ||||
|     } | ||||
| 
 | ||||
|     FileUtil::IOFile file(filename, "rb"); | ||||
|     if (!file.IsOpen()) { | ||||
|         LOG_ERROR(Loader, "Failed to load file {}", filename); | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ enum class FileType { | |||
|     CIA, | ||||
|     ELF, | ||||
|     THREEDSX, // 3DSX
 | ||||
|     ARTIC, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -73,6 +74,7 @@ enum class ResultStatus { | |||
|     ErrorMemoryAllocationFailed, | ||||
|     ErrorEncrypted, | ||||
|     ErrorGbaTitle, | ||||
|     ErrorArtic, | ||||
| }; | ||||
| 
 | ||||
| constexpr u32 MakeMagic(char a, char b, char c, char d) { | ||||
|  | @ -264,6 +266,14 @@ public: | |||
|         return ResultStatus::ErrorNotImplemented; | ||||
|     } | ||||
| 
 | ||||
|     virtual bool SupportsSaveStates() { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     virtual bool SupportsMultipleInstancesForSameFile() { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     Core::System& system; | ||||
|     FileUtil::IOFile file; | ||||
|  |  | |||
|  | @ -101,6 +101,8 @@ PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_ | |||
|     last_stats.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / | ||||
|                            static_cast<double>(system_frames); | ||||
|     last_stats.emulation_speed = system_us_per_second.count() / 1'000'000.0; | ||||
|     last_stats.artic_transmitted = static_cast<double>(artic_transmitted) / interval; | ||||
|     last_stats.artic_events.raw = artic_events.raw | prev_artic_event.raw; | ||||
| 
 | ||||
|     // Reset counters
 | ||||
|     reset_point = now; | ||||
|  | @ -108,6 +110,8 @@ PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_ | |||
|     accumulated_frametime = Clock::duration::zero(); | ||||
|     system_frames = 0; | ||||
|     game_frames = 0; | ||||
|     artic_transmitted = 0; | ||||
|     prev_artic_event.raw &= artic_events.raw; | ||||
| 
 | ||||
|     return last_stats; | ||||
| } | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include <chrono> | ||||
| #include <cstddef> | ||||
| #include <mutex> | ||||
| #include "common/bit_field.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/thread.h" | ||||
| 
 | ||||
|  | @ -25,6 +26,28 @@ public: | |||
| 
 | ||||
|     using Clock = std::chrono::high_resolution_clock; | ||||
| 
 | ||||
|     enum class PerfArticEventBits { | ||||
|         NONE = 0, | ||||
|         ARTIC_SAVE_DATA = (1 << 0), | ||||
|         ARTIC_EXT_DATA = (1 << 1), | ||||
|         ARTIC_BOSS_EXT_DATA = (1 << 2), | ||||
|         ARTIC_SHARED_EXT_DATA = (1 << 3), | ||||
|     }; | ||||
|     union PerfArticEvents { | ||||
|         u32 raw{}; | ||||
|         BitField<0, 1, u32> artic_save_data; | ||||
|         BitField<1, 1, u32> artic_ext_data; | ||||
|         BitField<2, 1, u32> artic_boss_ext_data; | ||||
|         BitField<3, 1, u32> artic_shared_ext_data; | ||||
| 
 | ||||
|         void Set(PerfArticEventBits event, bool set) { | ||||
|             raw = (raw & ~static_cast<u32>(event)) | (set ? static_cast<u32>(event) : 0); | ||||
|         } | ||||
|         bool Get(PerfArticEventBits event) { | ||||
|             return (raw & static_cast<u32>(event)) != 0; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     struct Results { | ||||
|         /// System FPS (LCD VBlanks) in Hz
 | ||||
|         double system_fps; | ||||
|  | @ -34,6 +57,10 @@ public: | |||
|         double frametime; | ||||
|         /// Ratio of walltime / emulated time elapsed
 | ||||
|         double emulation_speed; | ||||
|         /// Artic base bytes per second
 | ||||
|         double artic_transmitted = 0; | ||||
|         /// Artic base events
 | ||||
|         PerfArticEvents artic_events{}; | ||||
|     }; | ||||
| 
 | ||||
|     void BeginSystemFrame(); | ||||
|  | @ -55,6 +82,19 @@ public: | |||
|      */ | ||||
|     double GetLastFrameTimeScale() const; | ||||
| 
 | ||||
|     void AddArticBaseTraffic(u32 bytes) { | ||||
|         artic_transmitted += bytes; | ||||
|     } | ||||
| 
 | ||||
|     void ReportPerfArticEvent(PerfArticEventBits event, bool set) { | ||||
|         if (set) { | ||||
|             artic_events.Set(event, set); | ||||
|             prev_artic_event.Set(event, set); | ||||
|         } else { | ||||
|             artic_events.Set(event, set); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     mutable std::mutex object_mutex; | ||||
| 
 | ||||
|  | @ -77,6 +117,12 @@ private: | |||
|     u32 system_frames = 0; | ||||
|     /// Cumulative number of game frames (GSP frame submissions) since last reset
 | ||||
|     u32 game_frames = 0; | ||||
|     /// Cumulative number of transmitted artic base traffic
 | ||||
|     std::atomic<u32> artic_transmitted = 0; | ||||
|     // System events that affect performance
 | ||||
|     PerfArticEvents artic_events; | ||||
| 
 | ||||
|     PerfArticEvents prev_artic_event; | ||||
| 
 | ||||
|     /// Point when the previous system frame ended
 | ||||
|     Clock::time_point previous_frame_end = reset_point; | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
| #include "common/swap.h" | ||||
| #include "common/zstd_compression.h" | ||||
| #include "core/core.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/movie.h" | ||||
| #include "core/savestate.h" | ||||
| #include "core/savestate_data.h" | ||||
|  | @ -122,6 +123,12 @@ std::vector<SaveStateInfo> ListSaveStates(u64 program_id, u64 movie_id) { | |||
| } | ||||
| 
 | ||||
| void System::SaveState(u32 slot) const { | ||||
|     if (app_loader) { | ||||
|         if (!app_loader->SupportsSaveStates()) { | ||||
|             throw std::runtime_error("The current app loader doesn't support save states"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     std::ostringstream sstream{std::ios_base::binary}; | ||||
|     // Serialize
 | ||||
|     oarchive oa{sstream}; | ||||
|  | @ -164,6 +171,11 @@ void System::SaveState(u32 slot) const { | |||
| } | ||||
| 
 | ||||
| void System::LoadState(u32 slot) { | ||||
|     if (app_loader) { | ||||
|         if (!app_loader->SupportsSaveStates()) { | ||||
|             throw std::runtime_error("The current app loader doesn't support save states"); | ||||
|         } | ||||
|     } | ||||
|     if (Network::GetRoomMember().lock()->IsConnected()) { | ||||
|         throw std::runtime_error("Unable to load while connected to multiplayer"); | ||||
|     } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue