mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	boss: Implement some NsData header and read commands. (#7283)
* boss: Implement some NsData header and read commands. Co-authored-by: Rokkubro <lachlanb03@gmail.com> * boss: Move opening ext data to common function and improve logging. --------- Co-authored-by: Rokkubro <lachlanb03@gmail.com>
This commit is contained in:
		
							parent
							
								
									3113ae6616
								
							
						
					
					
						commit
						602f4f60d8
					
				
					 3 changed files with 239 additions and 49 deletions
				
			
		|  | @ -631,34 +631,51 @@ void Module::Interface::DeleteNsData(Kernel::HLERequestContext& ctx) { | |||
| 
 | ||||
| void Module::Interface::GetNsDataHeaderInfo(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx); | ||||
|     const u32 ns_data_id = rp.Pop<u32>(); | ||||
|     const u8 type = rp.Pop<u8>(); | ||||
|     const u32 size = rp.Pop<u32>(); | ||||
|     const auto ns_data_id = rp.Pop<u32>(); | ||||
|     const auto type = rp.PopEnum<NsDataHeaderInfoType>(); | ||||
|     const auto size = rp.Pop<u32>(); | ||||
|     auto& buffer = rp.PopMappedBuffer(); | ||||
| 
 | ||||
|     const auto online_service = GetSessionService(ctx); | ||||
|     if (online_service == nullptr) { | ||||
|         return; | ||||
|     } | ||||
|     const auto result = online_service->GetNsDataHeaderInfo(ns_data_id, type, size, buffer); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push(result); | ||||
|     rb.PushMappedBuffer(buffer); | ||||
| 
 | ||||
|     LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010x}, type={:#04x}, size={:#010x}", | ||||
|                 ns_data_id, type, size); | ||||
|     LOG_DEBUG(Service_BOSS, "called, ns_data_id={:#010x}, type={:#04x}, size={:#010x}", ns_data_id, | ||||
|               type, size); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::ReadNsData(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx); | ||||
|     const u32 ns_data_id = rp.Pop<u32>(); | ||||
|     const u64 offset = rp.Pop<u64>(); | ||||
|     const u32 size = rp.Pop<u32>(); | ||||
|     const auto ns_data_id = rp.Pop<u32>(); | ||||
|     const auto offset = rp.Pop<u64>(); | ||||
|     const auto size = rp.Pop<u32>(); | ||||
|     auto& buffer = rp.PopMappedBuffer(); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push<u32>(size); /// Should be actual read size
 | ||||
|     rb.Push<u32>(0);    /// unknown
 | ||||
|     rb.PushMappedBuffer(buffer); | ||||
|     const auto online_service = GetSessionService(ctx); | ||||
|     if (online_service == nullptr) { | ||||
|         return; | ||||
|     } | ||||
|     const auto result = online_service->ReadNsData(ns_data_id, offset, size, buffer); | ||||
| 
 | ||||
|     LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010x}, offset={:#018x}, size={:#010x}", | ||||
|                 ns_data_id, offset, size); | ||||
|     if (result.Succeeded()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); | ||||
|         rb.Push(result.Code()); | ||||
|         rb.Push<u32>(static_cast<u32>(result.Unwrap())); | ||||
|         rb.Push<u32>(0); /// unknown
 | ||||
|         rb.PushMappedBuffer(buffer); | ||||
|     } else { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(result.Code()); | ||||
|     } | ||||
| 
 | ||||
