Example #1
0
IPCCommandResult STMImmediate::IOCtl(const IOCtlRequest& request)
{
  s32 return_value = IPC_SUCCESS;
  switch (request.request)
  {
  case IOCTL_STM_IDLE:
  case IOCTL_STM_SHUTDOWN:
    NOTICE_LOG(IOS_STM, "IOCTL_STM_IDLE or IOCTL_STM_SHUTDOWN received, shutting down");
    Core::QueueHostJob(&Core::Stop, false);
    break;

  case IOCTL_STM_RELEASE_EH:
    if (!s_event_hook_request)
    {
      return_value = IPC_ENOENT;
      break;
    }
    Memory::Write_U32(0, s_event_hook_request->buffer_out);
    EnqueueReply(*s_event_hook_request, IPC_SUCCESS);
    s_event_hook_request.reset();
    break;

  case IOCTL_STM_HOTRESET:
    INFO_LOG(IOS_STM, "%s - IOCtl:", GetDeviceName().c_str());
    INFO_LOG(IOS_STM, "    IOCTL_STM_HOTRESET");
    break;

  case IOCTL_STM_VIDIMMING:  // (Input: 20 bytes, Output: 20 bytes)
    INFO_LOG(IOS_STM, "%s - IOCtl:", GetDeviceName().c_str());
    INFO_LOG(IOS_STM, "    IOCTL_STM_VIDIMMING");
    // Memory::Write_U32(1, buffer_out);
    // return_value = 1;
    break;

  case IOCTL_STM_LEDMODE:  // (Input: 20 bytes, Output: 20 bytes)
    INFO_LOG(IOS_STM, "%s - IOCtl:", GetDeviceName().c_str());
    INFO_LOG(IOS_STM, "    IOCTL_STM_LEDMODE");
    break;

  default:
    request.DumpUnknown(GetDeviceName(), LogTypes::IOS_STM);
  }

  return GetDefaultReply(return_value);
}
Example #2
0
IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request)
{
  s32 return_error_code = IPC_SUCCESS;

  switch (request.request)
  {
  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);

    m_patch_type = static_cast<PatchType>(Memory::Read_U32(request.buffer_in + 32));
    m_continue_install = Memory::Read_U32(request.buffer_in + 36);

    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)
    {
      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;
    }
    std::vector<u8> tmd_bytes;
    tmd_bytes.resize(tmd_size);
    Memory::CopyFromEmu(tmd_bytes.data(), tmd_addr, tmd_size);
    m_tmd.SetBytes(std::move(tmd_bytes));

    IOS::ES::TicketReader ticket = m_ios.GetES()->FindSignedTicket(m_tmd.GetTitleId());
    if (!ticket.IsValid())
    {
      return_error_code = -11028;
      break;
    }

    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;
  }

  case IOCTL_WFSI_PREPARE_PROFILE:
    m_base_extract_path = StringFromFormat("/vol/%s/tmp/", m_device_name.c_str());
    // Fall through intended.

  case IOCTL_WFSI_PREPARE_CONTENT:
  {
    const char* ioctl_name = request.request == IOCTL_WFSI_PREPARE_PROFILE ?
                                 "IOCTL_WFSI_PREPARE_PROFILE" :
                                 "IOCTL_WFSI_PREPARE_CONTENT";

    // Initializes the IV from the index of the content in the TMD contents.
    u32 content_id = Memory::Read_U32(request.buffer_in + 8);
    IOS::ES::Content content_info;
    if (!m_tmd.FindContentById(content_id, &content_info))
    {
      WARN_LOG(IOS_WFS, "%s: Content id %08x not found", ioctl_name, content_id);
      return_error_code = -10003;
      break;
    }

    memset(m_aes_iv, 0, sizeof(m_aes_iv));
    m_aes_iv[0] = content_info.index >> 8;
    m_aes_iv[1] = content_info.index & 0xFF;
    INFO_LOG(IOS_WFS, "%s: Content id %08x found at index %d", ioctl_name, content_id,
             content_info.index);

    m_arc_unpacker.Reset();
    break;
  }

  case IOCTL_WFSI_IMPORT_PROFILE:
  case IOCTL_WFSI_IMPORT_CONTENT:
  {
    const char* ioctl_name = request.request == IOCTL_WFSI_IMPORT_PROFILE ?
                                 "IOCTL_WFSI_IMPORT_PROFILE" :
                                 "IOCTL_WFSI_IMPORT_CONTENT";

    u32 content_id = Memory::Read_U32(request.buffer_in + 0xC);
    u32 input_ptr = Memory::Read_U32(request.buffer_in + 0x10);
    u32 input_size = Memory::Read_U32(request.buffer_in + 0x14);
    INFO_LOG(IOS_WFS, "%s: %08x bytes of data at %08x from content id %d", ioctl_name, input_size,
             input_ptr, content_id);

    std::vector<u8> decrypted(input_size);
    mbedtls_aes_crypt_cbc(&m_aes_ctx, MBEDTLS_AES_DECRYPT, input_size, m_aes_iv,
                          Memory::GetPointer(input_ptr), decrypted.data());

    m_arc_unpacker.AddBytes(decrypted);
    break;
  }

  case IOCTL_WFSI_IMPORT_CONTENT_END:
  case IOCTL_WFSI_IMPORT_PROFILE_END:
  {
    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) {
      INFO_LOG(IOS_WFS, "Extract: %s (%zd bytes)", filename.c_str(), bytes.size());

      std::string path = WFS::NativePath(m_base_extract_path + "/" + filename);
      File::CreateFullPath(path);
      File::IOFile f(path, "wb");
      if (!f)
      {
        ERROR_LOG(IOS_WFS, "Could not extract %s to %s", filename.c_str(), path.c_str());
        return;
      }
      f.WriteBytes(bytes.data(), bytes.size());
    };
    m_arc_unpacker.Extract(callback);

    // Technically not needed, but let's not keep large buffers in RAM for no
    // reason if we can avoid it.
    m_arc_unpacker.Reset();
    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
    // Bytes 1c-1e: title id?
    WARN_LOG(IOS_WFS, "IOCTL_WFSI_DELETE_TITLE: unimplemented");
    break;

  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");
    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;
    }

    IOS::ES::TMDReader tmd = GetIOS()->GetES()->FindInstalledTMD(tid);
    SetCurrentTitleIdAndGroupId(tmd.GetTitleId(), tmd.GetGroupId());
    break;
  }

  case IOCTL_WFSI_SET_DEVICE_NAME:
    INFO_LOG(IOS_WFS, "IOCTL_WFSI_SET_DEVICE_NAME");
    m_device_name = Memory::GetString(request.buffer_in);
    break;

  case IOCTL_WFSI_APPLY_TITLE_PROFILE:
  {
    INFO_LOG(IOS_WFS, "IOCTL_WFSI_APPLY_TITLE_PROFILE");

    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_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);
    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_WFS, "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_WFS, "IOCTL_WFSI_LOAD_DOL: no such file or directory: %s", path.c_str());
      return_error_code = WFS_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;
  }

  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
    // succeeding.
    request.DumpUnknown(GetDeviceName(), LogTypes::IOS, LogTypes::LWARNING);
    Memory::Memset(request.buffer_out, 0, request.buffer_out_size);
    break;
  }

  return GetDefaultReply(return_error_code);
}
Example #3
0
IPCCommandResult NetIPTop::IOCtl(const IOCtlRequest& request)
{
  if (Core::g_want_determinism)
  {
    return GetDefaultReply(IPC_EACCES);
  }

  s32 return_value = 0;
  switch (request.request)
  {
  case IOCTL_SO_STARTUP:
  {
    request.Log(GetDeviceName(), LogTypes::IOS_WC24);
    break;
  }
  case IOCTL_SO_SOCKET:
  {
    u32 af = Memory::Read_U32(request.buffer_in);
    u32 type = Memory::Read_U32(request.buffer_in + 4);
    u32 prot = Memory::Read_U32(request.buffer_in + 8);

    WiiSockMan& sm = WiiSockMan::GetInstance();
    return_value = sm.NewSocket(af, type, prot);
    INFO_LOG(IOS_NET, "IOCTL_SO_SOCKET "
                      "Socket: %08x (%d,%d,%d), BufferIn: (%08x, %i), BufferOut: (%08x, %i)",
             return_value, af, type, prot, request.buffer_in, request.buffer_in_size,
             request.buffer_out, request.buffer_out_size);
    break;
  }
  case IOCTL_SO_ICMPSOCKET:
  {
    u32 pf = Memory::Read_U32(request.buffer_in);

    WiiSockMan& sm = WiiSockMan::GetInstance();
    return_value = sm.NewSocket(pf, SOCK_RAW, IPPROTO_ICMP);
    INFO_LOG(IOS_NET, "IOCTL_SO_ICMPSOCKET(%x) %d", pf, return_value);
    break;
  }
  case IOCTL_SO_CLOSE:
  case IOCTL_SO_ICMPCLOSE:
  {
    u32 fd = Memory::Read_U32(request.buffer_in);
    WiiSockMan& sm = WiiSockMan::GetInstance();
    return_value = sm.DeleteSocket(fd);
    INFO_LOG(IOS_NET, "%s(%x) %x",
             request.request == IOCTL_SO_ICMPCLOSE ? "IOCTL_SO_ICMPCLOSE" : "IOCTL_SO_CLOSE", fd,
             return_value);
    break;
  }
  case IOCTL_SO_ACCEPT:
  case IOCTL_SO_BIND:
  case IOCTL_SO_CONNECT:
  case IOCTL_SO_FCNTL:
  {
    u32 fd = Memory::Read_U32(request.buffer_in);
    WiiSockMan& sm = WiiSockMan::GetInstance();
    sm.DoSock(fd, request, static_cast<NET_IOCTL>(request.request));
    return GetNoReply();
  }
  /////////////////////////////////////////////////////////////
  //                  TODO: Tidy all below                   //
  /////////////////////////////////////////////////////////////
  case IOCTL_SO_SHUTDOWN:
  {
    request.Log(GetDeviceName(), LogTypes::IOS_WC24);

    u32 fd = Memory::Read_U32(request.buffer_in);
    u32 how = Memory::Read_U32(request.buffer_in + 4);
    int ret = shutdown(fd, how);
    return_value = WiiSockMan::GetNetErrorCode(ret, "SO_SHUTDOWN", false);
    break;
  }
  case IOCTL_SO_LISTEN:
  {
    u32 fd = Memory::Read_U32(request.buffer_in);
    u32 BACKLOG = Memory::Read_U32(request.buffer_in + 0x04);
    u32 ret = listen(fd, BACKLOG);
    return_value = WiiSockMan::GetNetErrorCode(ret, "SO_LISTEN", false);
    request.Log(GetDeviceName(), LogTypes::IOS_WC24);
    break;
  }
  case IOCTL_SO_GETSOCKOPT:
  {
    u32 fd = Memory::Read_U32(request.buffer_out);
    u32 level = Memory::Read_U32(request.buffer_out + 4);
    u32 optname = Memory::Read_U32(request.buffer_out + 8);

    request.Log(GetDeviceName(), LogTypes::IOS_WC24);

    // Do the level/optname translation
    int nat_level = -1, nat_optname = -1;

    for (auto& map : opt_level_mapping)
      if (level == map[1])
        nat_level = map[0];

    for (auto& map : opt_name_mapping)
      if (optname == map[1])
        nat_optname = map[0];

    u8 optval[20];
    u32 optlen = 4;

    int ret = getsockopt(fd, nat_level, nat_optname, (char*)&optval, (socklen_t*)&optlen);
    return_value = WiiSockMan::GetNetErrorCode(ret, "SO_GETSOCKOPT", false);

    Memory::Write_U32(optlen, request.buffer_out + 0xC);
    Memory::CopyToEmu(request.buffer_out + 0x10, optval, optlen);

    if (optname == SO_ERROR)
    {
      s32 last_error = WiiSockMan::GetInstance().GetLastNetError();

      Memory::Write_U32(sizeof(s32), request.buffer_out + 0xC);
      Memory::Write_U32(last_error, request.buffer_out + 0x10);
    }
    break;
  }

  case IOCTL_SO_SETSOCKOPT:
  {
    u32 fd = Memory::Read_U32(request.buffer_in);
    u32 level = Memory::Read_U32(request.buffer_in + 4);
    u32 optname = Memory::Read_U32(request.buffer_in + 8);
    u32 optlen = Memory::Read_U32(request.buffer_in + 0xc);
    u8 optval[20];
    optlen = std::min(optlen, (u32)sizeof(optval));
    Memory::CopyFromEmu(optval, request.buffer_in + 0x10, optlen);

    INFO_LOG(IOS_NET, "IOCTL_SO_SETSOCKOPT(%08x, %08x, %08x, %08x) "
                      "BufferIn: (%08x, %i), BufferOut: (%08x, %i)"
                      "%02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx "
                      "%02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx",
             fd, level, optname, optlen, request.buffer_in, request.buffer_in_size,
             request.buffer_out, request.buffer_out_size, optval[0], optval[1], optval[2],
             optval[3], optval[4], optval[5], optval[6], optval[7], optval[8], optval[9],
             optval[10], optval[11], optval[12], optval[13], optval[14], optval[15], optval[16],
             optval[17], optval[18], optval[19]);

    // TODO: bug booto about this, 0x2005 most likely timeout related, default value on Wii is ,
    // 0x2001 is most likely tcpnodelay
    if (level == 6 && (optname == 0x2005 || optname == 0x2001))
    {
      return_value = 0;
      break;
    }

    // Do the level/optname translation
    int nat_level = -1, nat_optname = -1;

    for (auto& map : opt_level_mapping)
      if (level == map[1])
        nat_level = map[0];

    for (auto& map : opt_name_mapping)
      if (optname == map[1])
        nat_optname = map[0];

    if (nat_level == -1 || nat_optname == -1)
    {
      INFO_LOG(IOS_NET, "SO_SETSOCKOPT: unknown level %d or optname %d", level, optname);

      // Default to the given level/optname. They match on Windows...
      nat_level = level;
      nat_optname = optname;
    }

    int ret = setsockopt(fd, nat_level, nat_optname, (char*)optval, optlen);
    return_value = WiiSockMan::GetNetErrorCode(ret, "SO_SETSOCKOPT", false);
    break;
  }
  case IOCTL_SO_GETSOCKNAME:
  {
    u32 fd = Memory::Read_U32(request.buffer_in);

    request.Log(GetDeviceName(), LogTypes::IOS_WC24);

    sockaddr sa;
    socklen_t sa_len;
    sa_len = sizeof(sa);
    int ret = getsockname(fd, &sa, &sa_len);

    if (request.buffer_out_size < 2 + sizeof(sa.sa_data))
      WARN_LOG(IOS_NET, "IOCTL_SO_GETSOCKNAME output buffer is too small. Truncating");

    if (request.buffer_out_size > 0)
      Memory::Write_U8(request.buffer_out_size, request.buffer_out);
    if (request.buffer_out_size > 1)
      Memory::Write_U8(sa.sa_family & 0xFF, request.buffer_out + 1);
    if (request.buffer_out_size > 2)
      Memory::CopyToEmu(request.buffer_out + 2, &sa.sa_data,
                        std::min<size_t>(sizeof(sa.sa_data), request.buffer_out_size - 2));
    return_value = ret;
    break;
  }
  case IOCTL_SO_GETPEERNAME:
  {
    u32 fd = Memory::Read_U32(request.buffer_in);

    sockaddr sa;
    socklen_t sa_len;
    sa_len = sizeof(sa);

    int ret = getpeername(fd, &sa, &sa_len);

    if (request.buffer_out_size < 2 + sizeof(sa.sa_data))
      WARN_LOG(IOS_NET, "IOCTL_SO_GETPEERNAME output buffer is too small. Truncating");

    if (request.buffer_out_size > 0)
      Memory::Write_U8(request.buffer_out_size, request.buffer_out);
    if (request.buffer_out_size > 1)
      Memory::Write_U8(AF_INET, request.buffer_out + 1);
    if (request.buffer_out_size > 2)
      Memory::CopyToEmu(request.buffer_out + 2, &sa.sa_data,
                        std::min<size_t>(sizeof(sa.sa_data), request.buffer_out_size - 2));

    INFO_LOG(IOS_NET, "IOCTL_SO_GETPEERNAME(%x)", fd);

    return_value = ret;
    break;
  }

  case IOCTL_SO_GETHOSTID:
  {
    request.Log(GetDeviceName(), LogTypes::IOS_WC24);

#ifdef _WIN32
    DWORD forwardTableSize, ipTableSize, result;
    DWORD ifIndex = -1;
    std::unique_ptr<MIB_IPFORWARDTABLE> forwardTable;
    std::unique_ptr<MIB_IPADDRTABLE> ipTable;

    forwardTableSize = 0;
    if (GetIpForwardTable(nullptr, &forwardTableSize, FALSE) == ERROR_INSUFFICIENT_BUFFER)
    {
      forwardTable =
          std::unique_ptr<MIB_IPFORWARDTABLE>((PMIB_IPFORWARDTABLE) operator new(forwardTableSize));
    }

    ipTableSize = 0;
    if (GetIpAddrTable(nullptr, &ipTableSize, FALSE) == ERROR_INSUFFICIENT_BUFFER)
    {
      ipTable = std::unique_ptr<MIB_IPADDRTABLE>((PMIB_IPADDRTABLE) operator new(ipTableSize));
    }

    // find the interface IP used for the default route and use that
    result = GetIpForwardTable(forwardTable.get(), &forwardTableSize, FALSE);
    while (result == NO_ERROR ||
           result == ERROR_MORE_DATA)  // can return ERROR_MORE_DATA on XP even after the first call
    {
      for (DWORD i = 0; i < forwardTable->dwNumEntries; ++i)
      {
        if (forwardTable->table[i].dwForwardDest == 0)
        {
          ifIndex = forwardTable->table[i].dwForwardIfIndex;
          break;
        }
      }

      if (result == NO_ERROR || ifIndex != -1)
        break;

      result = GetIpForwardTable(forwardTable.get(), &forwardTableSize, FALSE);
    }

    if (ifIndex != -1 && GetIpAddrTable(ipTable.get(), &ipTableSize, FALSE) == NO_ERROR)
    {
      for (DWORD i = 0; i < ipTable->dwNumEntries; ++i)
      {
        if (ipTable->table[i].dwIndex == ifIndex)
        {
          return_value = Common::swap32(ipTable->table[i].dwAddr);
          break;
        }
      }
    }
#endif

    // default placeholder, in case of failure
    if (return_value == 0)
      return_value = 192 << 24 | 168 << 16 | 1 << 8 | 150;
    break;
  }

  case IOCTL_SO_INETATON:
  {
    std::string hostname = Memory::GetString(request.buffer_in);
    struct hostent* remoteHost = gethostbyname(hostname.c_str());

    if (remoteHost == nullptr || remoteHost->h_addr_list == nullptr ||
        remoteHost->h_addr_list[0] == nullptr)
    {
      INFO_LOG(IOS_NET, "IOCTL_SO_INETATON = -1 "
                        "%s, BufferIn: (%08x, %i), BufferOut: (%08x, %i), IP Found: None",
               hostname.c_str(), request.buffer_in, request.buffer_in_size, request.buffer_out,
               request.buffer_out_size);
      return_value = 0;
    }
    else
    {
      Memory::Write_U32(Common::swap32(*(u32*)remoteHost->h_addr_list[0]), request.buffer_out);
      INFO_LOG(IOS_NET, "IOCTL_SO_INETATON = 0 "
                        "%s, BufferIn: (%08x, %i), BufferOut: (%08x, %i), IP Found: %08X",
               hostname.c_str(), request.buffer_in, request.buffer_in_size, request.buffer_out,
               request.buffer_out_size, Common::swap32(*(u32*)remoteHost->h_addr_list[0]));
      return_value = 1;
    }
    break;
  }

  case IOCTL_SO_INETPTON:
  {
    std::string address = Memory::GetString(request.buffer_in);
    INFO_LOG(IOS_NET, "IOCTL_SO_INETPTON "
                      "(Translating: %s)",
             address.c_str());
    return_value = inet_pton(address.c_str(), Memory::GetPointer(request.buffer_out + 4));
    break;
  }

  case IOCTL_SO_INETNTOP:
  {
    // u32 af = Memory::Read_U32(BufferIn);
    // u32 validAddress = Memory::Read_U32(request.buffer_in + 4);
    // u32 src = Memory::Read_U32(request.buffer_in + 8);
    char ip_s[16];
    sprintf(ip_s, "%i.%i.%i.%i", Memory::Read_U8(request.buffer_in + 8),
            Memory::Read_U8(request.buffer_in + 8 + 1), Memory::Read_U8(request.buffer_in + 8 + 2),
            Memory::Read_U8(request.buffer_in + 8 + 3));
    INFO_LOG(IOS_NET, "IOCTL_SO_INETNTOP %s", ip_s);
    Memory::CopyToEmu(request.buffer_out, (u8*)ip_s, strlen(ip_s));
    break;
  }

  case IOCTL_SO_POLL:
  {
    // Map Wii/native poll events types
    struct
    {
      int native;
      int wii;
    } mapping[] = {
        {POLLRDNORM, 0x0001}, {POLLRDBAND, 0x0002}, {POLLPRI, 0x0004}, {POLLWRNORM, 0x0008},
        {POLLWRBAND, 0x0010}, {POLLERR, 0x0020},    {POLLHUP, 0x0040}, {POLLNVAL, 0x0080},
    };

    u32 unknown = Memory::Read_U32(request.buffer_in);
    u32 timeout = Memory::Read_U32(request.buffer_in + 4);

    int nfds = request.buffer_out_size / 0xc;
    if (nfds == 0)
      ERROR_LOG(IOS_NET, "Hidden POLL");

    std::vector<pollfd_t> ufds(nfds);

    for (int i = 0; i < nfds; ++i)
    {
      ufds[i].fd = Memory::Read_U32(request.buffer_out + 0xc * i);           // fd
      int events = Memory::Read_U32(request.buffer_out + 0xc * i + 4);       // events
      ufds[i].revents = Memory::Read_U32(request.buffer_out + 0xc * i + 8);  // revents

      // Translate Wii to native events
      int unhandled_events = events;
      ufds[i].events = 0;
      for (auto& map : mapping)
      {
        if (events & map.wii)
          ufds[i].events |= map.native;
        unhandled_events &= ~map.wii;
      }
      DEBUG_LOG(IOS_NET, "IOCTL_SO_POLL(%d) "
                         "Sock: %08x, Unknown: %08x, Events: %08x, "
                         "NativeEvents: %08x",
                i, ufds[i].fd, unknown, events, ufds[i].events);

      // Do not pass return-only events to the native poll
      ufds[i].events &= ~(POLLERR | POLLHUP | POLLNVAL | UNSUPPORTED_WSAPOLL);

      if (unhandled_events)
        ERROR_LOG(IOS_NET, "SO_POLL: unhandled Wii event types: %04x", unhandled_events);
    }

    int ret = poll(ufds.data(), nfds, timeout);
    ret = WiiSockMan::GetNetErrorCode(ret, "SO_POLL", false);

    for (int i = 0; i < nfds; ++i)
    {
      // Translate native to Wii events
      int revents = 0;
      for (auto& map : mapping)
      {
        if (ufds[i].revents & map.native)
          revents |= map.wii;
      }

      // No need to change fd or events as they are input only.
      // Memory::Write_U32(ufds[i].fd, request.buffer_out + 0xc*i); //fd
      // Memory::Write_U32(events, request.buffer_out + 0xc*i + 4); //events
      Memory::Write_U32(revents, request.buffer_out + 0xc * i + 8);  // revents

      DEBUG_LOG(IOS_NET, "IOCTL_SO_POLL socket %d wevents %08X events %08X revents %08X", i,
                revents, ufds[i].events, ufds[i].revents);
    }

    return_value = ret;
    break;
  }

  case IOCTL_SO_GETHOSTBYNAME:
  {
    if (request.buffer_out_size != 0x460)
    {
      ERROR_LOG(IOS_NET, "Bad buffer size for IOCTL_SO_GETHOSTBYNAME");
      return_value = -1;
      break;
    }

    std::string hostname = Memory::GetString(request.buffer_in);
    hostent* remoteHost = gethostbyname(hostname.c_str());

    INFO_LOG(IOS_NET, "IOCTL_SO_GETHOSTBYNAME "
                      "Address: %s, BufferIn: (%08x, %i), BufferOut: (%08x, %i)",
             hostname.c_str(), request.buffer_in, request.buffer_in_size, request.buffer_out,
             request.buffer_out_size);

    if (remoteHost)
    {
      for (int i = 0; remoteHost->h_aliases[i]; ++i)
      {
        DEBUG_LOG(IOS_NET, "alias%i:%s", i, remoteHost->h_aliases[i]);
      }

      for (int i = 0; remoteHost->h_addr_list[i]; ++i)
      {
        u32 ip = Common::swap32(*(u32*)(remoteHost->h_addr_list[i]));
        std::string ip_s = StringFromFormat("%i.%i.%i.%i", ip >> 24, (ip >> 16) & 0xff,
                                            (ip >> 8) & 0xff, ip & 0xff);
        DEBUG_LOG(IOS_NET, "addr%i:%s", i, ip_s.c_str());
      }

      // Host name; located immediately after struct
      static const u32 GETHOSTBYNAME_STRUCT_SIZE = 0x10;
      static const u32 GETHOSTBYNAME_IP_LIST_OFFSET = 0x110;
      // Limit host name length to avoid buffer overflow.
      u32 name_length = (u32)strlen(remoteHost->h_name) + 1;
      if (name_length > (GETHOSTBYNAME_IP_LIST_OFFSET - GETHOSTBYNAME_STRUCT_SIZE))
      {
        ERROR_LOG(IOS_NET, "Hostname too long in IOCTL_SO_GETHOSTBYNAME");
        return_value = -1;
        break;
      }
      Memory::CopyToEmu(request.buffer_out + GETHOSTBYNAME_STRUCT_SIZE, remoteHost->h_name,
                        name_length);
      Memory::Write_U32(request.buffer_out + GETHOSTBYNAME_STRUCT_SIZE, request.buffer_out);

      // IP address list; located at offset 0x110.
      u32 num_ip_addr = 0;
      while (remoteHost->h_addr_list[num_ip_addr])
        num_ip_addr++;
      // Limit number of IP addresses to avoid buffer overflow.
      // (0x460 - 0x340) / sizeof(pointer) == 72
      static const u32 GETHOSTBYNAME_MAX_ADDRESSES = 71;
      num_ip_addr = std::min(num_ip_addr, GETHOSTBYNAME_MAX_ADDRESSES);
      for (u32 i = 0; i < num_ip_addr; ++i)
      {
        u32 addr = request.buffer_out + GETHOSTBYNAME_IP_LIST_OFFSET + i * 4;
        Memory::Write_U32_Swap(*(u32*)(remoteHost->h_addr_list[i]), addr);
      }

      // List of pointers to IP addresses; located at offset 0x340.
      // This must be exact: PPC code to convert the struct hardcodes
      // this offset.
      static const u32 GETHOSTBYNAME_IP_PTR_LIST_OFFSET = 0x340;
      Memory::Write_U32(request.buffer_out + GETHOSTBYNAME_IP_PTR_LIST_OFFSET,
                        request.buffer_out + 12);
      for (u32 i = 0; i < num_ip_addr; ++i)
      {
        u32 addr = request.buffer_out + GETHOSTBYNAME_IP_PTR_LIST_OFFSET + i * 4;
        Memory::Write_U32(request.buffer_out + GETHOSTBYNAME_IP_LIST_OFFSET + i * 4, addr);
      }
      Memory::Write_U32(0, request.buffer_out + GETHOSTBYNAME_IP_PTR_LIST_OFFSET + num_ip_addr * 4);

      // Aliases - empty. (Hardware doesn't return anything.)
      Memory::Write_U32(request.buffer_out + GETHOSTBYNAME_IP_PTR_LIST_OFFSET + num_ip_addr * 4,
                        request.buffer_out + 4);

      // Returned struct must be ipv4.
      _assert_msg_(IOS_NET,
                   remoteHost->h_addrtype == AF_INET && remoteHost->h_length == sizeof(u32),
                   "returned host info is not IPv4");
      Memory::Write_U16(AF_INET, request.buffer_out + 8);
      Memory::Write_U16(sizeof(u32), request.buffer_out + 10);

      return_value = 0;
    }
    else
    {
      return_value = -1;
    }

    break;
  }

  case IOCTL_SO_ICMPCANCEL:
    ERROR_LOG(IOS_NET, "IOCTL_SO_ICMPCANCEL");

  default:
    request.DumpUnknown(GetDeviceName(), LogTypes::IOS_NET);
  }