@@ -52,9 +52,18 @@ class WFSI : public Device

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 @@ class WFSI : public Device
IOCTL_WFSI_FINALIZE_PROFILE = 0x88,

IOCTL_WFSI_APPLY_TITLE_PROFILE = 0x89,

IOCTL_WFSI_LOAD_DOL = 0x90,
};
};
} // namespace Device
@@ -4,6 +4,7 @@

#include "Core/IOS/WFS/WFSSRV.h"

#include <cinttypes>
#include <string>
#include <vector>

@@ -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,20 +110,40 @@ 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:
INFO_LOG(IOS, "IOCTL_WFS_GLOB_END(%u)", request.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<u16>(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<u32>(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;
}

u64 previous_position = fd_obj->file.Tell();
if (absolute)
{
fd_obj->file.Seek(position, SEEK_SET);
}
size_t read_bytes;
if (!fd_obj->file.ReadArray(Memory::GetPointer(addr), size, &read_bytes))
fd_obj->file.ReadArray(Memory::GetPointer(addr), size, &read_bytes);
// TODO(wfs): Handle read errors.
if (absolute)
{
return_error_code = -1; // TODO(wfs): proper error code.
break;
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<std::string> components = SplitString(expanded, '/');
std::vector<std::string> 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)
@@ -35,12 +35,22 @@ class WFSSRV : public Device
// 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 @@ class WFSSRV : public Device
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 @@ class WFSSRV : public Device
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<u32> m_hanging;
};
} // namespace Device
} // namespace HLE