ResultCode HostFileSystem::Rename(Uid, Gid, const std::string& old_path, const std::string& new_path) { if (!IsValidWiiPath(old_path)) return ResultCode::Invalid; const std::string old_name = BuildFilename(old_path); if (!IsValidWiiPath(new_path)) return ResultCode::Invalid; const std::string new_name = BuildFilename(new_path); // try to make the basis directory File::CreateFullPath(new_name); // if there is already a file, delete it if (File::Exists(old_name) && File::Exists(new_name)) { File::Delete(new_name); } // finally try to rename the file if (!File::Rename(old_name, new_name)) { ERROR_LOG(IOS_FS, "Rename %s to %s - failed", old_name.c_str(), new_name.c_str()); return ResultCode::NotFound; } return ResultCode::Success; }
ResultCode HostFileSystem::SetMetadata(Uid caller_uid, const std::string& path, Uid uid, Gid gid, FileAttribute, Modes) { if (!IsValidWiiPath(path)) return ResultCode::Invalid; return ResultCode::Success; }
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; }
ResultCode HostFileSystem::CreateDirectory(Uid, Gid, const std::string& path, FileAttribute, Modes) { if (!IsValidWiiPath(path)) return ResultCode::Invalid; std::string name(BuildFilename(path)); name += "/"; File::CreateFullPath(name); DEBUG_ASSERT_MSG(IOS_FS, File::IsDirectory(name), "CREATE_DIR %s failed", name.c_str()); return ResultCode::Success; }
ResultCode HostFileSystem::Rename(Uid, Gid, const std::string& old_path, const std::string& new_path) { if (!IsValidWiiPath(old_path)) return ResultCode::Invalid; const std::string old_name = BuildFilename(old_path); if (!IsValidWiiPath(new_path)) return ResultCode::Invalid; const std::string new_name = BuildFilename(new_path); // try to make the basis directory File::CreateFullPath(new_name); // If there is already something of the same type at the new path, delete it. if (File::Exists(new_name)) { const bool old_is_file = File::IsFile(old_name); const bool new_is_file = File::IsFile(new_name); if (old_is_file && new_is_file) File::Delete(new_name); else if (!old_is_file && !new_is_file) File::DeleteDirRecursively(new_name); else return ResultCode::Invalid; } // finally try to rename the file if (!File::Rename(old_name, new_name)) { ERROR_LOG(IOS_FS, "Rename %s to %s - failed", old_name.c_str(), new_name.c_str()); return ResultCode::NotFound; } return ResultCode::Success; }
ResultCode HostFileSystem::Delete(Uid, Gid, const std::string& path) { if (!IsValidWiiPath(path)) return ResultCode::Invalid; const std::string file_name = BuildFilename(path); if (File::Delete(file_name)) INFO_LOG(IOS_FS, "DeleteFile %s", file_name.c_str()); else if (File::DeleteDirRecursively(file_name)) INFO_LOG(IOS_FS, "DeleteDir %s", file_name.c_str()); else WARN_LOG(IOS_FS, "DeleteFile %s - failed!!!", file_name.c_str()); return ResultCode::Success; }
Result<std::vector<std::string>> HostFileSystem::ReadDirectory(Uid, Gid, const std::string& path) { if (!IsValidWiiPath(path)) return ResultCode::Invalid; // the Wii uses this function to define the type (dir or file) const std::string dir_name(BuildFilename(path)); const File::FileInfo file_info(dir_name); if (!file_info.Exists()) { WARN_LOG(IOS_FS, "Search not found: %s", dir_name.c_str()); return ResultCode::NotFound; } if (!file_info.IsDirectory()) { // It's not a directory, so error. return ResultCode::Invalid; } File::FSTEntry entry = File::ScanDirectoryTree(dir_name, false); for (File::FSTEntry& child : entry.children) { // Decode escaped invalid file system characters so that games (such as // Harry Potter and the Half-Blood Prince) can find what they expect. child.virtualName = Common::UnescapeFileName(child.virtualName); } // NOTE(leoetlino): this is absolutely wrong, but there is no way to fix this properly // if we use the host filesystem. std::sort(entry.children.begin(), entry.children.end(), [](const File::FSTEntry& one, const File::FSTEntry& two) { return one.virtualName < two.virtualName; }); std::vector<std::string> output; for (File::FSTEntry& child : entry.children) output.emplace_back(child.virtualName); return output; }
Result<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_path) { if (!IsValidWiiPath(wii_path)) return ResultCode::Invalid; DirectoryStats stats{}; std::string path(BuildFilename(wii_path)); if (File::IsDirectory(path)) { File::FSTEntry parent_dir = File::ScanDirectoryTree(path, true); // add one for the folder itself stats.used_inodes = 1 + (u32)parent_dir.size; u64 total_size = ComputeTotalFileSize(parent_dir); // "Real" size to convert to nand blocks stats.used_clusters = (u32)(total_size / (16 * 1024)); // one block is 16kb } else { WARN_LOG(IOS_FS, "fsBlock failed, cannot find directory: %s", path.c_str()); } return stats; }
IPCCommandResult CWII_IPC_HLE_Device_fs::IOCtlV(u32 _CommandAddress) { u32 ReturnValue = FS_RESULT_OK; SIOCtlVBuffer CommandBuffer(_CommandAddress); // Prepare the out buffer(s) with zeros as a safety precaution // to avoid returning bad values for (u32 i = 0; i < CommandBuffer.NumberPayloadBuffer; i++) { Memory::Memset(CommandBuffer.PayloadBuffer[i].m_Address, 0, CommandBuffer.PayloadBuffer[i].m_Size); } switch (CommandBuffer.Parameter) { case IOCTLV_READ_DIR: { const std::string relative_path = Memory::GetString(CommandBuffer.InBuffer[0].m_Address, CommandBuffer.InBuffer[0].m_Size); if (!IsValidWiiPath(relative_path)) { WARN_LOG(WII_IPC_FILEIO, "Not a valid path: %s", relative_path.c_str()); ReturnValue = FS_RESULT_FATAL; break; } // the Wii uses this function to define the type (dir or file) std::string DirName(HLE_IPC_BuildFilename(relative_path)); INFO_LOG(WII_IPC_FILEIO, "FS: IOCTL_READ_DIR %s", DirName.c_str()); if (!File::Exists(DirName)) { WARN_LOG(WII_IPC_FILEIO, "FS: Search not found: %s", DirName.c_str()); ReturnValue = FS_FILE_NOT_EXIST; break; } else if (!File::IsDirectory(DirName)) { // It's not a directory, so error. // Games don't usually seem to care WHICH error they get, as long as it's < // Well the system menu CARES! WARN_LOG(WII_IPC_FILEIO, "\tNot a directory - return FS_RESULT_FATAL"); ReturnValue = FS_RESULT_FATAL; break; } File::FSTEntry entry = File::ScanDirectoryTree(DirName, false); // it is one if ((CommandBuffer.InBuffer.size() == 1) && (CommandBuffer.PayloadBuffer.size() == 1)) { size_t numFile = entry.children.size(); INFO_LOG(WII_IPC_FILEIO, "\t%zu files found", numFile); Memory::Write_U32((u32)numFile, CommandBuffer.PayloadBuffer[0].m_Address); } else { for (File::FSTEntry& child : entry.children) { // Decode escaped invalid file system characters so that games (such as // Harry Potter and the Half-Blood Prince) can find what they expect. child.virtualName = Common::UnescapeFileName(child.virtualName); } std::sort(entry.children.begin(), entry.children.end(), [](const File::FSTEntry& one, const File::FSTEntry& two) { return one.virtualName < two.virtualName; }); u32 MaxEntries = Memory::Read_U32(CommandBuffer.InBuffer[0].m_Address); memset(Memory::GetPointer(CommandBuffer.PayloadBuffer[0].m_Address), 0, CommandBuffer.PayloadBuffer[0].m_Size); size_t numFiles = 0; char* pFilename = (char*)Memory::GetPointer((u32)(CommandBuffer.PayloadBuffer[0].m_Address)); for (size_t i = 0; i < entry.children.size() && i < MaxEntries; i++) { const std::string& FileName = entry.children[i].virtualName; strcpy(pFilename, FileName.c_str()); pFilename += FileName.length(); *pFilename++ = 0x00; // termination numFiles++; INFO_LOG(WII_IPC_FILEIO, "\tFound: %s", FileName.c_str()); } Memory::Write_U32((u32)numFiles, CommandBuffer.PayloadBuffer[1].m_Address); } ReturnValue = FS_RESULT_OK; } break; case IOCTLV_GETUSAGE: { _dbg_assert_(WII_IPC_FILEIO, CommandBuffer.PayloadBuffer.size() == 2); _dbg_assert_(WII_IPC_FILEIO, CommandBuffer.PayloadBuffer[0].m_Size == 4); _dbg_assert_(WII_IPC_FILEIO, CommandBuffer.PayloadBuffer[1].m_Size == 4); // this command sucks because it asks of the number of used // fsBlocks and inodes // It should be correct, but don't count on it... std::string relativepath = Memory::GetString(CommandBuffer.InBuffer[0].m_Address, CommandBuffer.InBuffer[0].m_Size); if (!IsValidWiiPath(relativepath)) { WARN_LOG(WII_IPC_FILEIO, "Not a valid path: %s", relativepath.c_str()); ReturnValue = FS_RESULT_FATAL; break; } std::string path(HLE_IPC_BuildFilename(relativepath)); u32 fsBlocks = 0; u32 iNodes = 0; INFO_LOG(WII_IPC_FILEIO, "IOCTL_GETUSAGE %s", path.c_str()); if (File::IsDirectory(path)) { // LPFaint99: After I found that setting the number of inodes to the number of children + 1 // for the directory itself // I decided to compare with sneek which has the following 2 special cases which are // Copyright (C) 2009-2011 crediar http://code.google.com/p/sneek/ if ((relativepath.compare(0, 16, "/title/00010001") == 0) || (relativepath.compare(0, 16, "/title/00010005") == 0)) { fsBlocks = 23; // size is size/0x4000 iNodes = 42; // empty folders return a FileCount of 1 } else { File::FSTEntry parentDir = File::ScanDirectoryTree(path, true); // add one for the folder itself iNodes = 1 + (u32)parentDir.size; u64 totalSize = ComputeTotalFileSize(parentDir); // "Real" size, to be converted to nand blocks fsBlocks = (u32)(totalSize / (16 * 1024)); // one bock is 16kb } ReturnValue = FS_RESULT_OK; INFO_LOG(WII_IPC_FILEIO, "FS: fsBlock: %i, iNodes: %i", fsBlocks, iNodes); } else { fsBlocks = 0; iNodes = 0; ReturnValue = FS_RESULT_OK; WARN_LOG(WII_IPC_FILEIO, "FS: fsBlock failed, cannot find directory: %s", path.c_str()); } Memory::Write_U32(fsBlocks, CommandBuffer.PayloadBuffer[0].m_Address); Memory::Write_U32(iNodes, CommandBuffer.PayloadBuffer[1].m_Address); } break; default: PanicAlert("CWII_IPC_HLE_Device_fs::IOCtlV: %i", CommandBuffer.Parameter); break; } Memory::Write_U32(ReturnValue, _CommandAddress + 4); return GetFSReply(); }
s32 CWII_IPC_HLE_Device_fs::ExecuteCommand(u32 _Parameter, u32 _BufferIn, u32 _BufferInSize, u32 _BufferOut, u32 _BufferOutSize) { switch (_Parameter) { case IOCTL_GET_STATS: { if (_BufferOutSize < 0x1c) return -1017; WARN_LOG(WII_IPC_FILEIO, "FS: GET STATS - returning static values for now"); NANDStat fs; // TODO: scrape the real amounts from somewhere... fs.BlockSize = 0x4000; fs.FreeUserBlocks = 0x5DEC; fs.UsedUserBlocks = 0x1DD4; fs.FreeSysBlocks = 0x10; fs.UsedSysBlocks = 0x02F0; fs.Free_INodes = 0x146B; fs.Used_Inodes = 0x0394; std::memcpy(Memory::GetPointer(_BufferOut), &fs, sizeof(NANDStat)); return FS_RESULT_OK; } break; case IOCTL_CREATE_DIR: { _dbg_assert_(WII_IPC_FILEIO, _BufferOutSize == 0); u32 Addr = _BufferIn; u32 OwnerID = Memory::Read_U32(Addr); Addr += 4; u16 GroupID = Memory::Read_U16(Addr); Addr += 2; const std::string wii_path = Memory::GetString(Addr, 64); if (!IsValidWiiPath(wii_path)) { WARN_LOG(WII_IPC_FILEIO, "Not a valid path: %s", wii_path.c_str()); return FS_RESULT_FATAL; } std::string DirName(HLE_IPC_BuildFilename(wii_path)); Addr += 64; Addr += 9; // owner attribs, permission u8 Attribs = Memory::Read_U8(Addr); INFO_LOG(WII_IPC_FILEIO, "FS: CREATE_DIR %s, OwnerID %#x, GroupID %#x, Attributes %#x", DirName.c_str(), OwnerID, GroupID, Attribs); DirName += DIR_SEP; File::CreateFullPath(DirName); _dbg_assert_msg_(WII_IPC_FILEIO, File::IsDirectory(DirName), "FS: CREATE_DIR %s failed", DirName.c_str()); return FS_RESULT_OK; } break; case IOCTL_SET_ATTR: { u32 Addr = _BufferIn; u32 OwnerID = Memory::Read_U32(Addr); Addr += 4; u16 GroupID = Memory::Read_U16(Addr); Addr += 2; const std::string wii_path = Memory::GetString(_BufferIn, 64); if (!IsValidWiiPath(wii_path)) { WARN_LOG(WII_IPC_FILEIO, "Not a valid path: %s", wii_path.c_str()); return FS_RESULT_FATAL; } std::string Filename = HLE_IPC_BuildFilename(wii_path); Addr += 64; u8 OwnerPerm = Memory::Read_U8(Addr); Addr += 1; u8 GroupPerm = Memory::Read_U8(Addr); Addr += 1; u8 OtherPerm = Memory::Read_U8(Addr); Addr += 1; u8 Attributes = Memory::Read_U8(Addr); Addr += 1; INFO_LOG(WII_IPC_FILEIO, "FS: SetAttrib %s", Filename.c_str()); DEBUG_LOG(WII_IPC_FILEIO, " OwnerID: 0x%08x", OwnerID); DEBUG_LOG(WII_IPC_FILEIO, " GroupID: 0x%04x", GroupID); DEBUG_LOG(WII_IPC_FILEIO, " OwnerPerm: 0x%02x", OwnerPerm); DEBUG_LOG(WII_IPC_FILEIO, " GroupPerm: 0x%02x", GroupPerm); DEBUG_LOG(WII_IPC_FILEIO, " OtherPerm: 0x%02x", OtherPerm); DEBUG_LOG(WII_IPC_FILEIO, " Attributes: 0x%02x", Attributes); return FS_RESULT_OK; } break; case IOCTL_GET_ATTR: { _dbg_assert_msg_(WII_IPC_FILEIO, _BufferOutSize == 76, " GET_ATTR needs an 76 bytes large output buffer but it is %i bytes large", _BufferOutSize); u32 OwnerID = 0; u16 GroupID = 0x3031; // this is also known as makercd, 01 (0x3031) for nintendo and 08 // (0x3038) for MH3 etc const std::string wii_path = Memory::GetString(_BufferIn, 64); if (!IsValidWiiPath(wii_path)) { WARN_LOG(WII_IPC_FILEIO, "Not a valid path: %s", wii_path.c_str()); return FS_RESULT_FATAL; } std::string Filename = HLE_IPC_BuildFilename(wii_path); u8 OwnerPerm = 0x3; // read/write u8 GroupPerm = 0x3; // read/write u8 OtherPerm = 0x3; // read/write u8 Attributes = 0x00; // no attributes if (File::IsDirectory(Filename)) { INFO_LOG(WII_IPC_FILEIO, "FS: GET_ATTR Directory %s - all permission flags are set", Filename.c_str()); } else { if (File::Exists(Filename)) { INFO_LOG(WII_IPC_FILEIO, "FS: GET_ATTR %s - all permission flags are set", Filename.c_str()); } else { INFO_LOG(WII_IPC_FILEIO, "FS: GET_ATTR unknown %s", Filename.c_str()); return FS_FILE_NOT_EXIST; } } // write answer to buffer if (_BufferOutSize == 76) { u32 Addr = _BufferOut; Memory::Write_U32(OwnerID, Addr); Addr += 4; Memory::Write_U16(GroupID, Addr); Addr += 2; memcpy(Memory::GetPointer(Addr), Memory::GetPointer(_BufferIn), 64); Addr += 64; Memory::Write_U8(OwnerPerm, Addr); Addr += 1; Memory::Write_U8(GroupPerm, Addr); Addr += 1; Memory::Write_U8(OtherPerm, Addr); Addr += 1; Memory::Write_U8(Attributes, Addr); Addr += 1; } return FS_RESULT_OK; } break; case IOCTL_DELETE_FILE: { _dbg_assert_(WII_IPC_FILEIO, _BufferOutSize == 0); int Offset = 0; const std::string wii_path = Memory::GetString(_BufferIn + Offset, 64); if (!IsValidWiiPath(wii_path)) { WARN_LOG(WII_IPC_FILEIO, "Not a valid path: %s", wii_path.c_str()); return FS_RESULT_FATAL; } std::string Filename = HLE_IPC_BuildFilename(wii_path); Offset += 64; if (File::Delete(Filename)) { INFO_LOG(WII_IPC_FILEIO, "FS: DeleteFile %s", Filename.c_str()); } else if (File::DeleteDir(Filename)) { INFO_LOG(WII_IPC_FILEIO, "FS: DeleteDir %s", Filename.c_str()); } else { WARN_LOG(WII_IPC_FILEIO, "FS: DeleteFile %s - failed!!!", Filename.c_str()); } return FS_RESULT_OK; } break; case IOCTL_RENAME_FILE: { _dbg_assert_(WII_IPC_FILEIO, _BufferOutSize == 0); int Offset = 0; const std::string wii_path = Memory::GetString(_BufferIn + Offset, 64); if (!IsValidWiiPath(wii_path)) { WARN_LOG(WII_IPC_FILEIO, "Not a valid path: %s", wii_path.c_str()); return FS_RESULT_FATAL; } std::string Filename = HLE_IPC_BuildFilename(wii_path); Offset += 64; const std::string wii_path_rename = Memory::GetString(_BufferIn + Offset, 64); if (!IsValidWiiPath(wii_path_rename)) { WARN_LOG(WII_IPC_FILEIO, "Not a valid path: %s", wii_path_rename.c_str()); return FS_RESULT_FATAL; } std::string FilenameRename = HLE_IPC_BuildFilename(wii_path_rename); Offset += 64; // try to make the basis directory File::CreateFullPath(FilenameRename); // if there is already a file, delete it if (File::Exists(Filename) && File::Exists(FilenameRename)) { File::Delete(FilenameRename); } // finally try to rename the file if (File::Rename(Filename, FilenameRename)) { INFO_LOG(WII_IPC_FILEIO, "FS: Rename %s to %s", Filename.c_str(), FilenameRename.c_str()); } else { ERROR_LOG(WII_IPC_FILEIO, "FS: Rename %s to %s - failed", Filename.c_str(), FilenameRename.c_str()); return FS_FILE_NOT_EXIST; } return FS_RESULT_OK; } break; case IOCTL_CREATE_FILE: { _dbg_assert_(WII_IPC_FILEIO, _BufferOutSize == 0); u32 Addr = _BufferIn; u32 OwnerID = Memory::Read_U32(Addr); Addr += 4; u16 GroupID = Memory::Read_U16(Addr); Addr += 2; const std::string wii_path = Memory::GetString(Addr, 64); if (!IsValidWiiPath(wii_path)) { WARN_LOG(WII_IPC_FILEIO, "Not a valid path: %s", wii_path.c_str()); return FS_RESULT_FATAL; } std::string Filename(HLE_IPC_BuildFilename(wii_path)); Addr += 64; u8 OwnerPerm = Memory::Read_U8(Addr); Addr++; u8 GroupPerm = Memory::Read_U8(Addr); Addr++; u8 OtherPerm = Memory::Read_U8(Addr); Addr++; u8 Attributes = Memory::Read_U8(Addr); Addr++; INFO_LOG(WII_IPC_FILEIO, "FS: CreateFile %s", Filename.c_str()); DEBUG_LOG(WII_IPC_FILEIO, " OwnerID: 0x%08x", OwnerID); DEBUG_LOG(WII_IPC_FILEIO, " GroupID: 0x%04x", GroupID); DEBUG_LOG(WII_IPC_FILEIO, " OwnerPerm: 0x%02x", OwnerPerm); DEBUG_LOG(WII_IPC_FILEIO, " GroupPerm: 0x%02x", GroupPerm); DEBUG_LOG(WII_IPC_FILEIO, " OtherPerm: 0x%02x", OtherPerm); DEBUG_LOG(WII_IPC_FILEIO, " Attributes: 0x%02x", Attributes); // check if the file already exist if (File::Exists(Filename)) { INFO_LOG(WII_IPC_FILEIO, "\tresult = FS_RESULT_EXISTS"); return FS_FILE_EXIST; } // create the file File::CreateFullPath(Filename); // just to be sure bool Result = File::CreateEmptyFile(Filename); if (!Result) { ERROR_LOG(WII_IPC_FILEIO, "CWII_IPC_HLE_Device_fs: couldn't create new file"); PanicAlert("CWII_IPC_HLE_Device_fs: couldn't create new file"); return FS_RESULT_FATAL; } INFO_LOG(WII_IPC_FILEIO, "\tresult = FS_RESULT_OK"); return FS_RESULT_OK; } break; case IOCTL_SHUTDOWN: { INFO_LOG(WII_IPC_FILEIO, "Wii called Shutdown()"); // TODO: stop emulation } break; default: ERROR_LOG(WII_IPC_FILEIO, "CWII_IPC_HLE_Device_fs::IOCtl: ni 0x%x", _Parameter); PanicAlert("CWII_IPC_HLE_Device_fs::IOCtl: ni 0x%x", _Parameter); break; } return FS_RESULT_FATAL; }