Merge pull request #5941 from delroth/wfs

WFS updates
This commit is contained in:
Pierre Bourdon 2017-08-23 11:14:01 +02:00 committed by GitHub
commit 187e65f495
6 changed files with 519 additions and 54 deletions

View File

@ -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);

View File

@ -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);

View File

@ -4,6 +4,7 @@
#include "Core/IOS/WFS/WFSI.h"
#include <cinttypes>
#include <mbedtls/aes.h>
#include <stack>
#include <string>
@ -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<char>(tid >> 24), static_cast<char>(tid >> 16),
static_cast<char>(tid >> 8), static_cast<char>(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<PatchType>(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<u8>& 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<char>(m_title_id >> 24), static_cast<char>(m_title_id >> 16),
static_cast<char>(m_title_id >> 8), static_cast<char>(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

View File

@ -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

View File

@ -4,6 +4,7 @@
#include "Core/IOS/WFS/WFSSRV.h"
#include <algorithm>
#include <cinttypes>
#include <string>
#include <vector>
@ -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<u32>(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;

View File

@ -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