Result<Metadata> HostFileSystem::GetMetadata(Uid, Gid, const std::string& path) { Metadata metadata; metadata.uid = 0; metadata.gid = 0x3031; // this is also known as makercd, 01 (0x3031) for nintendo and 08 // (0x3038) for MH3 etc if (!IsValidWiiPath(path)) return ResultCode::Invalid; std::string file_name = BuildFilename(path); metadata.modes = {Mode::ReadWrite, Mode::ReadWrite, Mode::ReadWrite}; metadata.attribute = 0x00; // no attributes // Hack: if the path that is being accessed is within an installed title directory, get the // UID/GID from the installed title TMD. Kernel* ios = GetIOS(); u64 title_id; if (ios && IsTitlePath(file_name, Common::FROM_SESSION_ROOT, &title_id)) { IOS::ES::TMDReader tmd = ios->GetES()->FindInstalledTMD(title_id); if (tmd.IsValid()) metadata.gid = tmd.GetGroupId(); } const File::FileInfo info{file_name}; metadata.is_file = info.IsFile(); metadata.size = info.GetSize(); if (!info.Exists()) return ResultCode::NotFound; return metadata; }
void MainMenuBar::RefreshWiiSystemMenuLabel() const { auto* const item = FindItem(IDM_LOAD_WII_MENU); if (Core::IsRunning()) { item->Enable(false); for (const int idm : {IDM_PERFORM_ONLINE_UPDATE_CURRENT, IDM_PERFORM_ONLINE_UPDATE_EUR, IDM_PERFORM_ONLINE_UPDATE_JPN, IDM_PERFORM_ONLINE_UPDATE_KOR, IDM_PERFORM_ONLINE_UPDATE_USA}) { FindItem(idm)->Enable(false); } return; } IOS::HLE::Kernel ios; const IOS::ES::TMDReader sys_menu_tmd = ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU); if (sys_menu_tmd.IsValid()) { const u16 version_number = sys_menu_tmd.GetTitleVersion(); const wxString version_string = StrToWxStr(DiscIO::GetSysMenuVersionString(version_number)); item->Enable(); item->SetItemLabel(wxString::Format(_("Load Wii System Menu %s"), version_string)); EnableUpdateMenu(UpdateMenuMode::CurrentRegionOnly); } else { item->Enable(false); item->SetItemLabel(_("Load Wii System Menu")); EnableUpdateMenu(UpdateMenuMode::SpecificRegionsOnly); } }
IPCCommandResult ES::GetStoredContents(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(2, 1) || request.in_vectors[0].size != sizeof(u64)) return GetDefaultReply(ES_EINVAL); const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); const IOS::ES::TMDReader tmd = FindInstalledTMD(title_id); if (!tmd.IsValid()) return GetDefaultReply(FS_ENOENT); return GetStoredContents(tmd, request); }
// Used by the GetStoredContents ioctlvs. This assumes that the first output vector // is used for the content count (u32). IPCCommandResult ES::GetStoredContentsCount(const IOS::ES::TMDReader& tmd, const IOCtlVRequest& request) { if (request.io_vectors[0].size != sizeof(u32) || !tmd.IsValid()) return GetDefaultReply(ES_EINVAL); const u16 num_contents = static_cast<u16>(GetStoredContentsFromTMD(tmd).size()); Memory::Write_U32(num_contents, request.io_vectors[0].address); INFO_LOG(IOS_ES, "GetStoredContentsCount (0x%x): %u content(s) for %016" PRIx64, request.request, num_contents, tmd.GetTitleId()); return GetDefaultReply(IPC_SUCCESS); }
void InfoPanel::LoadISODetails() { m_internal_name->SetValue(StrToWxStr(m_opened_iso->GetInternalName())); m_game_id->SetValue(StrToWxStr(m_opened_iso->GetGameID())); m_country->SetValue(GetCountryName(m_opened_iso->GetCountry())); m_maker_id->SetValue("0x" + StrToWxStr(m_opened_iso->GetMakerID())); m_revision->SetValue(OptionalToString(m_opened_iso->GetRevision())); m_date->SetValue(StrToWxStr(m_opened_iso->GetApploaderDate())); if (m_ios_version) { const IOS::ES::TMDReader tmd = m_opened_iso->GetTMD(m_opened_iso->GetGamePartition()); if (tmd.IsValid()) m_ios_version->SetValue(StringFromFormat("IOS%u", static_cast<u32>(tmd.GetIOSId()))); } }
void SConfig::SetRunningGameMetadata(const IOS::ES::TMDReader& tmd) { const u64 tmd_title_id = tmd.GetTitleId(); // If we're launching a disc game, we want to read the revision from // the disc header instead of the TMD. They can differ. // (IOS HLE ES calls us with a TMDReader rather than a volume when launching // a disc game, because ES has no reason to be accessing the disc directly.) if (!DVDInterface::UpdateRunningGameMetadata(tmd_title_id)) { // If not launching a disc game, just read everything from the TMD. SetRunningGameMetadata(tmd.GetGameID(), tmd_title_id, tmd.GetTitleVersion(), Core::TitleDatabase::TitleType::Channel); } }
IPCCommandResult ES::GetStoredTMDSize(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(1, 1)) return GetDefaultReply(ES_EINVAL); const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); const IOS::ES::TMDReader tmd = FindInstalledTMD(title_id); if (!tmd.IsValid()) return GetDefaultReply(FS_ENOENT); const u32 tmd_size = static_cast<u32>(tmd.GetBytes().size()); Memory::Write_U32(tmd_size, request.io_vectors[0].address); INFO_LOG(IOS_ES, "GetStoredTMDSize: %u bytes for %016" PRIx64, tmd_size, title_id); return GetDefaultReply(IPC_SUCCESS); }
IPCCommandResult ES::GetStoredTMD(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(2, 1)) return GetDefaultReply(ES_EINVAL); const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); const IOS::ES::TMDReader tmd = FindInstalledTMD(title_id); if (!tmd.IsValid()) return GetDefaultReply(FS_ENOENT); // TODO: actually use this param in when writing to the outbuffer :/ const u32 MaxCount = Memory::Read_U32(request.in_vectors[1].address); const std::vector<u8>& raw_tmd = tmd.GetBytes(); if (raw_tmd.size() != request.io_vectors[0].size) return GetDefaultReply(ES_EINVAL); Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size()); INFO_LOG(IOS_ES, "GetStoredTMD: title %016" PRIx64 " (buffer size: %u)", title_id, MaxCount); return GetDefaultReply(IPC_SUCCESS); }
IPCCommandResult ES::GetTMDStoredContents(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(2, 1)) return GetDefaultReply(ES_EINVAL); std::vector<u8> tmd_bytes(request.in_vectors[0].size); Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); const IOS::ES::TMDReader tmd{std::move(tmd_bytes)}; if (!tmd.IsValid()) return GetDefaultReply(ES_EINVAL); std::vector<u8> cert_store; ReturnCode ret = ReadCertStore(&cert_store); if (ret != IPC_SUCCESS) return GetDefaultReply(ret); ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, tmd, cert_store); if (ret != IPC_SUCCESS) return GetDefaultReply(ret); return GetStoredContents(tmd, request); }
// Used by the GetStoredContents ioctlvs. This assumes that the second input vector is used // for the content count and the output vector is used to store a list of content IDs (u32s). IPCCommandResult ES::GetStoredContents(const IOS::ES::TMDReader& tmd, const IOCtlVRequest& request) { if (!tmd.IsValid()) return GetDefaultReply(ES_EINVAL); if (request.in_vectors[1].size != sizeof(u32) || request.io_vectors[0].size != Memory::Read_U32(request.in_vectors[1].address) * sizeof(u32)) { return GetDefaultReply(ES_EINVAL); } const auto contents = GetStoredContentsFromTMD(tmd); const u32 max_content_count = Memory::Read_U32(request.in_vectors[1].address); for (u32 i = 0; i < std::min(static_cast<u32>(contents.size()), max_content_count); ++i) Memory::Write_U32(contents[i].id, request.io_vectors[0].address + i * sizeof(u32)); return GetDefaultReply(IPC_SUCCESS); }
void SConfig::SetRunningGameMetadata(const IOS::ES::TMDReader& tmd) { const u64 tmd_title_id = tmd.GetTitleId(); // If we're launching a disc game, we want to read the revision from // the disc header instead of the TMD. They can differ. // (IOS HLE ES calls us with a TMDReader rather than a volume when launching // a disc game, because ES has no reason to be accessing the disc directly.) if (!DVDInterface::UpdateRunningGameMetadata(tmd_title_id)) { // If not launching a disc game, just read everything from the TMD. const DiscIO::Country country = DiscIO::CountryCodeToCountry(static_cast<u8>(tmd_title_id), DiscIO::Platform::WiiWAD, tmd.GetRegion(), tmd.GetTitleVersion()); SetRunningGameMetadata(tmd.GetGameID(), tmd.GetGameTDBID(), tmd_title_id, tmd.GetTitleVersion(), country); } }
IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) { s32 return_error_code = IPC_SUCCESS; switch (request.request) { case IOCTL_WFSI_IMPORT_TITLE_INIT: { u32 tmd_addr = Memory::Read_U32(request.buffer_in); u32 tmd_size = Memory::Read_U32(request.buffer_in + 4); m_patch_type = static_cast<PatchType>(Memory::Read_U32(request.buffer_in + 32)); m_continue_install = Memory::Read_U32(request.buffer_in + 36); INFO_LOG(IOS_WFS, "IOCTL_WFSI_IMPORT_TITLE_INIT: patch type %d, continue install: %s", m_patch_type, m_continue_install ? "true" : "false"); if (m_patch_type == PatchType::PATCH_TYPE_2) { const std::string content_dir = StringFromFormat("/vol/%s/title/%s/%s/content", m_device_name.c_str(), m_current_group_id_str.c_str(), m_current_title_id_str.c_str()); File::Rename(WFS::NativePath(content_dir + "/default.dol"), WFS::NativePath(content_dir + "/_default.dol")); } if (!IOS::ES::IsValidTMDSize(tmd_size)) { ERROR_LOG(IOS_WFS, "IOCTL_WFSI_IMPORT_TITLE_INIT: TMD size too large (%d)", tmd_size); return_error_code = IPC_EINVAL; break; } std::vector<u8> tmd_bytes; tmd_bytes.resize(tmd_size); Memory::CopyFromEmu(tmd_bytes.data(), tmd_addr, tmd_size); m_tmd.SetBytes(std::move(tmd_bytes)); IOS::ES::TicketReader ticket = m_ios.GetES()->FindSignedTicket(m_tmd.GetTitleId()); if (!ticket.IsValid()) { return_error_code = -11028; break; } memcpy(m_aes_key, ticket.GetTitleKey(m_ios.GetIOSC()).data(), sizeof(m_aes_key)); mbedtls_aes_setkey_dec(&m_aes_ctx, m_aes_key, 128); SetImportTitleIdAndGroupId(m_tmd.GetTitleId(), m_tmd.GetGroupId()); if (m_patch_type == PatchType::PATCH_TYPE_1) CancelPatchImport(m_continue_install); else if (m_patch_type == PatchType::NOT_A_PATCH) CancelTitleImport(m_continue_install); break; } case IOCTL_WFSI_PREPARE_PROFILE: m_base_extract_path = StringFromFormat("/vol/%s/tmp/", m_device_name.c_str()); // Fall through intended. case IOCTL_WFSI_PREPARE_CONTENT: { const char* ioctl_name = request.request == IOCTL_WFSI_PREPARE_PROFILE ? "IOCTL_WFSI_PREPARE_PROFILE" : "IOCTL_WFSI_PREPARE_CONTENT"; // Initializes the IV from the index of the content in the TMD contents. u32 content_id = Memory::Read_U32(request.buffer_in + 8); IOS::ES::Content content_info; if (!m_tmd.FindContentById(content_id, &content_info)) { WARN_LOG(IOS_WFS, "%s: Content id %08x not found", ioctl_name, content_id); return_error_code = -10003; break; } memset(m_aes_iv, 0, sizeof(m_aes_iv)); m_aes_iv[0] = content_info.index >> 8; m_aes_iv[1] = content_info.index & 0xFF; INFO_LOG(IOS_WFS, "%s: Content id %08x found at index %d", ioctl_name, content_id, content_info.index); m_arc_unpacker.Reset(); break; } case IOCTL_WFSI_IMPORT_PROFILE: case IOCTL_WFSI_IMPORT_CONTENT: { const char* ioctl_name = request.request == IOCTL_WFSI_IMPORT_PROFILE ? "IOCTL_WFSI_IMPORT_PROFILE" : "IOCTL_WFSI_IMPORT_CONTENT"; u32 content_id = Memory::Read_U32(request.buffer_in + 0xC); u32 input_ptr = Memory::Read_U32(request.buffer_in + 0x10); u32 input_size = Memory::Read_U32(request.buffer_in + 0x14); INFO_LOG(IOS_WFS, "%s: %08x bytes of data at %08x from content id %d", ioctl_name, input_size, input_ptr, content_id); std::vector<u8> decrypted(input_size); mbedtls_aes_crypt_cbc(&m_aes_ctx, MBEDTLS_AES_DECRYPT, input_size, m_aes_iv, Memory::GetPointer(input_ptr), decrypted.data()); m_arc_unpacker.AddBytes(decrypted); break; } case IOCTL_WFSI_IMPORT_CONTENT_END: case IOCTL_WFSI_IMPORT_PROFILE_END: { const char* ioctl_name = request.request == IOCTL_WFSI_IMPORT_PROFILE_END ? "IOCTL_WFSI_IMPORT_PROFILE_END" : "IOCTL_WFSI_IMPORT_CONTENT_END"; INFO_LOG(IOS_WFS, "%s", ioctl_name); auto callback = [this](const std::string& filename, const std::vector<u8>& bytes) { INFO_LOG(IOS_WFS, "Extract: %s (%zd bytes)", filename.c_str(), bytes.size()); std::string path = WFS::NativePath(m_base_extract_path + "/" + filename); File::CreateFullPath(path); File::IOFile f(path, "wb"); if (!f) { ERROR_LOG(IOS_WFS, "Could not extract %s to %s", filename.c_str(), path.c_str()); return; } f.WriteBytes(bytes.data(), bytes.size()); }; m_arc_unpacker.Extract(callback); // Technically not needed, but let's not keep large buffers in RAM for no // reason if we can avoid it. m_arc_unpacker.Reset(); break; } case IOCTL_WFSI_FINALIZE_TITLE_INSTALL: { std::string tmd_path; if (m_patch_type == NOT_A_PATCH) { std::string title_install_dir = StringFromFormat("/vol/%s/_install/%s", m_device_name.c_str(), m_import_title_id_str.c_str()); std::string title_final_dir = StringFromFormat("/vol/%s/title/%s/%s", m_device_name.c_str(), m_import_group_id_str.c_str(), m_import_title_id_str.c_str()); File::Rename(WFS::NativePath(title_install_dir), WFS::NativePath(title_final_dir)); tmd_path = StringFromFormat("/vol/%s/title/%s/%s/meta/%016" PRIx64 ".tmd", m_device_name.c_str(), m_import_group_id_str.c_str(), m_import_title_id_str.c_str(), m_import_title_id); } else { std::string patch_dir = StringFromFormat("/vol/%s/title/%s/%s/_patch", m_device_name.c_str(), m_current_group_id_str.c_str(), m_current_title_id_str.c_str()); File::DeleteDirRecursively(WFS::NativePath(patch_dir)); tmd_path = StringFromFormat("/vol/%s/title/%s/%s/meta/%016" PRIx64 ".tmd", m_device_name.c_str(), m_current_group_id_str.c_str(), m_current_title_id_str.c_str(), m_import_title_id); } File::IOFile tmd_file(WFS::NativePath(tmd_path), "wb"); tmd_file.WriteBytes(m_tmd.GetBytes().data(), m_tmd.GetBytes().size()); break; } case IOCTL_WFSI_FINALIZE_PATCH_INSTALL: { INFO_LOG(IOS_WFS, "IOCTL_WFSI_FINALIZE_PATCH_INSTALL"); if (m_patch_type != NOT_A_PATCH) { std::string current_title_dir = StringFromFormat("/vol/%s/title/%s/%s", m_device_name.c_str(), m_current_group_id_str.c_str(), m_current_title_id_str.c_str()); std::string patch_dir = current_title_dir + "/_patch"; File::CopyDir(WFS::NativePath(patch_dir), WFS::NativePath(current_title_dir), true); } break; } case IOCTL_WFSI_DELETE_TITLE: // Bytes 0-4: ?? // Bytes 4-8: game id // Bytes 1c-1e: title id? WARN_LOG(IOS_WFS, "IOCTL_WFSI_DELETE_TITLE: unimplemented"); break; case IOCTL_WFSI_GET_VERSION: INFO_LOG(IOS_WFS, "IOCTL_WFSI_GET_VERSION"); Memory::Write_U32(0x20, request.buffer_out); break; case IOCTL_WFSI_IMPORT_TITLE_CANCEL: { INFO_LOG(IOS_WFS, "IOCTL_WFSI_IMPORT_TITLE_CANCEL"); bool continue_install = Memory::Read_U32(request.buffer_in) != 0; if (m_patch_type == PatchType::NOT_A_PATCH) return_error_code = CancelTitleImport(continue_install); else if (m_patch_type == PatchType::PATCH_TYPE_1 || m_patch_type == PatchType::PATCH_TYPE_2) return_error_code = CancelPatchImport(continue_install); else return_error_code = WFS_EINVAL; m_tmd = {}; break; } case IOCTL_WFSI_INIT: { INFO_LOG(IOS_WFS, "IOCTL_WFSI_INIT"); u64 tid; if (GetIOS()->GetES()->GetTitleId(&tid) < 0) { ERROR_LOG(IOS_WFS, "IOCTL_WFSI_INIT: Could not get title id."); return_error_code = IPC_EINVAL; break; } IOS::ES::TMDReader tmd = GetIOS()->GetES()->FindInstalledTMD(tid); SetCurrentTitleIdAndGroupId(tmd.GetTitleId(), tmd.GetGroupId()); break; } case IOCTL_WFSI_SET_DEVICE_NAME: INFO_LOG(IOS_WFS, "IOCTL_WFSI_SET_DEVICE_NAME"); m_device_name = Memory::GetString(request.buffer_in); break; case IOCTL_WFSI_APPLY_TITLE_PROFILE: { INFO_LOG(IOS_WFS, "IOCTL_WFSI_APPLY_TITLE_PROFILE"); if (m_patch_type == NOT_A_PATCH) { std::string install_directory = StringFromFormat("/vol/%s/_install", m_device_name.c_str()); if (!m_continue_install && File::IsDirectory(WFS::NativePath(install_directory))) { File::DeleteDirRecursively(WFS::NativePath(install_directory)); } m_base_extract_path = StringFromFormat("%s/%s/content", install_directory.c_str(), m_import_title_id_str.c_str()); File::CreateFullPath(WFS::NativePath(m_base_extract_path)); File::CreateDir(WFS::NativePath(m_base_extract_path)); for (auto dir : {"work", "meta", "save"}) { std::string path = StringFromFormat("%s/%s/%s", install_directory.c_str(), m_import_title_id_str.c_str(), dir); File::CreateDir(WFS::NativePath(path)); } std::string group_path = StringFromFormat("/vol/%s/title/%s", m_device_name.c_str(), m_import_group_id_str.c_str()); File::CreateFullPath(WFS::NativePath(group_path)); File::CreateDir(WFS::NativePath(group_path)); } else { m_base_extract_path = StringFromFormat("/vol/%s/title/%s/%s/_patch/content", m_device_name.c_str(), m_current_group_id_str.c_str(), m_current_title_id_str.c_str()); File::CreateFullPath(WFS::NativePath(m_base_extract_path)); File::CreateDir(WFS::NativePath(m_base_extract_path)); } break; } case IOCTL_WFSI_GET_TMD: { u64 subtitle_id = Memory::Read_U64(request.buffer_in); u32 address = Memory::Read_U32(request.buffer_in + 24); INFO_LOG(IOS_WFS, "IOCTL_WFSI_GET_TMD: subtitle ID %016" PRIx64, subtitle_id); u32 tmd_size; return_error_code = GetTmd(m_current_group_id, m_current_title_id, subtitle_id, address, &tmd_size); Memory::Write_U32(tmd_size, request.buffer_out); break; } case IOCTL_WFSI_GET_TMD_ABSOLUTE: { u64 subtitle_id = Memory::Read_U64(request.buffer_in); u32 address = Memory::Read_U32(request.buffer_in + 24); u16 group_id = Memory::Read_U16(request.buffer_in + 36); u32 title_id = Memory::Read_U32(request.buffer_in + 32); INFO_LOG(IOS_WFS, "IOCTL_WFSI_GET_TMD_ABSOLUTE: tid %08x, gid %04x, subtitle ID %016" PRIx64, title_id, group_id, subtitle_id); u32 tmd_size; return_error_code = GetTmd(group_id, title_id, subtitle_id, address, &tmd_size); Memory::Write_U32(tmd_size, request.buffer_out); break; } case IOCTL_WFSI_SET_FST_BUFFER: { INFO_LOG(IOS_WFS, "IOCTL_WFSI_SET_FST_BUFFER: address %08x, size %08x", request.buffer_in, request.buffer_in_size); break; } case IOCTL_WFSI_NOOP: break; case IOCTL_WFSI_LOAD_DOL: { std::string path = StringFromFormat("/vol/%s/title/%s/%s/content", m_device_name.c_str(), m_current_group_id_str.c_str(), m_current_title_id_str.c_str()); u32 dol_addr = Memory::Read_U32(request.buffer_in + 0x18); u32 max_dol_size = Memory::Read_U32(request.buffer_in + 0x14); u16 dol_extension_id = Memory::Read_U16(request.buffer_in + 0x1e); if (dol_extension_id == 0) { path += "/default.dol"; } else { path += StringFromFormat("/extension%d.dol", dol_extension_id); } INFO_LOG(IOS_WFS, "IOCTL_WFSI_LOAD_DOL: loading %s at address %08x (size %d)", path.c_str(), dol_addr, max_dol_size); File::IOFile fp(WFS::NativePath(path), "rb"); if (!fp) { WARN_LOG(IOS_WFS, "IOCTL_WFSI_LOAD_DOL: no such file or directory: %s", path.c_str()); return_error_code = WFS_ENOENT; break; } u32 real_dol_size = fp.GetSize(); if (dol_addr == 0) { // Write the expected size to the size parameter, in the input. Memory::Write_U32(real_dol_size, request.buffer_in + 0x14); } else { fp.ReadBytes(Memory::GetPointer(dol_addr), max_dol_size); } Memory::Write_U32(real_dol_size, request.buffer_out); break; } case IOCTL_WFSI_CHECK_HAS_SPACE: WARN_LOG(IOS_WFS, "IOCTL_WFSI_CHECK_HAS_SPACE: returning true"); // TODO(wfs): implement this properly. // 1 is returned if there is free space, 0 otherwise. // // WFSI builds a path depending on the import state // /vol/VOLUME_ID/title/GROUP_ID/GAME_ID // /vol/VOLUME_ID/_install/GAME_ID // then removes everything after the last path separator ('/') // it then calls WFSISrvGetFreeBlkNum (ioctl 0x5a, aliased to 0x5b) with that path. // If the ioctl fails, WFSI returns 0. // If the ioctl succeeds, WFSI returns 0 or 1 depending on the three u32s in the input buffer // and the three u32s returned by WFSSRV (TODO: figure out what it does) return_error_code = 1; break; default: // TODO(wfs): Should be returning an error. However until we have // everything properly stubbed it's easier to simulate the methods // succeeding. request.DumpUnknown(GetDeviceName(), LogTypes::IOS, LogTypes::LWARNING); Memory::Memset(request.buffer_out, 0, request.buffer_out_size); break; } return GetDefaultReply(return_error_code); }