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); }
IPCCommandResult NetIPTop::HandlePollRequest(const IOCtlRequest& request) { // 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) { s32 wii_fd = Memory::Read_U32(request.buffer_out + 0xc * i); ufds[i].fd = WiiSockMan::GetInstance().GetHostSocket(wii_fd); // 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, wii_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 GetDefaultReply(ret); }