diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 725d5134c5..84647fd4e9 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -521,7 +521,7 @@ bool DeleteDirRecursively(const std::string& directory) } // Create directory and copy contents (does not overwrite existing files) -void CopyDir(const std::string& source_path, const std::string& dest_path) +void CopyDir(const std::string& source_path, const std::string& dest_path, bool destructive) { if (source_path == dest_path) return; @@ -562,10 +562,16 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) { if (!Exists(dest)) File::CreateFullPath(dest + DIR_SEP); - CopyDir(source, dest); + CopyDir(source, dest, destructive); + } + else if (!Exists(dest) && !destructive) + { + Copy(source, dest); + } + else + { + Rename(source, dest); } - else if (!Exists(dest)) - File::Copy(source, dest); #ifdef _WIN32 } while (FindNextFile(hFind, &ffd) != 0); FindClose(hFind); diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 398abc748e..007fb3c541 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -156,8 +156,9 @@ bool DeleteDirRecursively(const std::string& directory); // Returns the current directory std::string GetCurrentDir(); -// Create directory and copy contents (does not overwrite existing files) -void CopyDir(const std::string& source_path, const std::string& dest_path); +// Create directory and copy contents (optionally overwrites existing files) +void CopyDir(const std::string& source_path, const std::string& dest_path, + bool destructive = false); // Set the current directory to given directory bool SetCurrentDir(const std::string& directory); diff --git a/Source/Core/Core/IOS/WFS/WFSI.cpp b/Source/Core/Core/IOS/WFS/WFSI.cpp index a2e856831e..a0d8610bde 100644 --- a/Source/Core/Core/IOS/WFS/WFSI.cpp +++ b/Source/Core/Core/IOS/WFS/WFSI.cpp @@ -4,6 +4,7 @@ #include "Core/IOS/WFS/WFSI.h" +#include #include #include #include @@ -20,6 +21,20 @@ #include "Core/IOS/WFS/WFSSRV.h" #include "DiscIO/NANDContentLoader.h" +namespace +{ +std::string TitleIdStr(u64 tid) +{ + return StringFromFormat("%c%c%c%c", static_cast(tid >> 24), static_cast(tid >> 16), + static_cast(tid >> 8), static_cast(tid)); +} + +std::string GroupIdStr(u16 gid) +{ + return StringFromFormat("%c%c", gid >> 8, gid & 0xFF); +} +} // namespace + namespace IOS { namespace HLE @@ -86,23 +101,54 @@ WFSI::WFSI(Kernel& ios, const std::string& device_name) : Device(ios, device_nam { } +void WFSI::SetCurrentTitleIdAndGroupId(u64 tid, u16 gid) +{ + m_current_title_id = tid; + m_current_group_id = gid; + + m_current_title_id_str = TitleIdStr(tid); + m_current_group_id_str = GroupIdStr(gid); +} + +void WFSI::SetImportTitleIdAndGroupId(u64 tid, u16 gid) +{ + m_import_title_id = tid; + m_import_group_id = gid; + + m_import_title_id_str = TitleIdStr(tid); + m_import_group_id_str = GroupIdStr(gid); +} + IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) { s32 return_error_code = IPC_SUCCESS; switch (request.request) { - case IOCTL_WFSI_PREPARE_DEVICE: + 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); - INFO_LOG(IOS_WFS, "IOCTL_WFSI_PREPARE_DEVICE"); + m_patch_type = static_cast(Memory::Read_U32(request.buffer_in + 32)); + m_continue_install = Memory::Read_U32(request.buffer_in + 36); - constexpr u32 MAX_TMD_SIZE = 0x4000; - if (tmd_size > MAX_TMD_SIZE) + 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) { - ERROR_LOG(IOS_WFS, "IOCTL_WFSI_PREPARE_DEVICE: TMD size too large (%d)", tmd_size); + 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; } @@ -121,6 +167,13 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) 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; } @@ -175,12 +228,12 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) break; } - case IOCTL_WFSI_FINALIZE_PROFILE: - case IOCTL_WFSI_FINALIZE_CONTENT: + case IOCTL_WFSI_IMPORT_CONTENT_END: + case IOCTL_WFSI_IMPORT_PROFILE_END: { - const char* ioctl_name = request.request == IOCTL_WFSI_FINALIZE_PROFILE ? - "IOCTL_WFSI_FINALIZE_PROFILE" : - "IOCTL_WFSI_FINALIZE_CONTENT"; + 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& bytes) { @@ -204,6 +257,53 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) 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 @@ -211,26 +311,40 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) WARN_LOG(IOS_WFS, "IOCTL_WFSI_DELETE_TITLE: unimplemented"); break; - case IOCTL_WFSI_IMPORT_TITLE: - WARN_LOG(IOS_WFS, "IOCTL_WFSI_IMPORT_TITLE: unimplemented"); + 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"); - if (GetIOS()->GetES()->GetTitleId(&m_title_id) < 0) + 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; } - m_title_id_str = StringFromFormat( - "%c%c%c%c", static_cast(m_title_id >> 24), static_cast(m_title_id >> 16), - static_cast(m_title_id >> 8), static_cast(m_title_id)); - IOS::ES::TMDReader tmd = GetIOS()->GetES()->FindInstalledTMD(m_title_id); - m_group_id = tmd.GetGroupId(); - m_group_id_str = StringFromFormat("%c%c", m_group_id >> 8, m_group_id & 0xFF); + IOS::ES::TMDReader tmd = GetIOS()->GetES()->FindInstalledTMD(tid); + SetCurrentTitleIdAndGroupId(tmd.GetTitleId(), tmd.GetGroupId()); break; } @@ -240,18 +354,89 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) break; case IOCTL_WFSI_APPLY_TITLE_PROFILE: + { INFO_LOG(IOS_WFS, "IOCTL_WFSI_APPLY_TITLE_PROFILE"); - m_base_extract_path = StringFromFormat("/vol/%s/_install/%s/content", m_device_name.c_str(), - m_title_id_str.c_str()); - File::CreateFullPath(WFS::NativePath(m_base_extract_path)); + 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_group_id_str.c_str(), m_title_id_str.c_str()); + 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); @@ -273,7 +458,7 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) if (!fp) { WARN_LOG(IOS_WFS, "IOCTL_WFSI_LOAD_DOL: no such file or directory: %s", path.c_str()); - return_error_code = WFSI_ENOENT; + return_error_code = WFS_ENOENT; break; } @@ -291,6 +476,23 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) 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 @@ -302,6 +504,75 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) return GetDefaultReply(return_error_code); } + +u32 WFSI::GetTmd(u16 group_id, u32 title_id, u64 subtitle_id, u32 address, u32* size) const +{ + std::string path = + StringFromFormat("/vol/%s/title/%s/%s/meta/%016" PRIx64 ".tmd", m_device_name.c_str(), + GroupIdStr(group_id).c_str(), TitleIdStr(title_id).c_str(), subtitle_id); + File::IOFile fp(WFS::NativePath(path), "rb"); + if (!fp) + { + WARN_LOG(IOS_WFS, "GetTmd: no such file or directory: %s", path.c_str()); + return WFS_ENOENT; + } + if (address) + { + fp.ReadBytes(Memory::GetPointer(address), fp.GetSize()); + } + *size = fp.GetSize(); + return IPC_SUCCESS; +} + +static s32 DeleteTemporaryFiles(const std::string& device_name, u64 title_id) +{ + File::Delete(WFS::NativePath( + StringFromFormat("/vol/%s/tmp/%016" PRIx64 ".ini", device_name.c_str(), title_id))); + File::Delete(WFS::NativePath( + StringFromFormat("/vol/%s/tmp/%016" PRIx64 ".ppcini", device_name.c_str(), title_id))); + return IPC_SUCCESS; +} + +s32 WFSI::CancelTitleImport(bool continue_install) +{ + m_arc_unpacker.Reset(); + + if (!continue_install) + { + File::DeleteDirRecursively( + WFS::NativePath(StringFromFormat("/vol/%s/_install", m_device_name.c_str()))); + } + + DeleteTemporaryFiles(m_device_name, m_import_title_id); + + return IPC_SUCCESS; +} + +s32 WFSI::CancelPatchImport(bool continue_install) +{ + m_arc_unpacker.Reset(); + + if (!continue_install) + { + File::DeleteDirRecursively(WFS::NativePath( + 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()))); + + if (m_patch_type == PatchType::PATCH_TYPE_2) + { + // Move back _default.dol to default.dol. + 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")); + } + } + + DeleteTemporaryFiles(m_device_name, m_current_title_id); + + return IPC_SUCCESS; +} } // namespace Device } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/WFS/WFSI.h b/Source/Core/Core/IOS/WFS/WFSI.h index 5ed9ee21eb..d37ea76d09 100644 --- a/Source/Core/Core/IOS/WFS/WFSI.h +++ b/Source/Core/Core/IOS/WFS/WFSI.h @@ -44,6 +44,14 @@ public: IPCCommandResult IOCtl(const IOCtlRequest& request) override; private: + u32 GetTmd(u16 group_id, u32 title_id, u64 subtitle_id, u32 address, u32* size) const; + + void SetCurrentTitleIdAndGroupId(u64 tid, u16 gid); + void SetImportTitleIdAndGroupId(u64 tid, u16 gid); + + s32 CancelTitleImport(bool continue_install); + s32 CancelPatchImport(bool continue_install); + std::string m_device_name; mbedtls_aes_context m_aes_ctx; @@ -52,39 +60,69 @@ private: IOS::ES::TMDReader m_tmd; std::string m_base_extract_path; - u64 m_title_id; - std::string m_title_id_str; - u16 m_group_id; - std::string m_group_id_str; + + u64 m_current_title_id; + std::string m_current_title_id_str; + u16 m_current_group_id; + std::string m_current_group_id_str; + u64 m_import_title_id; + std::string m_import_title_id_str; + u16 m_import_group_id; + std::string m_import_group_id_str; + + // Set on IMPORT_TITLE_INIT when the next profile application should not delete + // temporary install files. + bool m_continue_install = false; + + // Set on IMPORT_TITLE_INIT to indicate that the install is a patch and not a + // standalone title. + enum PatchType + { + NOT_A_PATCH, + PATCH_TYPE_1, + PATCH_TYPE_2, + }; + PatchType m_patch_type = NOT_A_PATCH; ARCUnpacker m_arc_unpacker; enum { - WFSI_ENOENT = -12000, - }; - - enum - { - IOCTL_WFSI_PREPARE_DEVICE = 0x02, + IOCTL_WFSI_IMPORT_TITLE_INIT = 0x02, IOCTL_WFSI_PREPARE_CONTENT = 0x03, IOCTL_WFSI_IMPORT_CONTENT = 0x04, - IOCTL_WFSI_FINALIZE_CONTENT = 0x05, + IOCTL_WFSI_IMPORT_CONTENT_END = 0x05, + + IOCTL_WFSI_FINALIZE_TITLE_INSTALL = 0x06, IOCTL_WFSI_DELETE_TITLE = 0x17, - IOCTL_WFSI_IMPORT_TITLE = 0x2f, + + IOCTL_WFSI_GET_VERSION = 0x1b, + + IOCTL_WFSI_IMPORT_TITLE_CANCEL = 0x2f, IOCTL_WFSI_INIT = 0x81, IOCTL_WFSI_SET_DEVICE_NAME = 0x82, IOCTL_WFSI_PREPARE_PROFILE = 0x86, IOCTL_WFSI_IMPORT_PROFILE = 0x87, - IOCTL_WFSI_FINALIZE_PROFILE = 0x88, + IOCTL_WFSI_IMPORT_PROFILE_END = 0x88, IOCTL_WFSI_APPLY_TITLE_PROFILE = 0x89, + IOCTL_WFSI_GET_TMD = 0x8a, + IOCTL_WFSI_GET_TMD_ABSOLUTE = 0x8b, + + IOCTL_WFSI_SET_FST_BUFFER = 0x8e, + + IOCTL_WFSI_NOOP = 0x8f, + IOCTL_WFSI_LOAD_DOL = 0x90, + + IOCTL_WFSI_FINALIZE_PATCH_INSTALL = 0x91, + + IOCTL_WFSI_CHECK_HAS_SPACE = 0x95, }; }; } // namespace Device diff --git a/Source/Core/Core/IOS/WFS/WFSSRV.cpp b/Source/Core/Core/IOS/WFS/WFSSRV.cpp index 08315c48c6..4bea8b7444 100644 --- a/Source/Core/Core/IOS/WFS/WFSSRV.cpp +++ b/Source/Core/Core/IOS/WFS/WFSSRV.cpp @@ -4,6 +4,7 @@ #include "Core/IOS/WFS/WFSSRV.h" +#include #include #include #include @@ -100,6 +101,29 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) INFO_LOG(IOS_WFS, "IOCTL_WFS_FLUSH: doing nothing"); break; + case IOCTL_WFS_MKDIR: + { + std::string path = NormalizePath( + Memory::GetString(request.buffer_in + 34, Memory::Read_U16(request.buffer_in + 32))); + std::string native_path = WFS::NativePath(path); + + if (File::Exists(native_path)) + { + INFO_LOG(IOS_WFS, "IOCTL_WFS_MKDIR(%s): already exists", path.c_str()); + return_error_code = WFS_EEXIST; + } + else if (!File::CreateDir(native_path)) + { + INFO_LOG(IOS_WFS, "IOCTL_WFS_MKDIR(%s): no such file or directory", path.c_str()); + return_error_code = WFS_ENOENT; + } + else + { + INFO_LOG(IOS_WFS, "IOCTL_WFS_MKDIR(%s): directory created", path.c_str()); + } + break; + } + // TODO(wfs): Globbing is not really implemented, we just fake the one case // (listing /vol/*) which is required to get the installer to work. case IOCTL_WFS_GLOB_START: @@ -136,9 +160,48 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) Memory::CopyToEmu(request.buffer_out + 2, m_home_directory.data(), m_home_directory.size()); break; + case IOCTL_WFS_GET_ATTRIBUTES: + { + std::string path = NormalizePath( + Memory::GetString(request.buffer_in + 2, Memory::Read_U16(request.buffer_in))); + std::string native_path = WFS::NativePath(path); + Memory::Memset(0, request.buffer_out, request.buffer_out_size); + if (!File::Exists(native_path)) + { + INFO_LOG(IOS_WFS, "IOCTL_WFS_GET_ATTRIBUTES(%s): no such file or directory", path.c_str()); + return_error_code = WFS_ENOENT; + } + else if (File::IsDirectory(native_path)) + { + INFO_LOG(IOS_WFS, "IOCTL_WFS_GET_ATTRIBUTES(%s): directory", path.c_str()); + Memory::Write_U32(0x80000000, request.buffer_out + 4); + } + else + { + u32 size = static_cast(File::GetSize(native_path)); + INFO_LOG(IOS_WFS, "IOCTL_WFS_GET_ATTRIBUTES(%s): file with size %d", path.c_str(), size); + Memory::Write_U32(size, request.buffer_out); + } + break; + } + + case IOCTL_WFS_RENAME: + case IOCTL_WFS_RENAME_2: + { + const std::string source_path = + Memory::GetString(request.buffer_in + 2, Memory::Read_U16(request.buffer_in)); + const std::string dest_path = + Memory::GetString(request.buffer_in + 512 + 2, Memory::Read_U16(request.buffer_in + 512)); + return_error_code = Rename(source_path, dest_path); + break; + } + + case IOCTL_WFS_CREATE_OPEN: case IOCTL_WFS_OPEN: { - u32 mode = Memory::Read_U32(request.buffer_in); + const char* ioctl_name = + request.request == IOCTL_WFS_OPEN ? "IOCTL_WFS_OPEN" : "IOCTL_WFS_CREATE_OPEN"; + u32 mode = request.request == IOCTL_WFS_OPEN ? Memory::Read_U32(request.buffer_in) : 2; u16 path_len = Memory::Read_U16(request.buffer_in + 0x20); std::string path = Memory::GetString(request.buffer_in + 0x22, path_len); @@ -153,14 +216,21 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) if (!fd_obj->Open()) { - ERROR_LOG(IOS_WFS, "IOCTL_WFS_OPEN(%s, %d): error opening file", path.c_str(), mode); + ERROR_LOG(IOS_WFS, "%s(%s, %d): error opening file", ioctl_name, path.c_str(), mode); ReleaseFileDescriptor(fd); return_error_code = WFS_ENOENT; break; } - INFO_LOG(IOS_WFS, "IOCTL_WFS_OPEN(%s, %d) -> %d", path.c_str(), mode, fd); - Memory::Write_U16(fd, request.buffer_out + 0x14); + INFO_LOG(IOS_WFS, "%s(%s, %d) -> %d", ioctl_name, path.c_str(), mode, fd); + if (request.request == IOCTL_WFS_OPEN) + { + Memory::Write_U16(fd, request.buffer_out + 0x14); + } + else + { + Memory::Write_U16(fd, request.buffer_out); + } break; } @@ -194,6 +264,16 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) break; } + case IOCTL_WFS_CLOSE_2: + { + // TODO(wfs): Figure out the exact semantics difference from the other + // close. + u16 fd = Memory::Read_U16(request.buffer_in + 0x4); + INFO_LOG(IOS_WFS, "IOCTL_WFS_CLOSE_2(%d)", fd); + ReleaseFileDescriptor(fd); + break; + } + case IOCTL_WFS_READ: case IOCTL_WFS_READ_ABSOLUTE: { @@ -235,6 +315,45 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) break; } + case IOCTL_WFS_WRITE: + case IOCTL_WFS_WRITE_ABSOLUTE: + { + u32 addr = Memory::Read_U32(request.buffer_in); + u32 position = Memory::Read_U32(request.buffer_in + 4); // Only for absolute. + u16 fd = Memory::Read_U16(request.buffer_in + 0xC); + u32 size = Memory::Read_U32(request.buffer_in + 8); + + bool absolute = request.request == IOCTL_WFS_WRITE_ABSOLUTE; + + FileDescriptor* fd_obj = FindFileDescriptor(fd); + if (fd_obj == nullptr) + { + ERROR_LOG(IOS_WFS, "IOCTL_WFS_WRITE: invalid file descriptor %d", fd); + return_error_code = WFS_EBADFD; + break; + } + + u64 previous_position = fd_obj->file.Tell(); + if (absolute) + { + fd_obj->file.Seek(position, SEEK_SET); + } + fd_obj->file.WriteArray(Memory::GetPointer(addr), size); + // TODO(wfs): Handle write errors. + if (absolute) + { + fd_obj->file.Seek(previous_position, SEEK_SET); + } + else + { + fd_obj->position += size; + } + + INFO_LOG(IOS_WFS, "IOCTL_WFS_WRITE: written %d bytes from FD %d (%s)", size, fd, + fd_obj->path.c_str()); + break; + } + default: // TODO(wfs): Should be returning -3. However until we have everything // properly stubbed it's easier to simulate the methods succeeding. @@ -246,6 +365,26 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) return GetDefaultReply(return_error_code); } +s32 WFSSRV::Rename(std::string source, std::string dest) const +{ + source = NormalizePath(source); + dest = NormalizePath(dest); + + INFO_LOG(IOS_WFS, "IOCTL_WFS_RENAME: %s to %s", source.c_str(), dest.c_str()); + + const bool opened = std::any_of(m_fds.begin(), m_fds.end(), + [&](const auto& fd) { return fd.in_use && fd.path == source; }); + + if (opened) + return WFS_FILE_IS_OPENED; + + // TODO(wfs): Handle other rename failures + if (!File::Rename(WFS::NativePath(source), WFS::NativePath(dest))) + return WFS_ENOENT; + + return IPC_SUCCESS; +} + std::string WFSSRV::NormalizePath(const std::string& path) const { std::string expanded; diff --git a/Source/Core/Core/IOS/WFS/WFSSRV.h b/Source/Core/Core/IOS/WFS/WFSSRV.h index f509ae9ee1..a970ba1da0 100644 --- a/Source/Core/Core/IOS/WFS/WFSSRV.h +++ b/Source/Core/Core/IOS/WFS/WFSSRV.h @@ -22,6 +22,15 @@ namespace WFS std::string NativePath(const std::string& wfs_path); } +enum +{ + WFS_EINVAL = -10003, // Invalid argument. + WFS_EBADFD = -10026, // Invalid file descriptor. + WFS_EEXIST = -10027, // File already exists. + WFS_ENOENT = -10028, // No such file or directory. + WFS_FILE_IS_OPENED = -10032, // Cannot perform operation on an opened file. +}; + namespace Device { class WFSSRV : public Device @@ -31,6 +40,8 @@ public: IPCCommandResult IOCtl(const IOCtlRequest& request) override; + s32 Rename(std::string source, std::string dest) const; + private: // WFS device name, e.g. msc01/msc02. std::string m_device_name; @@ -59,7 +70,9 @@ private: IOCTL_WFS_GET_HOMEDIR = 0x12, IOCTL_WFS_GETCWD = 0x13, IOCTL_WFS_DELETE = 0x15, + IOCTL_WFS_RENAME = 0x16, IOCTL_WFS_GET_ATTRIBUTES = 0x17, + IOCTL_WFS_CREATE_OPEN = 0x19, IOCTL_WFS_OPEN = 0x1A, IOCTL_WFS_GET_SIZE = 0x1B, IOCTL_WFS_CLOSE = 0x1E, @@ -67,13 +80,10 @@ private: IOCTL_WFS_WRITE = 0x22, IOCTL_WFS_ATTACH_DETACH = 0x2d, IOCTL_WFS_ATTACH_DETACH_2 = 0x2e, + IOCTL_WFS_RENAME_2 = 0x41, + IOCTL_WFS_CLOSE_2 = 0x47, IOCTL_WFS_READ_ABSOLUTE = 0x48, - }; - - enum - { - WFS_EBADFD = -10026, // Invalid file descriptor. - WFS_ENOENT = -10028, // No such file or directory. + IOCTL_WFS_WRITE_ABSOLUTE = 0x49, }; struct FileDescriptor