|     LOG_DEBUG(Service_BOSS, "called, ns_data_id={:#010x}, offset={:#018x}, size={:#010x}", | ||||
|               ns_data_id, offset, size); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::SetNsDataAdditionalInfo(Kernel::HLERequestContext& ctx) { | ||||
|  | @ -710,14 +727,27 @@ void Module::Interface::GetNsDataNewFlag(Kernel::HLERequestContext& ctx) { | |||
| 
 | ||||
| void Module::Interface::GetNsDataLastUpdate(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx); | ||||
|     const u32 unk_param1 = rp.Pop<u32>(); | ||||
|     const u32 ns_data_id = rp.Pop<u32>(); | ||||
| 
 | ||||
|     const auto online_service = GetSessionService(ctx); | ||||
|     if (online_service == nullptr) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto entry = online_service->GetNsDataEntryFromId(ns_data_id); | ||||
|     if (!entry.has_value()) { | ||||
|         // TODO: Proper error code.
 | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(RESULT_UNKNOWN); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push<u32>(0); // stub 0 (32bit value)
 | ||||
|     rb.Push<u32>(0); // stub 0 (32bit value)
 | ||||
|     rb.Push<u32>(0); | ||||
|     rb.Push<u32>(entry->header.download_date); // return the download date from the ns data
 | ||||
| 
 | ||||
|     LOG_WARNING(Service_BOSS, "(STUBBED) unk_param1={:#010x}", unk_param1); | ||||
|     LOG_DEBUG(Service_BOSS, "called, ns_data_id={:#010X}", ns_data_id); | ||||
| } | ||||
| 
 | ||||
| void Module::Interface::GetErrorCode(Kernel::HLERequestContext& ctx) { | ||||
|  |  | |||
|  | @ -73,12 +73,14 @@ ResultCode OnlineService::InitializeSession(u64 init_program_id) { | |||
|         auto create_archive_result = systemsavedata_factory.Open(archive_path, 0); | ||||
|         if (!create_archive_result.Succeeded()) { | ||||
|             LOG_ERROR(Service_BOSS, "Could not open BOSS savedata"); | ||||
|             return ResultCode(1); | ||||
|             // TODO: Proper error code.
 | ||||
|             return RESULT_UNKNOWN; | ||||
|         } | ||||
|         boss_system_save_data_archive = std::move(create_archive_result).Unwrap(); | ||||
|     } else { | ||||
|         LOG_ERROR(Service_BOSS, "Could not open BOSS savedata"); | ||||
|         return ResultCode(1); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     FileSys::Mode open_mode = {}; | ||||
|  | @ -151,14 +153,16 @@ void OnlineService::RegisterTask(const u32 size, Kernel::MappedBuffer& buffer) { | |||
| ResultCode OnlineService::UnregisterTask(const u32 size, Kernel::MappedBuffer& buffer) { | ||||
|     if (size > TASK_ID_SIZE) { | ||||
|         LOG_WARNING(Service_BOSS, "TaskId cannot be longer than 8"); | ||||
|         return ResultCode(1); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     std::string task_id(size, 0); | ||||
|     buffer.Read(task_id.data(), 0, size); | ||||
|     if (task_id_list.erase(task_id) == 0) { | ||||
|         LOG_WARNING(Service_BOSS, "TaskId not in list"); | ||||
|         return ResultCode(1); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     return RESULT_SUCCESS; | ||||
|  | @ -187,20 +191,32 @@ void OnlineService::GetTaskIdList() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| std::vector<FileSys::Entry> OnlineService::GetBossExtDataFiles() { | ||||
| FileSys::Path OnlineService::GetBossDataDir() { | ||||
|     const u32 high = static_cast<u32>(extdata_id >> 32); | ||||
|     const u32 low = static_cast<u32>(extdata_id & 0xFFFFFFFF); | ||||
|     return FileSys::ConstructExtDataBinaryPath(1, high, low); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<FileSys::ArchiveBackend> OnlineService::OpenBossExtData() { | ||||
|     FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory( | ||||
|         FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), FileSys::ExtSaveDataType::Boss); | ||||
|     const FileSys::Path boss_path{GetBossDataDir()}; | ||||
|     auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0); | ||||
|     if (!archive_result.Succeeded()) { | ||||
|         LOG_WARNING(Service_BOSS, "Extdata opening failed"); | ||||
|         return {}; | ||||
|         LOG_WARNING(Service_BOSS, "Failed to open SpotPass ext data archive with ID '{:#010x}'.", | ||||
|                     extdata_id); | ||||
|         return nullptr; | ||||
|     } | ||||
|     return std::move(archive_result).Unwrap(); | ||||
| } | ||||
| 
 | ||||
|     auto boss_archive = std::move(archive_result).Unwrap(); | ||||
| std::vector<FileSys::Entry> OnlineService::GetBossExtDataFiles( | ||||
|     FileSys::ArchiveBackend* boss_archive) { | ||||
|     auto dir_result = boss_archive->OpenDirectory("/"); | ||||
|     if (!dir_result.Succeeded()) { | ||||
|         LOG_WARNING(Service_BOSS, "Extdata directory opening failed"); | ||||
|         LOG_WARNING(Service_BOSS, | ||||
|                     "Failed to open root directory of SpotPass ext data with ID '{:#010x}'.", | ||||
|                     extdata_id); | ||||
|         return {}; | ||||
|     } | ||||
|     auto dir = std::move(dir_result).Unwrap(); | ||||
|  | @ -219,29 +235,20 @@ std::vector<FileSys::Entry> OnlineService::GetBossExtDataFiles() { | |||
|     return boss_files; | ||||
| } | ||||
| 
 | ||||
| FileSys::Path OnlineService::GetBossDataDir() { | ||||
|     const u32 high = static_cast<u32>(extdata_id >> 32); | ||||
|     const u32 low = static_cast<u32>(extdata_id & 0xFFFFFFFF); | ||||
|     return FileSys::ConstructExtDataBinaryPath(1, high, low); | ||||
| } | ||||
| 
 | ||||
| std::vector<NsDataEntry> OnlineService::GetNsDataEntries() { | ||||
|     FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory( | ||||
|         FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), FileSys::ExtSaveDataType::Boss); | ||||
|     const FileSys::Path boss_path{GetBossDataDir()}; | ||||
|     auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0); | ||||
|     if (!archive_result.Succeeded()) { | ||||
|         LOG_WARNING(Service_BOSS, "Extdata opening failed"); | ||||
|     auto boss_archive = OpenBossExtData(); | ||||
|     if (!boss_archive) { | ||||
|         return {}; | ||||
|     } | ||||
|     auto boss_archive = std::move(archive_result).Unwrap().get(); | ||||
| 
 | ||||
|     std::vector<NsDataEntry> ns_data; | ||||
|     std::vector<FileSys::Entry> boss_files = GetBossExtDataFiles(); | ||||
|     const auto boss_files = GetBossExtDataFiles(boss_archive.get()); | ||||
|     for (const auto& current_file : boss_files) { | ||||
|         constexpr u32 boss_header_length = 0x34; | ||||
|         if (current_file.is_directory || current_file.file_size < boss_header_length) { | ||||
|             LOG_WARNING(Service_BOSS, "SpotPass extdata contains directory or file is too short"); | ||||
|             LOG_WARNING(Service_BOSS, | ||||
|                         "SpotPass extdata contains directory or file is too short: '{}'", | ||||
|                         Common::UTF16ToUTF8(current_file.filename)); | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|  | @ -315,6 +322,129 @@ u16 OnlineService::GetNsDataIdList(const u32 filter, const u32 max_entries, | |||
|     return static_cast<u16>(output_entries.size()); | ||||
| } | ||||
| 
 | ||||
| std::optional<NsDataEntry> OnlineService::GetNsDataEntryFromId(const u32 ns_data_id) { | ||||
|     std::vector<NsDataEntry> ns_data = GetNsDataEntries(); | ||||
|     const auto entry_iter = std::find_if(ns_data.begin(), ns_data.end(), [ns_data_id](auto entry) { | ||||
|         return entry.header.ns_data_id == ns_data_id; | ||||
|     }); | ||||
|     if (entry_iter == ns_data.end()) { | ||||
|         LOG_WARNING(Service_BOSS, "Could not find NsData with ID {:#010X}", ns_data_id); | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     return *entry_iter; | ||||
| } | ||||
| 
 | ||||
| ResultCode OnlineService::GetNsDataHeaderInfo(const u32 ns_data_id, const NsDataHeaderInfoType type, | ||||
|                                               const u32 size, Kernel::MappedBuffer& buffer) { | ||||
|     const auto entry = GetNsDataEntryFromId(ns_data_id); | ||||
|     if (!entry.has_value()) { | ||||
|         LOG_WARNING(Service_BOSS, "Failed to find NsData entry for ID {:#010X}", ns_data_id); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     static constexpr std::array EXPECTED_NS_DATA_HEADER_INFO_SIZES = { | ||||
|         sizeof(u64),              // Program ID
 | ||||
|         sizeof(u32),              // Unknown
 | ||||
|         sizeof(u32),              // Data Type
 | ||||
|         sizeof(u32),              // Payload Size
 | ||||
|         sizeof(u32),              // NsData ID
 | ||||
|         sizeof(u32),              // Version
 | ||||
|         sizeof(NsDataHeaderInfo), // Everything
 | ||||
|     }; | ||||
|     if (size != EXPECTED_NS_DATA_HEADER_INFO_SIZES[static_cast<u8>(type)]) { | ||||
|         LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     switch (type) { | ||||
|     case NsDataHeaderInfoType::ProgramId: | ||||
|         buffer.Write(&entry->header.program_id, 0, size); | ||||
|         return RESULT_SUCCESS; | ||||
|     case NsDataHeaderInfoType::Unknown: { | ||||
|         // TODO: Figure out what this is. Stubbed to zero for now.
 | ||||
|         const u32 zero = 0; | ||||
|         buffer.Write(&zero, 0, size); | ||||
|         return RESULT_SUCCESS; | ||||
|     } | ||||
|     case NsDataHeaderInfoType::Datatype: | ||||
|         buffer.Write(&entry->header.datatype, 0, size); | ||||
|         return RESULT_SUCCESS; | ||||
|     case NsDataHeaderInfoType::PayloadSize: | ||||
|         buffer.Write(&entry->header.payload_size, 0, size); | ||||
|         return RESULT_SUCCESS; | ||||
|     case NsDataHeaderInfoType::NsDataId: | ||||
|         buffer.Write(&entry->header.ns_data_id, 0, size); | ||||
|         return RESULT_SUCCESS; | ||||
|     case NsDataHeaderInfoType::Version: | ||||
|         buffer.Write(&entry->header.version, 0, size); | ||||
|         return RESULT_SUCCESS; | ||||
|     case NsDataHeaderInfoType::Everything: { | ||||
|         const NsDataHeaderInfo info = { | ||||
|             .program_id = entry->header.program_id, | ||||
|             .datatype = entry->header.datatype, | ||||
|             .payload_size = entry->header.payload_size, | ||||
|             .ns_data_id = entry->header.ns_data_id, | ||||
|             .version = entry->header.version, | ||||
|         }; | ||||
|         buffer.Write(&info, 0, size); | ||||
|         return RESULT_SUCCESS; | ||||
|     } | ||||
|     default: | ||||
|         LOG_WARNING(Service_BOSS, "Unknown header info type {}", type); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ResultVal<size_t> OnlineService::ReadNsData(const u32 ns_data_id, const u64 offset, const u32 size, | ||||
|                                             Kernel::MappedBuffer& buffer) { | ||||
|     std::optional<NsDataEntry> entry = GetNsDataEntryFromId(ns_data_id); | ||||
|     if (!entry.has_value()) { | ||||
|         LOG_WARNING(Service_BOSS, "Failed to find NsData entry for ID {:#010X}", ns_data_id); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     if (entry->header.payload_size < size + offset) { | ||||
|         LOG_WARNING(Service_BOSS, | ||||
|                     "Invalid request to read {:#010X} bytes at offset {:#010X}, payload " | ||||
|                     "length is {:#010X}", | ||||
|                     size, offset, static_cast<u32>(entry->header.payload_size)); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     auto boss_archive = OpenBossExtData(); | ||||
|     if (!boss_archive) { | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     FileSys::Path file_path = fmt::format("/{}", entry->filename); | ||||
|     FileSys::Mode mode{}; | ||||
|     mode.read_flag.Assign(1); | ||||
|     auto file_result = boss_archive->OpenFile(file_path, mode); | ||||
|     if (!file_result.Succeeded()) { | ||||
|         LOG_WARNING(Service_BOSS, "Failed to open SpotPass extdata file '{}'.", entry->filename); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     auto file = std::move(file_result).Unwrap(); | ||||
|     std::vector<u8> ns_data_array(size); | ||||
|     auto read_result = file->Read(sizeof(BossHeader) + offset, size, ns_data_array.data()); | ||||
|     if (!read_result.Succeeded()) { | ||||
|         LOG_WARNING(Service_BOSS, "Failed to read SpotPass extdata file '{}'.", entry->filename); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     buffer.Write(ns_data_array.data(), 0, size); | ||||
|     return read_result; | ||||
| } | ||||
| 
 | ||||
| template <class... Ts> | ||||
| struct overload : Ts... { | ||||
|     using Ts::operator()...; | ||||
|  | @ -325,8 +455,9 @@ overload(Ts...) -> overload<Ts...>; | |||
| ResultCode OnlineService::SendProperty(const u16 id, const u32 size, Kernel::MappedBuffer& buffer) { | ||||
|     const auto property_id = static_cast<PropertyID>(id); | ||||
|     if (!current_props.properties.contains(property_id)) { | ||||
|         LOG_ERROR(Service_BOSS, "Unknown property with id {:#06x}", property_id); | ||||
|         return ResultCode(1); | ||||
|         LOG_ERROR(Service_BOSS, "Unknown property with ID {:#06x} and size {}", property_id, size); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     auto& prop = current_props.properties[property_id]; | ||||
|  | @ -365,8 +496,9 @@ ResultCode OnlineService::ReceiveProperty(const u16 id, const u32 size, | |||
|                                           Kernel::MappedBuffer& buffer) { | ||||
|     const auto property_id = static_cast<PropertyID>(id); | ||||
|     if (!current_props.properties.contains(property_id)) { | ||||
|         LOG_ERROR(Service_BOSS, "Unknown property with id {:#06x}", property_id); | ||||
|         return ResultCode(1); | ||||
|         LOG_ERROR(Service_BOSS, "Unknown property with ID {:#06x} and size {}", property_id, size); | ||||
|         // TODO: Proper error code.
 | ||||
|         return RESULT_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     auto write_pod = [&]<typename T>(T& cur_prop) { | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ class MappedBuffer; | |||
| } | ||||
| 
 | ||||
| namespace FileSys { | ||||
| class ArchiveBackend; | ||||
| struct Entry; | ||||
| class Path; | ||||
| } // namespace FileSys
 | ||||
|  | @ -67,6 +68,27 @@ struct NsDataEntry { | |||
|     BossHeader header; | ||||
| }; | ||||
| 
 | ||||
| enum class NsDataHeaderInfoType : u8 { | ||||
|     ProgramId = 0, | ||||
|     Unknown = 1, | ||||
|     Datatype = 2, | ||||
|     PayloadSize = 3, | ||||
|     NsDataId = 4, | ||||
|     Version = 5, | ||||
|     Everything = 6, | ||||
| }; | ||||
| 
 | ||||
| struct NsDataHeaderInfo { | ||||
|     u64 program_id; | ||||
|     INSERT_PADDING_BYTES(4); | ||||
|     u32 datatype; | ||||
|     u32 payload_size; | ||||
|     u32 ns_data_id; | ||||
|     u32 version; | ||||
|     INSERT_PADDING_BYTES(4); | ||||
| }; | ||||
| static_assert(sizeof(NsDataHeaderInfo) == 0x20, "NsDataHeaderInfo has incorrect size"); | ||||
| 
 | ||||
| enum class PropertyID : u16 { | ||||
|     Interval = 0x03, | ||||
|     Duration = 0x04, | ||||
|  | @ -144,11 +166,17 @@ public: | |||
|     ResultCode UnregisterTask(const u32 size, Kernel::MappedBuffer& buffer); | ||||
|     void GetTaskIdList(); | ||||
|     u16 GetNsDataIdList(const u32 filter, const u32 max_entries, Kernel::MappedBuffer& buffer); | ||||
|     std::optional<NsDataEntry> GetNsDataEntryFromId(const u32 ns_data_id); | ||||
|     ResultCode GetNsDataHeaderInfo(const u32 ns_data_id, const NsDataHeaderInfoType type, | ||||
|                                    const u32 size, Kernel::MappedBuffer& buffer); | ||||
|     ResultVal<size_t> ReadNsData(const u32 ns_data_id, const u64 offset, const u32 size, | ||||
|                                  Kernel::MappedBuffer& buffer); | ||||
|     ResultCode SendProperty(const u16 id, const u32 size, Kernel::MappedBuffer& buffer); | ||||
|     ResultCode ReceiveProperty(const u16 id, const u32 size, Kernel::MappedBuffer& buffer); | ||||
| 
 | ||||
| private: | ||||
|     std::vector<FileSys::Entry> GetBossExtDataFiles(); | ||||
|     std::unique_ptr<FileSys::ArchiveBackend> OpenBossExtData(); | ||||
|     std::vector<FileSys::Entry> GetBossExtDataFiles(FileSys::ArchiveBackend* boss_archive); | ||||
|     FileSys::Path GetBossDataDir(); | ||||
|     std::vector<NsDataEntry> GetNsDataEntries(); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue