From 97083ebba44f578cb218714f47d61c36b739252c Mon Sep 17 00:00:00 2001 From: Raul Tambre Date: Sat, 9 Apr 2016 12:45:45 +0300 Subject: [PATCH 1/6] Implement cellFsAllocateFileAreaWithoutZeroFill And a couple minor null pointer checks in sys_fs. --- rpcs3/Emu/Cell/Modules/cellFs.cpp | 5 +++-- rpcs3/Emu/Cell/lv2/sys_fs.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellFs.cpp b/rpcs3/Emu/Cell/Modules/cellFs.cpp index 1ac9a05dfe..c0ca6a1ed8 100644 --- a/rpcs3/Emu/Cell/Modules/cellFs.cpp +++ b/rpcs3/Emu/Cell/Modules/cellFs.cpp @@ -1017,9 +1017,10 @@ s32 cellFsChangeFileSizeWithoutAllocation() throw EXCEPTION(""); } -s32 cellFsAllocateFileAreaWithoutZeroFill() +s32 cellFsAllocateFileAreaWithoutZeroFill(vm::cptr path, u64 size) { - throw EXCEPTION(""); + cellFs.warning("cellFsAllocateFileAreaWithoutZeroFill(path=*0x%x, size=0x%llx)", path, size); + return sys_fs_truncate(path, size); } s32 cellFsChangeFileSizeByFdWithoutAllocation() diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index de95dfebdd..ff18d94620 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -124,7 +124,12 @@ s32 sys_fs_open(vm::cptr path, s32 flags, vm::ptr fd, s32 mode, vm::c s32 sys_fs_read(u32 fd, vm::ptr buf, u64 nbytes, vm::ptr nread) { - sys_fs.trace("sys_fs_read(fd=%d, buf=0x%x, nbytes=0x%llx, nread=0x%x)", fd, buf, nbytes, nread); + sys_fs.trace("sys_fs_read(fd=%d, buf=*0x%x, nbytes=0x%llx, nread=*0x%x)", fd, buf, nbytes, nread); + + if (!buf) + { + return CELL_EFAULT; + } const auto file = idm::get(fd); From b073ead988761209f24bdf82571c76ab39feeeb2 Mon Sep 17 00:00:00 2001 From: Raul Tambre Date: Sat, 9 Apr 2016 13:03:53 +0300 Subject: [PATCH 2/6] cellUserInfo: Cleanup, notify about errors --- rpcs3/Emu/Cell/Modules/cellUserInfo.cpp | 26 +++++++++++++++++++++++-- rpcs3/Emu/Cell/Modules/cellUserInfo.h | 24 ++++++++++++++++++----- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp b/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp index 9c6e3f0d85..94a7118349 100644 --- a/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp +++ b/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp @@ -13,7 +13,9 @@ s32 cellUserInfoGetStat(u32 id, vm::ptr stat) cellUserInfo.warning("cellUserInfoGetStat(id=%d, stat=*0x%x)", id, stat); if (id > CELL_SYSUTIL_USERID_MAX) + { return CELL_USERINFO_ERROR_NOUSER; + } if (id == CELL_SYSUTIL_USERID_CURRENT) { @@ -22,15 +24,24 @@ s32 cellUserInfoGetStat(u32 id, vm::ptr stat) } const std::string& path = vfs::get(fmt::format("/dev_hdd0/home/%08d/", id)); + if (!fs::is_dir(path)) + { + cellUserInfo.error("cellUserInfoGetStat(): CELL_USERINFO_ERROR_NOUSER. User %d doesn't exist. Did you delete the user folder?", id); return CELL_USERINFO_ERROR_NOUSER; + } const fs::file f(path + "localusername"); + if (!f) + { + cellUserInfo.error("cellUserInfoGetStat(): CELL_USERINFO_ERROR_INTERNAL. Username for user %d doesn't exist. Did you delete the username file?", id); return CELL_USERINFO_ERROR_INTERNAL; + } stat->id = id; strcpy_trunc(stat->name, f.to_string()); + return CELL_OK; } @@ -52,20 +63,31 @@ s32 cellUserInfoEnableOverlay() return CELL_OK; } -s32 cellUserInfoGetList(vm::ptr listNum, vm::ptr listBuf, vm::ptr currentUserId) +ppu_error_code cellUserInfoGetList(vm::ptr listNum, vm::ptr listBuf, vm::ptr currentUserId) { - cellUserInfo.warning("cellUserInfoGetList(listNum=*0x%x, listBuf=*0x%x, currentUserId=*0x%x)", listNum, listBuf, currentUserId); + cellUserInfo.todo("cellUserInfoGetList(listNum=*0x%x, listBuf=*0x%x, currentUserId=*0x%x)", listNum, listBuf, currentUserId); // If only listNum is NULL, an error will be returned if (listBuf && !listNum) + { return CELL_USERINFO_ERROR_PARAM; + } + if (listNum) + { *listNum = 1; + } + if (listBuf) + { listBuf->userId[0] = 1; + } if (currentUserId) + { + // TODO: Properly set the current user ID here, once implemented *currentUserId = 1; + } return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/cellUserInfo.h b/rpcs3/Emu/Cell/Modules/cellUserInfo.h index a9a77159c0..3b1dc6bdf7 100644 --- a/rpcs3/Emu/Cell/Modules/cellUserInfo.h +++ b/rpcs3/Emu/Cell/Modules/cellUserInfo.h @@ -3,16 +3,30 @@ namespace vm { using namespace ps3; } // Return Codes -enum +enum CellUserInfoError : s32 { CELL_USERINFO_RET_OK = 0, CELL_USERINFO_RET_CANCEL = 1, - CELL_USERINFO_ERROR_BUSY = 0x8002c301, - CELL_USERINFO_ERROR_INTERNAL = 0x8002c302, - CELL_USERINFO_ERROR_PARAM = 0x8002c303, - CELL_USERINFO_ERROR_NOUSER = 0x8002c304, + CELL_USERINFO_ERROR_BUSY = ERROR_CODE(0x8002c301), + CELL_USERINFO_ERROR_INTERNAL = ERROR_CODE(0x8002c302), + CELL_USERINFO_ERROR_PARAM = ERROR_CODE(0x8002c303), + CELL_USERINFO_ERROR_NOUSER = ERROR_CODE(0x8002c304), }; +template<> +inline const char* ppu_error_code::print(CellUserInfoError error) +{ + switch (error) + { + STR_CASE(CELL_USERINFO_ERROR_BUSY); + STR_CASE(CELL_USERINFO_ERROR_INTERNAL); + STR_CASE(CELL_USERINFO_ERROR_PARAM); + STR_CASE(CELL_USERINFO_ERROR_NOUSER); + } + + return nullptr; +} + // Enums enum CellUserInfoParamSize { From 8d2b6f605498d239cad8c8275903e733b2e652e5 Mon Sep 17 00:00:00 2001 From: Raul Tambre Date: Sat, 9 Apr 2016 18:07:30 +0300 Subject: [PATCH 3/6] Implement sceNpUtilCmpNpId Will need a full implementation of emulated network to be "fully" implemented. --- rpcs3/Emu/Cell/Modules/sceNp.cpp | 11 +++++++++-- rpcs3/Emu/Cell/Modules/sceNp.h | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index a1cd7c7ede..0b5cdfd94b 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -1445,9 +1445,16 @@ s32 sceNpSignalingGetPeerNetInfoResult() return CELL_OK; } -s32 sceNpUtilCmpNpId() +s32 sceNpUtilCmpNpId(vm::ptr id1, vm::ptr id2) { - UNIMPLEMENTED_FUNC(sceNp); + sceNp.warning("sceNpUtilCmpNpId(id1=*0x%x, id2=*0x%x)", id1, id2); + + // TODO: Improve the comparison. + if (strcmp(id1->handle.data, id2->handle.data) != 0) + { + return SCE_NP_UTIL_ERROR_NOT_MATCH; + } + return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/sceNp.h b/rpcs3/Emu/Cell/Modules/sceNp.h index 16483c5ffa..e06d8287bf 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.h +++ b/rpcs3/Emu/Cell/Modules/sceNp.h @@ -67,6 +67,23 @@ enum SCE_NP_EXT_ERROR_NO_CONTEXT = 0x8002a6a3, SCE_NP_EXT_ERROR_NO_ORIGIN = 0x8002a6a4, + // NP Common Utility + SCE_NP_UTIL_ERROR_INVALID_ARGUMENT = 0x8002ab01, + SCE_NP_UTIL_ERROR_OUT_OF_MEMORY = 0x8002ab02, + SCE_NP_UTIL_ERROR_INSUFFICIENT = 0x8002ab03, + SCE_NP_UTIL_ERROR_PARSER_FAILED = 0x8002ab04, + SCE_NP_UTIL_ERROR_INVALID_PROTOCOL_ID = 0x8002ab05, + SCE_NP_UTIL_ERROR_INVALID_NP_ID = 0x8002ab06, + SCE_NP_UTIL_ERROR_INVALID_NP_LOBBY_ID = 0x8002ab07, + SCE_NP_UTIL_ERROR_INVALID_NP_ROOM_ID = 0x8002ab08, + SCE_NP_UTIL_ERROR_INVALID_NP_ENV = 0x8002ab09, + SCE_NP_UTIL_ERROR_INVALID_TITLEID = 0x8002ab0a, + SCE_NP_UTIL_ERROR_INVALID_CHARACTER = 0x8002ab0b, + SCE_NP_UTIL_ERROR_INVALID_ESCAPE_STRING = 0x8002ab0c, + SCE_NP_UTIL_ERROR_UNKNOWN_TYPE = 0x8002ab0d, + SCE_NP_UTIL_ERROR_UNKNOWN = 0x8002ab0e, + SCE_NP_UTIL_ERROR_NOT_MATCH = 0x8002ab0f, + // NP Community Utility SCE_NP_COMMUNITY_ERROR_ALREADY_INITIALIZED = 0x8002a101, SCE_NP_COMMUNITY_ERROR_NOT_INITIALIZED = 0x8002a102, From 31ec26a7a1d0099157e5cce9a87f2efdec6a1a3c Mon Sep 17 00:00:00 2001 From: Raul Tambre Date: Sat, 16 Apr 2016 11:32:43 +0300 Subject: [PATCH 4/6] Big sys_net improvements and fixes * Error codes are now properly handled * Couple more functions implemented * Support for setting certain socket information * socketselect() handles Unix waiting behaviour on Windows * accept() fixed --- rpcs3/Emu/Cell/Modules/cellPngDec.cpp | 5 +- rpcs3/Emu/Cell/Modules/sys_net.cpp | 370 +++++++++++++++++++++----- rpcs3/Emu/Cell/Modules/sys_net.h | 111 +++++++- 3 files changed, 410 insertions(+), 76 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellPngDec.cpp b/rpcs3/Emu/Cell/Modules/cellPngDec.cpp index 82b8038ee5..31aa99ba5a 100644 --- a/rpcs3/Emu/Cell/Modules/cellPngDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPngDec.cpp @@ -587,11 +587,10 @@ s32 pngDecodeData(PPUThread& ppu, PHandle handle, PStream stream, vm::ptr da // Check if the outputWidthByte is smaller than the intended output length of a line. For example an image might be in RGB, but we need to output 4 components, so we need to perform alpha padding. else if (stream->out_param.outputWidthByte < (stream->out_param.outputWidth * stream->out_param.outputComponents)) { - // Not sure what to do, when a fixed alpha value isn't specified, but specifying full opaque seems to cause no issues currently. Logging this just in case. + // If fixed alpha is not specified in such a case, the default value for the alpha is 0xFF (255) if (!stream->fixed_alpha) { - cellPngDec.error("Fixed alpha not specified for padding. Please notify a developer of this."); - stream->fixed_alpha_colour = 255; + stream->fixed_alpha_colour = 0xFF; } // We need to fill alpha (before or after, depending on the output colour format) using the fixed alpha value passed by the game. diff --git a/rpcs3/Emu/Cell/Modules/sys_net.cpp b/rpcs3/Emu/Cell/Modules/sys_net.cpp index 126dfd515c..b4f1c1e61d 100644 --- a/rpcs3/Emu/Cell/Modules/sys_net.cpp +++ b/rpcs3/Emu/Cell/Modules/sys_net.cpp @@ -10,10 +10,13 @@ #include #include #include +#include #include #include #endif +#include + logs::channel libnet("libnet", logs::level::notice); // We map host sockets to sequential IDs to return as FDs because syscalls using @@ -22,6 +25,7 @@ logs::channel libnet("libnet", logs::level::notice); std::vector g_socketMap{ 0 }; // Auxiliary Functions +// FIXME: Use the variant from OS instead? Why do we even have such a custom function? int inet_pton4(const char *src, char *dst) { const char digits[] = "0123456789"; @@ -72,19 +76,6 @@ int inet_pton(int af, const char *src, char *dst) } } -s32 getLastError() -{ -#ifdef _WIN32 - s32 ret = WSAGetLastError(); - if (ret > 10000 && ret < 11000) - return ret % 10000; - else - return -1; -#else - return errno; -#endif -} - void copy_fdset(fd_set* set, vm::ptr src) { FD_ZERO(set); @@ -94,9 +85,9 @@ void copy_fdset(fd_set* set, vm::ptr src) // Go through the bit set fds_bits and calculate the // socket FDs from it, setting it in the native fd-set. - for (int i = 0; i < 32; i++) + for (s32 i = 0; i < 32; i++) { - for (int bit = 0; bit < 32; bit++) + for (s32 bit = 0; bit < 32; bit++) { if (src->fds_bits[i] & (1 << bit)) { @@ -115,6 +106,7 @@ namespace sys_net { be_t _errno; be_t _h_errno; + char addr[16]; }; // TODO @@ -126,6 +118,9 @@ namespace sys_net if (!g_tls_net_data) { g_tls_net_data.set(vm::alloc(sizeof(decltype(g_tls_net_data)::type), vm::main)); + + // Initial values + g_tls_net_data->_errno = SYS_NET_EBUSY; thread_ctrl::atexit([addr = g_tls_net_data.addr()] { @@ -148,27 +143,72 @@ namespace sys_net return g_tls_net_data.ref(&_tls_data_t::_h_errno); } + // Error helper functions + s32 get_last_error() + { + // Convert the error code for socket functions to a one for sys_net +#ifdef _WIN32 + switch (WSAGetLastError()) + { + case WSAEWOULDBLOCK: + return SYS_NET_EWOULDBLOCK; + + default: + throw EXCEPTION("Unknown Win32 socket error: %d", WSAGetLastError()); + } +#else + switch (errno) + { + case EWOULDBLOCK: + return SYS_NET_EWOULDBLOCK; + + default: + throw EXCEPTION("Unknown Unix socket error: %d", errno); + } + + return errno; +#endif + } + // Functions s32 accept(s32 s, vm::ptr addr, vm::ptr paddrlen) { libnet.warning("accept(s=%d, family=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen); s = g_socketMap[s]; - if (!addr) { - int ret = ::accept(s, nullptr, nullptr); - get_errno() = getLastError(); - return ret; + s32 ret; + + if (!addr) + { + ret = ::accept(s, nullptr, nullptr); + + if (ret < 0) + { + libnet.error("accept(): error %d", get_errno() = get_last_error()); + return -1; + } } - else { + else + { ::sockaddr _addr; - memcpy(&_addr, addr.get_ptr(), sizeof(::sockaddr)); - _addr.sa_family = addr->sa_family; - ::socklen_t _paddrlen; - s32 ret = ::accept(s, &_addr, &_paddrlen); + ::socklen_t _paddrlen = 16; + + ret = ::accept(s, &_addr, &_paddrlen); + + if (ret < 0) + { + libnet.error("accept(): error %d", get_errno() = get_last_error()); + return -1; + } + *paddrlen = _paddrlen; - get_errno() = getLastError(); - return ret; + addr->sa_len = _paddrlen; + addr->sa_family = _addr.sa_family; + memcpy(addr->sa_data, _addr.sa_data, addr->sa_len - 2); } + + g_socketMap.push_back(ret); + return g_socketMap.size() - 1; } s32 bind(s32 s, vm::cptr addr, u32 addrlen) @@ -180,9 +220,14 @@ namespace sys_net memcpy(&saddr, addr.get_ptr(), sizeof(::sockaddr_in)); saddr.sin_family = addr->sa_family; const char *ipaddr = ::inet_ntoa(saddr.sin_addr); - libnet.warning("binding on %s to port %d", ipaddr, ntohs(saddr.sin_port)); + libnet.warning("binding to %s on port %d", ipaddr, ntohs(saddr.sin_port)); s32 ret = ::bind(s, (const ::sockaddr*)&saddr, addrlen); - get_errno() = getLastError(); + + if (ret != 0) + { + libnet.error("bind(): error %d", get_errno() = get_last_error()); + return -1; + } return ret; } @@ -195,10 +240,19 @@ namespace sys_net ::sockaddr_in saddr; memcpy(&saddr, addr.get_ptr(), sizeof(::sockaddr_in)); saddr.sin_family = addr->sa_family; - const char *ipaddr = ::inet_ntoa(saddr.sin_addr); - libnet.warning("connecting on %s to port %d", ipaddr, ntohs(saddr.sin_port)); + + libnet.warning("connecting to %s on port %d", ::inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port)); s32 ret = ::connect(s, (const ::sockaddr*)&saddr, addrlen); - get_errno() = getLastError(); + + if (ret != 0) + { + if ((get_errno() = get_last_error()) != SYS_NET_EWOULDBLOCK) + { + libnet.error("connect(): error %d", get_errno().get_ref()); + } + + return -1; + } return ret; } @@ -269,10 +323,18 @@ namespace sys_net return CELL_OK; } - s32 inet_ntoa() + vm::ptr inet_ntoa(u32 in) { - UNIMPLEMENTED_FUNC(libnet); - return CELL_OK; + libnet.warning("inet_ntoa(in=0x%x)", in); + initialize_tls(); + + ::in_addr addr; + addr.s_addr = in; + + char* result = ::inet_ntoa(addr); + strcpy(g_tls_net_data->addr, result); + + return vm::ptr::make(vm::get_addr(g_tls_net_data->addr)); } vm::cptr inet_ntop(s32 af, vm::ptr src, vm::ptr dst, u32 size) @@ -298,8 +360,14 @@ namespace sys_net { libnet.warning("listen(s=%d, backlog=%d)", s, backlog); s = g_socketMap[s]; + s32 ret = ::listen(s, backlog); - get_errno() = getLastError(); + + if (ret != 0) + { + libnet.error("listen(): error %d", get_errno() = get_last_error()); + return -1; + } return ret; } @@ -310,7 +378,12 @@ namespace sys_net s = g_socketMap[s]; s32 ret = ::recv(s, buf.get_ptr(), len, flags); - get_errno() = getLastError(); + + if (ret < 0) + { + libnet.error("recv(): error %d", get_errno() = get_last_error()); + return -1; + } return ret; } @@ -321,12 +394,18 @@ namespace sys_net s = g_socketMap[s]; ::sockaddr _addr; + ::socklen_t _paddrlen; + memcpy(&_addr, addr.get_ptr(), sizeof(::sockaddr)); _addr.sa_family = addr->sa_family; - ::socklen_t _paddrlen; s32 ret = ::recvfrom(s, buf.get_ptr(), len, flags, &_addr, &_paddrlen); *paddrlen = _paddrlen; - get_errno() = getLastError(); + + if (ret < 0) + { + libnet.error("recvfrom(): error %d", get_errno() = get_last_error()); + return -1; + } return ret; } @@ -343,7 +422,12 @@ namespace sys_net s = g_socketMap[s]; s32 ret = ::send(s, buf.get_ptr(), len, flags); - get_errno() = getLastError(); + + if (ret < 0) + { + libnet.error("send(): error %d", get_errno() = get_last_error()); + return -1; + } return ret; } @@ -363,18 +447,121 @@ namespace sys_net memcpy(&_addr, addr.get_ptr(), sizeof(::sockaddr)); _addr.sa_family = addr->sa_family; s32 ret = ::sendto(s, buf.get_ptr(), len, flags, &_addr, addrlen); - get_errno() = getLastError(); + + if (ret < 0) + { + libnet.error("sendto(): error %d", get_errno() = get_last_error()); + return -1; + } return ret; } - s32 setsockopt(s32 s, s32 level, s32 optname, vm::cptr optval, u32 optlen) + s32 setsockopt(s32 s, s32 level, s32 optname, vm::cptr optval, u32 optlen) { - libnet.warning("socket(s=%d, level=%d, optname=%d, optval=*0x%x, optlen=%d)", s, level, optname, optval, optlen); + libnet.warning("setsockopt(s=%d, level=%d, optname=%d, optval=*0x%x, optlen=%d)", s, level, optname, optval, optlen); s = g_socketMap[s]; - s32 ret = ::setsockopt(s, level, optname, optval.get_ptr(), optlen); - get_errno() = getLastError(); + if (level != SOL_SOCKET && level != IPPROTO_TCP) + { + throw EXCEPTION("Invalid socket option level!"); + } + + s32 ret; + +#ifdef _WIN32 + if (level == SOL_SOCKET) + { + switch (optname) + { + case OP_SO_NBIO: + { + unsigned long mode = *(unsigned long*)optval.get_ptr(); + ret = ioctlsocket(s, FIONBIO, &mode); + break; + } + + default: + throw EXCEPTION("Unknown socket option for Win32: 0x%x", optname); + } + } + else if (level == PROTO_IPPROTO_TCP) + { + switch (optname) + { + case OP_TCP_NODELAY: + { + const char delay = *(char*)optval.get_ptr(); + ret = ::setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &delay, sizeof(delay)); + break; + } + + case OP_TCP_MAXSEG: + { + libnet.warning("TCP_MAXSEG can't be set on Windows."); + break; + } + + default: + throw EXCEPTION("Unknown TCP option for Win32: 0x%x", optname); + } + } +#else + if (level == SOL_SOCKET) + { + switch (optname) + { + case OP_SO_NBIO: + { + // Obtain the flags + s32 flags = fcntl(s, F_GETFL, 0); + + if (flags < 0) + { + throw EXCEPTION("Failed to obtain socket flags."); + } + + u32 mode = *(u32*)optval.get_ptr(); + flags = mode ? (flags &~O_NONBLOCK) : (flags | O_NONBLOCK); + + // Re-set the flags + ret = fcntl(s, F_SETFL, flags); + break; + } + + default: + throw EXCEPTION("Unknown socket option for Unix: 0x%x", optname); + } + } + else if (level == PROTO_IPPROTO_TCP) + { + switch (optname) + { + case OP_TCP_NODELAY: + { + u32 delay = *(u32*)optval.get_ptr(); + ret = ::setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &delay, optlen); + break; + } + + case OP_TCP_MAXSEG: + { + u32 maxseg = *(u32*)optval.get_ptr(); + ret = ::setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, &maxseg, optlen); + break; + } + + default: + throw EXCEPTION("Unknown TCP option for Win32: 0x%x", optname); + } + } +#endif + + if (ret != 0) + { + libnet.error("setsockopt(): error %d", get_errno() = get_last_error()); + return -1; + } return ret; } @@ -385,7 +572,7 @@ namespace sys_net s = g_socketMap[s]; s32 ret = ::shutdown(s, how); - get_errno() = getLastError(); + get_errno() = get_last_error(); return ret; } @@ -394,8 +581,30 @@ namespace sys_net { libnet.warning("socket(family=%d, type=%d, protocol=%d)", family, type, protocol); + if (type < 1 || type > 10 || (type > 4 && type < 6) || (type > 6 && type < 10)) + { + get_errno() = SYS_NET_EPROTONOSUPPORT; + return -1; + } + + // HACKS: Neither Unix nor Windows support TCP/UDP over UDPP2P. + // But what's the usage of it anyways? + if (type == SOCK_STREAM_P2P) + { + type = SOCK_STREAM; + } + else if (type == SOCK_DGRAM_P2P) + { + type = SOCK_DGRAM; + } + s32 sock = ::socket(family, type, protocol); - get_errno() = getLastError(); + + if (sock < 0) + { + libnet.error("socket(): error %d", get_errno() = get_last_error()); + return -1; + } g_socketMap.push_back(sock); return g_socketMap.size() - 1; @@ -403,15 +612,21 @@ namespace sys_net s32 socketclose(s32 s) { - libnet.warning("socket(s=%d)", s); + libnet.warning("socketclose(s=%d)", s); s = g_socketMap[s]; #ifdef _WIN32 - int ret = ::closesocket(s); + s32 ret = ::closesocket(s); #else - int ret = ::close(s); + s32 ret = ::close(s); #endif - get_errno() = getLastError(); + + if (ret != 0) + { + libnet.error("socketclose(): error %d", get_errno() = get_last_error()); + return -1; + } + return ret; } @@ -444,16 +659,26 @@ namespace sys_net copy_fdset(&_writefds, writefds); copy_fdset(&_exceptfds, exceptfds); - s32 ret = ::select(nfds, &_readfds, &_writefds, &_exceptfds, timeout ? &_timeout : NULL); - get_errno() = getLastError(); - - if (getLastError() >= 0) +#ifdef _WIN32 + // On Unix, when the sets are empty (thus nothing to wait on), it waits until the timeout. + // This behaviour is often used to "sleep" on Unix based systems. + // WinSock in such case returns WSAEINVAL and doesn't allow such behaviour. + // Since it's not possible on Windows, we just return and say that the timeout is over and hope that it's good enough. + if (_readfds.fd_count == 0 && _writefds.fd_count == 0 && _exceptfds.fd_count == 0) { - libnet.error("socketselect(): error %d", getLastError()); + return 0; // Timeout! + } +#endif + + s32 ret = ::select(g_socketMap[nfds], &_readfds, &_writefds, &_exceptfds, timeout ? &_timeout : nullptr); + + if (ret < 0) + { + libnet.error("socketselect(): error %d", get_errno() = get_last_error()); + return -1; } - //return ret; - return CELL_OK; + return ret; } s32 sys_net_initialize_network_ex(vm::ptr param) @@ -463,8 +688,16 @@ namespace sys_net #ifdef _WIN32 WSADATA wsaData; WORD wVersionRequested = MAKEWORD(2, 2); - WSAStartup(wVersionRequested, &wsaData); + + if (WSAStartup(wVersionRequested, &wsaData) != 0) + { + throw EXCEPTION("sys_net: WSAStartup unsuccessful"); + } #endif + + // Errno is set to 0 upon initialization + get_errno() = 0; + return CELL_OK; } @@ -498,9 +731,9 @@ namespace sys_net return CELL_OK; } - s32 sys_net_get_sockinfo() + s32 sys_net_get_sockinfo(s32 s, vm::ptr p, s32 n) { - UNIMPLEMENTED_FUNC(libnet); + libnet.todo("sys_net_get_sockinfo()"); return CELL_OK; } @@ -525,7 +758,6 @@ namespace sys_net vm::ptr _sys_net_errno_loc() { libnet.warning("_sys_net_errno_loc()"); - return get_errno().ptr(); } @@ -591,18 +823,22 @@ namespace sys_net s32 sys_net_finalize_network() { - libnet.warning("sys_net_initialize_network_ex()"); + libnet.warning("sys_net_finalize_network()"); #ifdef _WIN32 WSACleanup(); #endif + + // Errno is set to SYS_NET_EBUSY after finalization + get_errno() = SYS_NET_EBUSY; + return CELL_OK; } - s32 _sys_net_h_errno_loc() + vm::ptr _sys_net_h_errno_loc() { - UNIMPLEMENTED_FUNC(libnet); - return CELL_OK; + libnet.warning("_sys_net_h_errno_loc()"); + return get_h_errno().ptr(); } s32 sys_net_set_netemu_test_param() @@ -611,9 +847,9 @@ namespace sys_net return CELL_OK; } - s32 sys_net_free_thread_context() + s32 sys_net_free_thread_context(u64 tid, s32 flags) { - UNIMPLEMENTED_FUNC(libnet); + libnet.todo("sys_net_free_thread_context(tid=%d, flags=%d)", tid, flags); return CELL_OK; } } diff --git a/rpcs3/Emu/Cell/Modules/sys_net.h b/rpcs3/Emu/Cell/Modules/sys_net.h index f704539f09..03eab9efd9 100644 --- a/rpcs3/Emu/Cell/Modules/sys_net.h +++ b/rpcs3/Emu/Cell/Modules/sys_net.h @@ -2,16 +2,102 @@ namespace vm { using namespace ps3; } -// must die -#undef s_addr - namespace sys_net { + // Error codes + enum + { + SYS_NET_ENOENT = 2, + SYS_NET_EINTR = 4, + SYS_NET_EBADF = 9, + SYS_NET_ENOMEM = 12, + SYS_NET_EACCES = 13, + SYS_NET_EFAULT = 14, + SYS_NET_EBUSY = 16, + SYS_NET_EINVAL = 22, + SYS_NET_EMFILE = 24, + SYS_NET_ENOSPC = 28, + SYS_NET_EPIPE = 32, + SYS_NET_EAGAIN = 35, + SYS_NET_EWOULDBLOCK = SYS_NET_EAGAIN, + SYS_NET_EINPROGRESS = 36, + SYS_NET_EALREADY = 37, + SYS_NET_EDESTADDRREQ = 39, + SYS_NET_EMSGSIZE = 40, + SYS_NET_EPROTOTYPE = 41, + SYS_NET_ENOPROTOOPT = 42, + SYS_NET_EPROTONOSUPPORT = 43, + SYS_NET_EOPNOTSUPP = 45, + SYS_NET_EPFNOSUPPORT = 46, + SYS_NET_EAFNOSUPPORT = 47, + SYS_NET_EADDRINUSE = 48, + SYS_NET_EADDRNOTAVAIL = 49, + SYS_NET_ENETDOWN = 50, + SYS_NET_ENETUNREACH = 51, + SYS_NET_ECONNABORTED = 53, + SYS_NET_ECONNRESET = 54, + SYS_NET_ENOBUFS = 55, + SYS_NET_EISCONN = 56, + SYS_NET_ENOTCONN = 57, + SYS_NET_ESHUTDOWN = 58, + SYS_NET_ETOOMANYREFS = 59, + SYS_NET_ETIMEDOUT = 60, + SYS_NET_ECONNREFUSED = 61, + SYS_NET_EHOSTDOWN = 64, + SYS_NET_EHOSTUNREACH = 65, + }; + + // Socket types + enum + { + SOCK_STREAM = 1, + SOCK_DGRAM = 2, + SOCK_RAW = 3, + SOCK_DGRAM_P2P = 6, + SOCK_STREAM_P2P = 10, + }; + + // Socket options + // Note: All options are prefixed with "OP_" to prevent name conflicts. + enum + { + OP_SO_SNDBUF = 0x1001, + OP_SO_RCVBUF = 0x1002, + OP_SO_SNDLOWAT = 0x1003, + OP_SO_RCVLOWAT = 0x1004, + OP_SO_SNDTIMEO = 0x1005, + OP_SO_RCVTIMEO = 0x1006, + OP_SO_ERROR = 0x1007, + OP_SO_TYPE = 0x1008, + OP_SO_NBIO = 0x1100, // Non-blocking IO + OP_SO_TPPOLICY = 0x1101, + }; + + // TCP options + enum + { + OP_TCP_NODELAY = 1, + OP_TCP_MAXSEG = 2, + OP_TCP_MSS_TO_ADVERTISE = 3, + }; + + // IP protocols + // Note: Proctols are prefixed with "PROTO_" to prevent name conflicts + enum + { + PROTO_IPPROTO_IP = 0, + PROTO_IPPROTO_ICMP = 1, + PROTO_IPPROTO_IGMP = 2, + PROTO_IPPROTO_TCP = 6, + PROTO_IPPROTO_UDP = 17, + PROTO_IPPROTO_ICMPV6 = 58, + }; + // only for reference, no need to use it - using in_addr_t = u32; - using in_port_t = u16; + using in_addr_t = u32; + using in_port_t = u16; using sa_family_t = u8; - using socklen_t = u32; + using socklen_t = u32; struct fd_set { @@ -112,6 +198,19 @@ namespace sys_net be_t tv_sec; be_t tv_usec; }; + + struct sys_net_sockinfo_t + { + be_t s; + be_t proto; + be_t recv_queue_length; + be_t send_queue_length; + in_addr local_adr; + be_t local_port; + in_addr remote_adr; + be_t remote_port; + be_t state; + }; } struct sys_net_initialize_parameter_t From 2e5d20c3153e65e5920ee8f9065901b7273737f1 Mon Sep 17 00:00:00 2001 From: Raul Tambre Date: Mon, 18 Apr 2016 10:06:22 +0300 Subject: [PATCH 5/6] sys_net: Use IDM, initialize networking on startup --- rpcs3/Emu/Cell/Modules/sys_net.cpp | 172 +++++++++++++++++------------ rpcs3/rpcs3.cpp | 5 + 2 files changed, 106 insertions(+), 71 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/sys_net.cpp b/rpcs3/Emu/Cell/Modules/sys_net.cpp index b4f1c1e61d..e8a71bb949 100644 --- a/rpcs3/Emu/Cell/Modules/sys_net.cpp +++ b/rpcs3/Emu/Cell/Modules/sys_net.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" +#include "Emu/IdManager.h" #include "sys_net.h" @@ -19,10 +20,14 @@ logs::channel libnet("libnet", logs::level::notice); -// We map host sockets to sequential IDs to return as FDs because syscalls using -// socketselect(), etc. expect socket FDs to be under 1024. -// We start at 1 because 0 is an invalid socket. -std::vector g_socketMap{ 0 }; +namespace sys_net +{ +#ifdef _WIN32 + using socket_t = SOCKET; +#else + using socket_t = int; +#endif +} // Auxiliary Functions // FIXME: Use the variant from OS instead? Why do we even have such a custom function? @@ -76,6 +81,35 @@ int inet_pton(int af, const char *src, char *dst) } } +// Custom structure for sockets +// We map host sockets to sequential IDs to return as descriptors because syscalls expect socket IDs to be under 1024. +struct sys_net_socket final +{ + using id_base = sys_net_socket; + + static constexpr u32 id_min = 0; // Minimal valid socket number is 0 (not 1). + static constexpr u32 id_max = 1023; + + sys_net::socket_t s = -1; + + explicit sys_net_socket(s32 socket) : s(socket) + { + } + + ~sys_net_socket() + { + if (s != -1) +#ifdef _WIN32 + ::closesocket(s); +#else + ::close(s); +#endif + } + + sys_net_socket(sys_net_socket const &) = delete; + sys_net_socket& operator=(const sys_net_socket&) = delete; +}; + void copy_fdset(fd_set* set, vm::ptr src) { FD_ZERO(set); @@ -91,9 +125,9 @@ void copy_fdset(fd_set* set, vm::ptr src) { if (src->fds_bits[i] & (1 << bit)) { - u32 sock = (i << 5) | bit; + sys_net::socket_t sock = idm::get((i << 5) | bit)->s; //libnet.error("setting: fd %d", sock); - FD_SET(g_socketMap[sock], set); + FD_SET(sock, set); } } } @@ -147,40 +181,41 @@ namespace sys_net s32 get_last_error() { // Convert the error code for socket functions to a one for sys_net + s32 result; + const char* name{}; + #ifdef _WIN32 - switch (WSAGetLastError()) - { - case WSAEWOULDBLOCK: - return SYS_NET_EWOULDBLOCK; - - default: - throw EXCEPTION("Unknown Win32 socket error: %d", WSAGetLastError()); - } + switch (s32 code = WSAGetLastError()) +#define ERROR_CASE(error) case WSA ## error: result = SYS_NET_ ## error; name = #error; break; #else - switch (errno) + switch (s32 code = errno) +#define ERROR_CASE(error) case error: result = SYS_NET_ ## error; name = #error; break; +#endif { - case EWOULDBLOCK: - return SYS_NET_EWOULDBLOCK; - - default: - throw EXCEPTION("Unknown Unix socket error: %d", errno); + ERROR_CASE(EWOULDBLOCK); + default: throw fmt::exception("Unknown/illegal socket error: %d" HERE, code); } - return errno; -#endif + if (name && result != SYS_NET_EWOULDBLOCK) + { + ppu_error_code::report(result, name); + } + + return result; +#undef ERROR_CASE } // Functions s32 accept(s32 s, vm::ptr addr, vm::ptr paddrlen) { libnet.warning("accept(s=%d, family=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); s32 ret; if (!addr) { - ret = ::accept(s, nullptr, nullptr); + ret = ::accept(sock->s, nullptr, nullptr); if (ret < 0) { @@ -193,7 +228,7 @@ namespace sys_net ::sockaddr _addr; ::socklen_t _paddrlen = 16; - ret = ::accept(s, &_addr, &_paddrlen); + ret = ::accept(sock->s, &_addr, &_paddrlen); if (ret < 0) { @@ -207,21 +242,20 @@ namespace sys_net memcpy(addr->sa_data, _addr.sa_data, addr->sa_len - 2); } - g_socketMap.push_back(ret); - return g_socketMap.size() - 1; + return idm::make(ret); } s32 bind(s32 s, vm::cptr addr, u32 addrlen) { libnet.warning("bind(s=%d, family=*0x%x, addrlen=%d)", s, addr, addrlen); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); ::sockaddr_in saddr; memcpy(&saddr, addr.get_ptr(), sizeof(::sockaddr_in)); saddr.sin_family = addr->sa_family; const char *ipaddr = ::inet_ntoa(saddr.sin_addr); libnet.warning("binding to %s on port %d", ipaddr, ntohs(saddr.sin_port)); - s32 ret = ::bind(s, (const ::sockaddr*)&saddr, addrlen); + s32 ret = ::bind(sock->s, (const ::sockaddr*)&saddr, addrlen); if (ret != 0) { @@ -235,14 +269,14 @@ namespace sys_net s32 connect(s32 s, vm::ptr addr, u32 addrlen) { libnet.warning("connect(s=%d, family=*0x%x, addrlen=%d)", s, addr, addrlen); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); ::sockaddr_in saddr; memcpy(&saddr, addr.get_ptr(), sizeof(::sockaddr_in)); saddr.sin_family = addr->sa_family; libnet.warning("connecting to %s on port %d", ::inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port)); - s32 ret = ::connect(s, (const ::sockaddr*)&saddr, addrlen); + s32 ret = ::connect(sock->s, (const ::sockaddr*)&saddr, addrlen); if (ret != 0) { @@ -359,9 +393,9 @@ namespace sys_net s32 listen(s32 s, s32 backlog) { libnet.warning("listen(s=%d, backlog=%d)", s, backlog); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); - s32 ret = ::listen(s, backlog); + s32 ret = ::listen(sock->s, backlog); if (ret != 0) { @@ -375,9 +409,9 @@ namespace sys_net s32 recv(s32 s, vm::ptr buf, u32 len, s32 flags) { libnet.warning("recv(s=%d, buf=*0x%x, len=%d, flags=0x%x)", s, buf, len, flags); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); - s32 ret = ::recv(s, buf.get_ptr(), len, flags); + s32 ret = ::recv(sock->s, buf.get_ptr(), len, flags); if (ret < 0) { @@ -391,14 +425,14 @@ namespace sys_net s32 recvfrom(s32 s, vm::ptr buf, u32 len, s32 flags, vm::ptr addr, vm::ptr paddrlen) { libnet.warning("recvfrom(s=%d, buf=*0x%x, len=%d, flags=0x%x, addr=*0x%x, paddrlen=*0x%x)", s, buf, len, flags, addr, paddrlen); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); ::sockaddr _addr; ::socklen_t _paddrlen; memcpy(&_addr, addr.get_ptr(), sizeof(::sockaddr)); _addr.sa_family = addr->sa_family; - s32 ret = ::recvfrom(s, buf.get_ptr(), len, flags, &_addr, &_paddrlen); + s32 ret = ::recvfrom(sock->s, buf.get_ptr(), len, flags, &_addr, &_paddrlen); *paddrlen = _paddrlen; if (ret < 0) @@ -419,9 +453,9 @@ namespace sys_net s32 send(s32 s, vm::cptr buf, u32 len, s32 flags) { libnet.warning("send(s=%d, buf=*0x%x, len=%d, flags=0x%x)", s, buf, len, flags); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); - s32 ret = ::send(s, buf.get_ptr(), len, flags); + s32 ret = ::send(sock->s, buf.get_ptr(), len, flags); if (ret < 0) { @@ -441,12 +475,12 @@ namespace sys_net s32 sendto(s32 s, vm::cptr buf, u32 len, s32 flags, vm::ptr addr, u32 addrlen) { libnet.warning("sendto(s=%d, buf=*0x%x, len=%d, flags=0x%x, addr=*0x%x, addrlen=%d)", s, buf, len, flags, addr, addrlen); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); ::sockaddr _addr; memcpy(&_addr, addr.get_ptr(), sizeof(::sockaddr)); _addr.sa_family = addr->sa_family; - s32 ret = ::sendto(s, buf.get_ptr(), len, flags, &_addr, addrlen); + s32 ret = ::sendto(sock->s, buf.get_ptr(), len, flags, &_addr, addrlen); if (ret < 0) { @@ -460,7 +494,7 @@ namespace sys_net s32 setsockopt(s32 s, s32 level, s32 optname, vm::cptr optval, u32 optlen) { libnet.warning("setsockopt(s=%d, level=%d, optname=%d, optval=*0x%x, optlen=%d)", s, level, optname, optval, optlen); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); if (level != SOL_SOCKET && level != IPPROTO_TCP) { @@ -477,7 +511,7 @@ namespace sys_net case OP_SO_NBIO: { unsigned long mode = *(unsigned long*)optval.get_ptr(); - ret = ioctlsocket(s, FIONBIO, &mode); + ret = ioctlsocket(sock->s, FIONBIO, &mode); break; } @@ -492,7 +526,7 @@ namespace sys_net case OP_TCP_NODELAY: { const char delay = *(char*)optval.get_ptr(); - ret = ::setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &delay, sizeof(delay)); + ret = ::setsockopt(sock->s, IPPROTO_TCP, TCP_NODELAY, &delay, sizeof(delay)); break; } @@ -525,7 +559,7 @@ namespace sys_net flags = mode ? (flags &~O_NONBLOCK) : (flags | O_NONBLOCK); // Re-set the flags - ret = fcntl(s, F_SETFL, flags); + ret = fcntl(sock->s, F_SETFL, flags); break; } @@ -540,14 +574,14 @@ namespace sys_net case OP_TCP_NODELAY: { u32 delay = *(u32*)optval.get_ptr(); - ret = ::setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &delay, optlen); + ret = ::setsockopt(sock->s, IPPROTO_TCP, TCP_NODELAY, &delay, optlen); break; } case OP_TCP_MAXSEG: { u32 maxseg = *(u32*)optval.get_ptr(); - ret = ::setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, &maxseg, optlen); + ret = ::setsockopt(sock->s, IPPROTO_TCP, TCP_MAXSEG, &maxseg, optlen); break; } @@ -569,10 +603,15 @@ namespace sys_net s32 shutdown(s32 s, s32 how) { libnet.warning("shutdown(s=%d, how=%d)", s, how); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); - s32 ret = ::shutdown(s, how); - get_errno() = get_last_error(); + s32 ret = ::shutdown(sock->s, how); + + if (ret != 0) + { + libnet.error("shutdown(): error %d", get_errno() = get_last_error()); + return -1; + } return ret; } @@ -591,14 +630,16 @@ namespace sys_net // But what's the usage of it anyways? if (type == SOCK_STREAM_P2P) { + libnet.warning("SOCK_STREAM_P2P is not properly implemented."); type = SOCK_STREAM; } else if (type == SOCK_DGRAM_P2P) { + libnet.warning("SOCK_DGRAM_P2P is not properly implemented."); type = SOCK_DGRAM; } - s32 sock = ::socket(family, type, protocol); + socket_t sock = ::socket(family, type, protocol); if (sock < 0) { @@ -606,19 +647,18 @@ namespace sys_net return -1; } - g_socketMap.push_back(sock); - return g_socketMap.size() - 1; + return idm::make(sock); } s32 socketclose(s32 s) { libnet.warning("socketclose(s=%d)", s); - s = g_socketMap[s]; + std::shared_ptr sock = idm::get(s); #ifdef _WIN32 - s32 ret = ::closesocket(s); + s32 ret = ::closesocket(sock->s); #else - s32 ret = ::close(s); + s32 ret = ::close(sock->s); #endif if (ret != 0) @@ -627,6 +667,9 @@ namespace sys_net return -1; } + idm::get(s)->s = -1; + idm::remove(s); + return ret; } @@ -670,7 +713,8 @@ namespace sys_net } #endif - s32 ret = ::select(g_socketMap[nfds], &_readfds, &_writefds, &_exceptfds, timeout ? &_timeout : nullptr); + // There's no good way to determine nfds and it shouldn't be too slow, so let's let it check the whole set. It also isn't used on Windows. + s32 ret = ::select(FD_SETSIZE, &_readfds, &_writefds, &_exceptfds, timeout ? &_timeout : nullptr); if (ret < 0) { @@ -685,16 +729,6 @@ namespace sys_net { libnet.warning("sys_net_initialize_network_ex(param=*0x%x)", param); -#ifdef _WIN32 - WSADATA wsaData; - WORD wVersionRequested = MAKEWORD(2, 2); - - if (WSAStartup(wVersionRequested, &wsaData) != 0) - { - throw EXCEPTION("sys_net: WSAStartup unsuccessful"); - } -#endif - // Errno is set to 0 upon initialization get_errno() = 0; @@ -825,10 +859,6 @@ namespace sys_net { libnet.warning("sys_net_finalize_network()"); -#ifdef _WIN32 - WSACleanup(); -#endif - // Errno is set to SYS_NET_EBUSY after finalization get_errno() = SYS_NET_EBUSY; diff --git a/rpcs3/rpcs3.cpp b/rpcs3/rpcs3.cpp index 814391fb26..37c83d4561 100644 --- a/rpcs3/rpcs3.cpp +++ b/rpcs3/rpcs3.cpp @@ -254,9 +254,14 @@ Rpcs3App::Rpcs3App() #ifdef _WIN32 timeBeginPeriod(1); + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 2); + WSAStartup(wVersionRequested, &wsaData); + std::atexit([] { timeEndPeriod(1); + WSACleanup(); }); #endif From 92446de9d98b8f523342ce6954fd246f49071bb0 Mon Sep 17 00:00:00 2001 From: Raul Tambre Date: Wed, 27 Apr 2016 18:08:12 +0300 Subject: [PATCH 6/6] Change "start after boot" default value to true --- rpcs3/Emu/System.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 253824b2de..4db13bc6fd 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -25,7 +25,7 @@ #include -cfg::bool_entry g_cfg_autostart(cfg::root.misc, "Always start after boot"); +cfg::bool_entry g_cfg_autostart(cfg::root.misc, "Always start after boot", true); cfg::bool_entry g_cfg_autoexit(cfg::root.misc, "Exit RPCS3 when process finishes"); std::string g_cfg_defaults;