diff --git a/Source/Core/Common/NandPaths.cpp b/Source/Core/Common/NandPaths.cpp index 1fcbab38be..ec43b38484 100644 --- a/Source/Core/Common/NandPaths.cpp +++ b/Source/Core/Common/NandPaths.cpp @@ -58,6 +58,35 @@ std::string GetTMDFileName(u64 _titleID, FromWhichRoot from) return GetTitleContentPath(_titleID, from) + "title.tmd"; } +bool IsTitlePath(const std::string& path, FromWhichRoot from, u64* title_id) +{ + std::string expected_prefix = RootUserPath(from) + "/title/"; + if (!StringBeginsWith(path, expected_prefix)) + { + return false; + } + + // Try to find a title ID in the remaining path. + std::string subdirectory = path.substr(expected_prefix.size()); + std::vector components = SplitString(subdirectory, '/'); + if (components.size() < 2) + { + return false; + } + + u32 title_id_high, title_id_low; + if (!AsciiToHex(components[0], title_id_high) || !AsciiToHex(components[1], title_id_low)) + { + return false; + } + + if (title_id != nullptr) + { + *title_id = (static_cast(title_id_high) << 32) | title_id_low; + } + return true; +} + std::string EscapeFileName(const std::string& filename) { // Prevent paths from containing special names like ., .., ..., ...., and so on diff --git a/Source/Core/Common/NandPaths.h b/Source/Core/Common/NandPaths.h index 0a7738664c..6ef53a9912 100644 --- a/Source/Core/Common/NandPaths.h +++ b/Source/Core/Common/NandPaths.h @@ -29,6 +29,9 @@ std::string GetTitleDataPath(u64 _titleID, FromWhichRoot from); std::string GetTitleContentPath(u64 _titleID, FromWhichRoot from); std::string GetTMDFileName(u64 _titleID, FromWhichRoot from); +// Returns whether a path is within an installed title's directory. +bool IsTitlePath(const std::string& path, FromWhichRoot from, u64* title_id = nullptr); + // Escapes characters that are invalid or have special meanings in the host file system std::string EscapeFileName(const std::string& filename); // Escapes characters that are invalid or have special meanings in the host file system diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 04dbcf5848..9fd579888b 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -186,6 +186,7 @@ set(SRCS IOS/USB/OH0/OH0.cpp IOS/USB/OH0/OH0Device.cpp IOS/USB/USB_HID/HIDv4.cpp + IOS/USB/USB_HID/HIDv5.cpp IOS/USB/USB_VEN/VEN.cpp IOS/USB/USBV0.cpp IOS/USB/USBV4.cpp diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index 8968495f03..5e275711de 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -221,6 +221,7 @@ + @@ -468,6 +469,7 @@ + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 0f5eff13f5..167fcec007 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -826,6 +826,9 @@ IOS\USB + + IOS\USB + IOS\USB @@ -1469,6 +1472,9 @@ IOS\USB + + IOS\USB + IOS\USB diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index edcb89dca8..e398d0bb5c 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -165,15 +165,24 @@ IPCCommandResult ES::GetTitleDirectory(const IOCtlVRequest& request) return GetDefaultReply(IPC_SUCCESS); } -IPCCommandResult ES::GetTitleID(const IOCtlVRequest& request) +ReturnCode ES::GetTitleId(u64* title_id) const +{ + if (!s_title_context.active) + return ES_EINVAL; + *title_id = s_title_context.tmd.GetTitleId(); + return IPC_SUCCESS; +} + +IPCCommandResult ES::GetTitleId(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(0, 1)) return GetDefaultReply(ES_EINVAL); - if (!s_title_context.active) - return GetDefaultReply(ES_EINVAL); + u64 title_id; + const ReturnCode ret = GetTitleId(&title_id); + if (ret != IPC_SUCCESS) + return GetDefaultReply(ret); - const u64 title_id = s_title_context.tmd.GetTitleId(); Memory::Write_U64(title_id, request.io_vectors[0].address); INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEID: %08x/%08x", static_cast(title_id >> 32), static_cast(title_id)); @@ -421,7 +430,7 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) case IOCTL_ES_GETTITLEDIR: return GetTitleDirectory(request); case IOCTL_ES_GETTITLEID: - return GetTitleID(request); + return GetTitleId(request); case IOCTL_ES_SETUID: return SetUID(context->uid, request); case IOCTL_ES_DIVERIFY: diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 6352eec917..b21c3335d1 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -127,6 +127,7 @@ public: ReturnCode DeleteContent(u64 title_id, u32 content_id) const; ReturnCode GetDeviceId(u32* device_id) const; + ReturnCode GetTitleId(u64* device_id) const; // Views ReturnCode GetV0TicketFromView(const u8* ticket_view, u8* ticket) const; @@ -243,7 +244,7 @@ private: // Misc IPCCommandResult SetUID(u32 uid, const IOCtlVRequest& request); IPCCommandResult GetTitleDirectory(const IOCtlVRequest& request); - IPCCommandResult GetTitleID(const IOCtlVRequest& request); + IPCCommandResult GetTitleId(const IOCtlVRequest& request); IPCCommandResult GetConsumption(const IOCtlVRequest& request); IPCCommandResult Launch(const IOCtlVRequest& request); IPCCommandResult LaunchBC(const IOCtlVRequest& request); diff --git a/Source/Core/Core/IOS/FS/FS.cpp b/Source/Core/Core/IOS/FS/FS.cpp index 3ebad9c46a..50e8a47b28 100644 --- a/Source/Core/Core/IOS/FS/FS.cpp +++ b/Source/Core/Core/IOS/FS/FS.cpp @@ -21,6 +21,8 @@ #include "Common/NandPaths.h" #include "Core/HW/Memmap.h" #include "Core/HW/SystemTimers.h" +#include "Core/IOS/ES/ES.h" +#include "Core/IOS/ES/Formats.h" #include "Core/IOS/FS/FileIO.h" namespace IOS @@ -326,6 +328,18 @@ IPCCommandResult FS::GetAttribute(const IOCtlRequest& request) u8 OtherPerm = 0x3; // read/write u8 Attributes = 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. + u64 title_id; + if (IsTitlePath(Filename, Common::FROM_SESSION_ROOT, &title_id)) + { + IOS::ES::TMDReader tmd = GetIOS()->GetES()->FindInstalledTMD(title_id); + if (tmd.IsValid()) + { + GroupID = tmd.GetGroupId(); + } + } + if (File::IsDirectory(Filename)) { INFO_LOG(IOS_FILEIO, "FS: GET_ATTR Directory %s - all permission flags are set", diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 06c8e4ecd1..3d372c7688 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -47,6 +47,7 @@ #include "Core/IOS/USB/OH0/OH0.h" #include "Core/IOS/USB/OH0/OH0Device.h" #include "Core/IOS/USB/USB_HID/HIDv4.h" +#include "Core/IOS/USB/USB_HID/HIDv5.h" #include "Core/IOS/USB/USB_KBD.h" #include "Core/IOS/USB/USB_VEN/VEN.h" #include "Core/IOS/WFS/WFSI.h" @@ -361,7 +362,10 @@ void Kernel::AddStaticDevices() AddDevice(std::make_unique(*this, "/dev/usb/kbd")); AddDevice(std::make_unique(*this, "/dev/sdio/slot0")); AddDevice(std::make_unique(*this, "/dev/sdio/slot1")); - AddDevice(std::make_unique(*this, "/dev/usb/hid")); + if (GetVersion() == 59) + AddDevice(std::make_unique(*this, "/dev/usb/hid")); + else + AddDevice(std::make_unique(*this, "/dev/usb/hid")); AddDevice(std::make_unique(*this, "/dev/usb/oh0")); AddDevice(std::make_unique(*this, "/dev/usb/oh1")); AddDevice(std::make_unique(*this, "/dev/usb/ven")); diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp new file mode 100644 index 0000000000..d51d648c66 --- /dev/null +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp @@ -0,0 +1,63 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/IOS/USB/USB_HID/HIDv5.h" + +#include + +#include "Core/HW/Memmap.h" +#include "Core/IOS/Device.h" +#include "Core/IOS/USB/Common.h" +#include "Core/IOS/USB/USBV5.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +USB_HIDv5::USB_HIDv5(Kernel& ios, const std::string& device_name) : USBHost(ios, device_name) +{ +} + +USB_HIDv5::~USB_HIDv5() +{ + StopThreads(); +} + +IPCCommandResult USB_HIDv5::IOCtl(const IOCtlRequest& request) +{ + request.Log(GetDeviceName(), LogTypes::IOS_USB); + switch (request.request) + { + case USB::IOCTL_USBV5_GETVERSION: + Memory::Write_U32(VERSION, request.buffer_out); + return GetDefaultReply(IPC_SUCCESS); + case USB::IOCTL_USBV5_SHUTDOWN: + if (m_hanging_request) + { + IOCtlRequest hanging_request{m_hanging_request}; + m_ios.EnqueueIPCReply(hanging_request, IPC_SUCCESS); + } + return GetDefaultReply(IPC_SUCCESS); + case USB::IOCTL_USBV5_GETDEVICECHANGE: + if (m_devicechange_replied) + { + m_hanging_request = request.address; + return GetNoReply(); + } + else + { + m_devicechange_replied = true; + return GetDefaultReply(IPC_SUCCESS); + } + default: + request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB); + return GetDefaultReply(IPC_SUCCESS); + } +} + +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h new file mode 100644 index 0000000000..069651ff6e --- /dev/null +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h @@ -0,0 +1,37 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" +#include "Core/IOS/IOS.h" +#include "Core/IOS/USB/Host.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +// Stub implementation that only gets DQX to boot. +class USB_HIDv5 : public USBHost +{ +public: + USB_HIDv5(Kernel& ios, const std::string& device_name); + ~USB_HIDv5() override; + + IPCCommandResult IOCtl(const IOCtlRequest& request) override; + +private: + static constexpr u32 VERSION = 0x50001; + + u32 m_hanging_request = 0; + bool m_devicechange_replied = false; +}; + +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/WFS/WFSI.cpp b/Source/Core/Core/IOS/WFS/WFSI.cpp index c35eeec0e9..9059269308 100644 --- a/Source/Core/Core/IOS/WFS/WFSI.cpp +++ b/Source/Core/Core/IOS/WFS/WFSI.cpp @@ -15,6 +15,7 @@ #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Core/HW/Memmap.h" +#include "Core/IOS/ES/ES.h" #include "Core/IOS/ES/Formats.h" #include "Core/IOS/WFS/WFSSRV.h" #include "DiscIO/NANDContentLoader.h" @@ -101,7 +102,7 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) constexpr u32 MAX_TMD_SIZE = 0x4000; if (tmd_size > MAX_TMD_SIZE) { - ERROR_LOG(IOS, "IOCTL_WFSI_INIT: TMD size too large (%d)", tmd_size); + ERROR_LOG(IOS, "IOCTL_WFSI_PREPARE_DEVICE: TMD size too large (%d)", tmd_size); return_error_code = IPC_EINVAL; break; } @@ -215,9 +216,23 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) break; case IOCTL_WFSI_INIT: - // Nothing to do. + { INFO_LOG(IOS, "IOCTL_WFSI_INIT"); + if (GetIOS()->GetES()->GetTitleId(&m_title_id) < 0) + { + ERROR_LOG(IOS, "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); break; + } case IOCTL_WFSI_SET_DEVICE_NAME: INFO_LOG(IOS, "IOCTL_WFSI_SET_DEVICE_NAME"); @@ -227,14 +242,55 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request) case IOCTL_WFSI_APPLY_TITLE_PROFILE: INFO_LOG(IOS, "IOCTL_WFSI_APPLY_TITLE_PROFILE"); - m_base_extract_path = StringFromFormat( - "/vol/%s/_install/%c%c%c%c/content", m_device_name.c_str(), - static_cast(m_tmd.GetTitleId() >> 24), static_cast(m_tmd.GetTitleId() >> 16), - static_cast(m_tmd.GetTitleId() >> 8), static_cast(m_tmd.GetTitleId())); + 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)); 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()); + + 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, "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, "IOCTL_WFSI_LOAD_DOL: no such file or directory: %s", path.c_str()); + return_error_code = WFSI_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; + } + default: // TODO(wfs): Should be returning an error. However until we have // everything properly stubbed it's easier to simulate the methods diff --git a/Source/Core/Core/IOS/WFS/WFSI.h b/Source/Core/Core/IOS/WFS/WFSI.h index a6494c4f2c..5ed9ee21eb 100644 --- a/Source/Core/Core/IOS/WFS/WFSI.h +++ b/Source/Core/Core/IOS/WFS/WFSI.h @@ -52,9 +52,18 @@ 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; ARCUnpacker m_arc_unpacker; + enum + { + WFSI_ENOENT = -12000, + }; + enum { IOCTL_WFSI_PREPARE_DEVICE = 0x02, @@ -74,6 +83,8 @@ private: IOCTL_WFSI_FINALIZE_PROFILE = 0x88, IOCTL_WFSI_APPLY_TITLE_PROFILE = 0x89, + + IOCTL_WFSI_LOAD_DOL = 0x90, }; }; } // namespace Device diff --git a/Source/Core/Core/IOS/WFS/WFSSRV.cpp b/Source/Core/Core/IOS/WFS/WFSSRV.cpp index 81639e69ad..b714a821a6 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 @@ -44,6 +45,27 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) INFO_LOG(IOS, "IOCTL_WFS_INIT"); break; + case IOCTL_WFS_UNKNOWN_8: + // TODO(wfs): Figure out what this actually does. + INFO_LOG(IOS, "IOCTL_WFS_UNKNOWN_8"); + Memory::Write_U8(7, request.buffer_out); + Memory::CopyToEmu(request.buffer_out + 1, "msc01\x00\x00\x00", 8); + break; + + case IOCTL_WFS_SHUTDOWN: + INFO_LOG(IOS, "IOCTL_WFS_SHUTDOWN"); + + // Close all hanging attach/detach ioctls with an appropriate error code. + for (auto address : m_hanging) + { + IOCtlRequest hanging_request{address}; + Memory::Write_U32(0x80000000, hanging_request.buffer_out); + Memory::Write_U32(0, hanging_request.buffer_out + 4); + Memory::Write_U32(0, hanging_request.buffer_out + 8); + m_ios.EnqueueIPCReply(hanging_request, 0); + } + break; + case IOCTL_WFS_DEVICE_INFO: INFO_LOG(IOS, "IOCTL_WFS_DEVICE_INFO"); Memory::Write_U64(16ull << 30, request.buffer_out); // 16GB storage. @@ -68,11 +90,16 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) case IOCTL_WFS_ATTACH_DETACH: INFO_LOG(IOS, "IOCTL_WFS_ATTACH_DETACH(%u)", request.request); - Memory::Write_U32(1, request.buffer_out); - Memory::Write_U32(0, request.buffer_out + 4); - Memory::Write_U32(0, request.buffer_out + 8); + + // Leave hanging, but we need to acknowledge the request at shutdown time. + m_hanging.push_back(request.address); return GetNoReply(); + case IOCTL_WFS_FLUSH: + // Nothing to do. + INFO_LOG(IOS, "IOCTL_WFS_FLUSH: doing nothing"); + 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: @@ -83,7 +110,7 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) case IOCTL_WFS_GLOB_NEXT: INFO_LOG(IOS, "IOCTL_WFS_GLOB_NEXT(%u)", request.request); - return_error_code = WFS_EEMPTY; + return_error_code = WFS_ENOENT; break; case IOCTL_WFS_GLOB_END: @@ -91,12 +118,32 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) Memory::Memset(request.buffer_out, 0, request.buffer_out_size); break; + case IOCTL_WFS_SET_HOMEDIR: + m_home_directory = + Memory::GetString(request.buffer_in + 2, Memory::Read_U16(request.buffer_in)); + INFO_LOG(IOS, "IOCTL_WFS_SET_HOMEDIR: %s", m_home_directory.c_str()); + break; + + case IOCTL_WFS_CHDIR: + m_current_directory = + Memory::GetString(request.buffer_in + 2, Memory::Read_U16(request.buffer_in)); + INFO_LOG(IOS, "IOCTL_WFS_CHDIR: %s", m_current_directory.c_str()); + break; + + case IOCTL_WFS_GET_HOMEDIR: + INFO_LOG(IOS, "IOCTL_WFS_GET_HOMEDIR: %s", m_home_directory.c_str()); + Memory::Write_U16(static_cast(m_home_directory.size()), request.buffer_out); + Memory::CopyToEmu(request.buffer_out + 2, m_home_directory.data(), m_home_directory.size()); + break; + case IOCTL_WFS_OPEN: { u32 mode = Memory::Read_U32(request.buffer_in); u16 path_len = Memory::Read_U16(request.buffer_in + 0x20); std::string path = Memory::GetString(request.buffer_in + 0x22, path_len); + path = NormalizePath(path); + u16 fd = GetNewFileDescriptor(); FileDescriptor* fd_obj = &m_fds[fd]; fd_obj->in_use = true; @@ -108,7 +155,7 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) { ERROR_LOG(IOS, "IOCTL_WFS_OPEN(%s, %d): error opening file", path.c_str(), mode); ReleaseFileDescriptor(fd); - return_error_code = -1; // TODO(wfs): proper error code. + return_error_code = WFS_ENOENT; break; } @@ -117,6 +164,28 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) break; } + case IOCTL_WFS_GET_SIZE: + { + u16 fd = Memory::Read_U16(request.buffer_in); + FileDescriptor* fd_obj = FindFileDescriptor(fd); + if (fd_obj == nullptr) + { + ERROR_LOG(IOS, "IOCTL_WFS_GET_SIZE: invalid file descriptor %d", fd); + return_error_code = WFS_EBADFD; + break; + } + + u64 size = fd_obj->file.GetSize(); + u32 truncated_size = static_cast(size); + INFO_LOG(IOS, "IOCTL_WFS_GET_SIZE(%d) -> %d", fd, truncated_size); + if (size != truncated_size) + { + ERROR_LOG(IOS, "IOCTL_WFS_GET_SIZE: file %d too large (%" PRIu64 ")", fd, size); + } + Memory::Write_U32(truncated_size, request.buffer_out); + break; + } + case IOCTL_WFS_CLOSE: { u16 fd = Memory::Read_U16(request.buffer_in + 0x4); @@ -126,26 +195,39 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) } case IOCTL_WFS_READ: + case IOCTL_WFS_READ_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_READ_ABSOLUTE; + FileDescriptor* fd_obj = FindFileDescriptor(fd); if (fd_obj == nullptr) { ERROR_LOG(IOS, "IOCTL_WFS_READ: invalid file descriptor %d", fd); - return_error_code = -1; // TODO(wfs): proper error code. + return_error_code = WFS_EBADFD; break; } - size_t read_bytes; - if (!fd_obj->file.ReadArray(Memory::GetPointer(addr), size, &read_bytes)) + u64 previous_position = fd_obj->file.Tell(); + if (absolute) { - return_error_code = -1; // TODO(wfs): proper error code. - break; + fd_obj->file.Seek(position, SEEK_SET); + } + size_t read_bytes; + fd_obj->file.ReadArray(Memory::GetPointer(addr), size, &read_bytes); + // TODO(wfs): Handle read errors. + if (absolute) + { + fd_obj->file.Seek(previous_position, SEEK_SET); + } + else + { + fd_obj->position += read_bytes; } - fd_obj->position += read_bytes; INFO_LOG(IOS, "IOCTL_WFS_READ: read %zd bytes from FD %d (%s)", read_bytes, fd, fd_obj->path.c_str()); @@ -164,6 +246,42 @@ IPCCommandResult WFSSRV::IOCtl(const IOCtlRequest& request) return GetDefaultReply(return_error_code); } +std::string WFSSRV::NormalizePath(const std::string& path) const +{ + std::string expanded; + if (!path.empty() && path[0] == '~') + { + expanded = m_home_directory + "/" + path.substr(1); + } + else if (path.empty() || path[0] != '/') + { + expanded = m_current_directory + "/" + path; + } + else + { + expanded = path; + } + + std::vector components = SplitString(expanded, '/'); + std::vector normalized_components; + for (const auto& component : components) + { + if (component.empty() || component == ".") + { + continue; + } + else if (component == ".." && !normalized_components.empty()) + { + normalized_components.pop_back(); + } + else + { + normalized_components.push_back(component); + } + } + return "/" + JoinStrings(normalized_components, "/"); +} + WFSSRV::FileDescriptor* WFSSRV::FindFileDescriptor(u16 fd) { if (fd >= m_fds.size() || !m_fds[fd].in_use) diff --git a/Source/Core/Core/IOS/WFS/WFSSRV.h b/Source/Core/Core/IOS/WFS/WFSSRV.h index 602d30777e..f509ae9ee1 100644 --- a/Source/Core/Core/IOS/WFS/WFSSRV.h +++ b/Source/Core/Core/IOS/WFS/WFSSRV.h @@ -35,12 +35,22 @@ private: // WFS device name, e.g. msc01/msc02. std::string m_device_name; + // Home / current directories. + std::string m_home_directory; + std::string m_current_directory; + + std::string NormalizePath(const std::string& path) const; + enum { IOCTL_WFS_INIT = 0x02, + IOCTL_WFS_SHUTDOWN = 0x03, IOCTL_WFS_DEVICE_INFO = 0x04, IOCTL_WFS_GET_DEVICE_NAME = 0x05, + IOCTL_WFS_UNMOUNT_VOLUME = 0x06, + IOCTL_WFS_UNKNOWN_8 = 0x08, IOCTL_WFS_FLUSH = 0x0a, + IOCTL_WFS_MKDIR = 0x0c, IOCTL_WFS_GLOB_START = 0x0d, IOCTL_WFS_GLOB_NEXT = 0x0e, IOCTL_WFS_GLOB_END = 0x0f, @@ -51,16 +61,19 @@ private: IOCTL_WFS_DELETE = 0x15, IOCTL_WFS_GET_ATTRIBUTES = 0x17, IOCTL_WFS_OPEN = 0x1A, + IOCTL_WFS_GET_SIZE = 0x1B, IOCTL_WFS_CLOSE = 0x1E, IOCTL_WFS_READ = 0x20, IOCTL_WFS_WRITE = 0x22, IOCTL_WFS_ATTACH_DETACH = 0x2d, IOCTL_WFS_ATTACH_DETACH_2 = 0x2e, + IOCTL_WFS_READ_ABSOLUTE = 0x48, }; enum { - WFS_EEMPTY = -10028, // Directory is empty of iteration completed. + WFS_EBADFD = -10026, // Invalid file descriptor. + WFS_ENOENT = -10028, // No such file or directory. }; struct FileDescriptor @@ -78,6 +91,10 @@ private: FileDescriptor* FindFileDescriptor(u16 fd); u16 GetNewFileDescriptor(); void ReleaseFileDescriptor(u16 fd); + + // List of addresses of IPC requests left hanging that need closing at + // shutdown time. + std::vector m_hanging; }; } // namespace Device } // namespace HLE