diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 9d9bc2098f..b93ca87d44 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -171,6 +171,8 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "ColorPickingDrawSyncReadback", &flags_.ColorPickingDrawSyncReadback); CheckSetting(iniFile, gameID, "SeparateLogicOpDraws", &flags_.SeparateLogicOpDraws); CheckSetting(iniFile, gameID, "SnapHalfPixelSprites", &flags_.SnapHalfPixelSprites); + CheckSetting(iniFile, gameID, "MatchingCancelUnknownTargetSucceeds", &flags_.MatchingCancelUnknownTargetSucceeds); + CheckSetting(iniFile, gameID, "MatchingStartUnknownIdSucceeds", &flags_.MatchingStartUnknownIdSucceeds); CheckSetting(iniFile, gameID, "FixedFramebuffer", &flags_.FixedFramebuffer); CheckSetting(iniFile, gameID, "NativeResolutionFramebuffer", &flags_.NativeResolutionFramebuffer); } diff --git a/Core/Compatibility.h b/Core/Compatibility.h index a700a736aa..ba428d5884 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -137,6 +137,8 @@ struct CompatFlags { bool ColorPickingDrawSyncReadback; bool SeparateLogicOpDraws; bool SnapHalfPixelSprites; + bool MatchingCancelUnknownTargetSucceeds; + bool MatchingStartUnknownIdSucceeds; CompatFramebuffer FixedFramebuffer; uint32_t NativeResolutionFramebuffer; }; diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 6397f59efa..c8013fc17b 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -145,7 +145,9 @@ static int chatMessageGeneration = 0; static int chatMessageCount = 0; bool isMacMatch(const SceNetEtherAddr* addr1, const SceNetEtherAddr* addr2) { - // Ignoring the 1st byte since there are games (ie. Gran Turismo) who tamper with the 1st byte of OUI to change the unicast/multicast bit + // Ignoring the 1st byte: games overwrite it (Gran Turismo changes the unicast bit, Street Riders sets it to + // 00 when sending PDP). On a PSP, whose MACs start with 00, that's harmless; PS Vitas' don't, so the aemu + // plugin ignores it too. return (memcmp(((const char*)addr1)+1, ((const char*)addr2)+1, ETHER_ADDR_LEN-1) == 0); } @@ -1570,7 +1572,8 @@ int friendFinder() { // Calculate EnterGameMode Timeout to prevent waiting forever for disconnected players if (isAdhocctlBusy && adhocctlState == ADHOCCTL_STATE_DISCONNECTED && adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE && netAdhocGameModeEntered && static_cast(now - adhocctlStartTime) > netAdhocEnterGameModeTimeout) { netAdhocGameModeEntered = false; - notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, SCE_NET_ADHOC_ERROR_TIMEOUT); + // adhocctl's own TIMEOUT (pspautotests net/adhoc/gamemode). + notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, SCE_NET_ADHOCCTL_ERROR_TIMEOUT); } auto friend_finder_wait = [] { diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 756d835c73..0884c4a596 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -525,7 +525,7 @@ typedef struct SceNetAdhocMatchingContext { // End of psp definitions -const size_t MAX_ADHOCCTL_HANDLERS = 32; //4 +const size_t MAX_ADHOCCTL_HANDLERS = 4; // pspautotests net/adhoc/adhocctl const size_t MAX_MATCHING_HANDLERS = 32; //4 enum { diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index 76c64eb848..511eaa976f 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -1049,7 +1049,7 @@ static int sceNetInit(MIPSState *mips, u32 poolSize, u32 calloutPri, u32 callout INFO_LOG(Log::sceNet, "sceNetInit(poolsize=%d, calloutpri=%i, calloutstack=%d, netintrpri=%i, netintrstack=%d) at %08x", poolSize, calloutPri, calloutStack, netinitPri, netinitStack, mips->pc); - netMallocStat.pool = poolSize - 0x20; // On Vantage Master Portable this is slightly (32 bytes) smaller than the poolSize arg when tested with JPCSP + prx files + netMallocStat.pool = poolSize - 0x20; // 0x1FFE0 for a 0x20000 pool, as on the PSP (pspautotests net/inet/offline) netMallocStat.maximum = 0x4050; // Dummy maximum foot print netMallocStat.free = netMallocStat.pool; // Dummy free size, we should set this high enough to prevent any issue (ie. Vantage Master Portable), this is probably the only field being checked by games? @@ -1065,12 +1065,24 @@ static int sceNetInit(MIPSState *mips, u32 poolSize, u32 calloutPri, u32 callout // Free(delete) thread info / data. // Normal usage: sceKernelDeleteThread followed by sceNetFreeThreadInfo with the same threadID as argument +// Both take any thread, also the current one or one that has ended (pspautotests net/inet/offline). +static bool IsThreadID(SceUID thid) { + int type = 0; + return thid != 0 && kernelObjects.GetIDType(thid, &type) && type == SCE_KERNEL_TMID_Thread; +} + static int sceNetFreeThreadinfo(SceUID thid) { + if (!IsThreadID(thid)) { + return hleLogError(Log::sceNet, 0x80410104, "not a thread"); + } return hleLogError(Log::sceNet, 0, "UNIMPL"); } // Abort a thread. static int sceNetThreadAbort(SceUID thid) { + if (!IsThreadID(thid)) { + return hleLogError(Log::sceNet, 0x80410104, "not a thread"); + } return hleLogError(Log::sceNet, 0, "UNIMPL"); } @@ -1184,7 +1196,7 @@ static void sceNetEtherStrton(u32 bufferPtr, u32 macPtr) { static int sceNetGetMallocStat(u32 statPtr) { auto stat = PSPPointer::Create(statPtr); if (!stat.IsValid()) - return hleLogError(Log::sceNet, 0, "invalid address"); + return hleLogError(Log::sceNet, 0x80410103, "invalid address"); *stat = netMallocStat; stat.NotifyWrite("sceNetGetMallocStat"); @@ -1497,6 +1509,10 @@ static int NetApctl_AddHandler(u32 handlerPtr, u32 handlerArg) { // TODO: Should we allow the same handler to be added more than once? static u32 sceNetApctlAddHandler(u32 handlerPtr, u32 handlerArg) { INFO_LOG(Log::sceNet, "%s(%08x, %08x)", __FUNCTION__, handlerPtr, handlerArg); + // A game gets two (pspautotests net/inet/offline). + if (apctlHandlers.size() >= MAX_APCTL_GAME_HANDLERS) { + return hleLogError(Log::sceNet, 0x80410A0F, "too many handlers"); + } return NetApctl_AddHandler(handlerPtr, handlerArg); } @@ -1513,6 +1529,14 @@ static int NetApctl_DelHandler(u32 handlerID) { static int sceNetApctlDelHandler(u32 handlerID) { INFO_LOG(Log::sceNet, "%s(%d)", __FUNCTION__, handlerID); + // An ID that can't be a handler's (100, -1) and a free one give different errors + // (pspautotests net/inet/offline). Where the range ends is inferred from the handler limit. + if (handlerID >= MAX_APCTL_GAME_HANDLERS) { + return hleLogError(Log::sceNet, 0x80410A82, "bad handler id"); + } + if (apctlHandlers.find(handlerID) == apctlHandlers.end()) { + return hleLogError(Log::sceNet, SCE_NET_APCTL_ERROR_INVALID_ID, "no such handler"); + } return NetApctl_DelHandler(handlerID); } @@ -1575,7 +1599,7 @@ static int sceNetApctlGetState(u32 pStateAddr) { return hleLogDebug(Log::sceNet, 0); } - return hleLogError(Log::sceNet, -1, "apctl invalid arg"); // 0x8002013A or ERROR_NET_WLAN_INVALID_ARG ? + return hleLogError(Log::sceNet, 0x80410A03, "apctl invalid arg"); // pspautotests net/inet/offline } // This one logs like a syscall because it's called at the end of some. @@ -1601,8 +1625,9 @@ int NetApctl_GetBSSDescIDListUser(u32 sizeAddr, u32 bufAddr) { const int userInfoSize = 8; // 8 bytes per entry (next address + entry id) // Faking 4 entries, games like MGS:PW Recruit will need to have a different AP for each entry int entries = 4; - if (!Memory::IsValid4AlignedAddress(sizeAddr) || !Memory::IsValidAddress(bufAddr)) - return hleLogError(Log::sceNet, -1, "apctl invalid arg"); // 0x8002013A or ERROR_NET_WLAN_INVALID_ARG ? + // A NULL buffer asks for the size (pspautotests net/inet/offline). + if (!Memory::IsValid4AlignedAddress(sizeAddr) || (bufAddr != 0 && !Memory::IsValidAddress(bufAddr))) + return hleLogError(Log::sceNet, 0x80410A03, "apctl invalid arg"); int size = Memory::ReadUnchecked_U32(sizeAddr); // Return size required @@ -1748,16 +1773,14 @@ static int sceNetApctlDelInternalHandler(u32 handlerID) { static int sceNetApctlAddInternal03Handler(u32 handlerPtr, u32 handlerArg) { ERROR_LOG(Log::sceNet, "UNIMPL %s(%08x, %08x)", __FUNCTION__, handlerPtr, handlerArg); - // This seems to be a 3rd kind of handler - // Simple forward, don't need to use hleCall - return sceNetApctlAddHandler(handlerPtr, handlerArg); + // This seems to be a 3rd kind of handler. The game handler limit doesn't apply. + return NetApctl_AddHandler(handlerPtr, handlerArg); } static int sceNetApctlDelInternal03Handler(u32 handlerID) { ERROR_LOG(Log::sceNet, "UNIMPL %s(%i)", __FUNCTION__, handlerID); // This seems to be a 3rd kind of handler - // Simple forward, don't need to use hleCall - return sceNetApctlDelHandler(handlerID); + return NetApctl_DelHandler(handlerID); } static int sceNetApctl_lib2_69745F0A(int handlerId) { diff --git a/Core/HLE/sceNet.h b/Core/HLE/sceNet.h index 8851f219b4..5418efa2fb 100644 --- a/Core/HLE/sceNet.h +++ b/Core/HLE/sceNet.h @@ -38,6 +38,7 @@ // TODO: Determine how many handlers we can actually have const size_t MAX_APCTL_HANDLERS = 32; +const size_t MAX_APCTL_GAME_HANDLERS = 2; // Through sceNetApctlAddHandler (pspautotests net/inet/offline) typedef void(*sceNetApctlHandler)(int oldState, int newState, int event, int error, void* pArg); diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 297a2206bc..9d93e54e02 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -2125,6 +2125,19 @@ int sceNetAdhocctlInit(MIPSState *mips, int stackSize, int prio, u32 productAddr return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_ALREADY_INITIALIZED); } + // pspautotests net/adhoc/adhocctl. The priority error comes from creating the library's thread, + // which the firmware does as a kernel caller (threads/threads/kernel/priority). + if (stackSize < 0xC00) { + return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_STACKSIZE_TOO_SHORT, "stack too small"); + } + if (prio < 0x01 || prio > 0x7E) { + return hleLogError(Log::sceNet, SCE_KERNEL_ERROR_ILLEGAL_PRIORITY, "bad priority"); + } + const auto adhocId = PSPPointer::Create(productAddr); + if (!adhocId.IsValid() || adhocId->type < 0 || adhocId->type > 2) { + return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_INVALID_ARG, "bad adhoc id"); + } + if (g_Config.bEnableWlan) { // Needed for peers and ad hoc servers on the same LAN (Android 17+). System_RequestLocalNetworkPermission(); @@ -2224,6 +2237,16 @@ static int pdp_create_postoffice(const SceNetEtherAddr *saddr, int sport, int bu return i + 1; } +// Out of socket IDs is its own error (pspautotests net/adhoc/limits). +static bool HasFreeAdhocSocketSlot() { + for (int i = 0; i < MAX_SOCKET; i++) { + if (adhocSockets[i] == NULL) { + return true; + } + } + return false; +} + int NetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 flag, bool postoffice_enet_pdp_reliable) { if (!g_Config.bEnableWlan) { return -1; @@ -2237,11 +2260,31 @@ int NetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 flag, bool return SCE_NET_ADHOC_ERROR_NOT_INITIALIZED; } - if (mac == NULL || bufferSize <= 0) { + // Checked in this order on the PSP (pspautotests net/adhoc/pdp). Any buffer size is accepted. + if (mac == NULL || flag != 0) { DEBUG_LOG(Log::sceNet, "%s: bad arg", __func__); return SCE_NET_ADHOC_ERROR_INVALID_ARG; } + const SceNetEtherAddr *requestedMac = (const SceNetEtherAddr *)mac; + if (adhocctlCurrentMode == ADHOCCTL_MODE_NONE || isZeroMAC(requestedMac) || (requestedMac->data[0] & 1) != 0) { + DEBUG_LOG(Log::sceNet, "%s: invalid mac address supplied", __func__); + return SCE_NET_ADHOC_ERROR_INVALID_ADDR; + } + + if (port == 0xFFFF) { + return SCE_NET_ADHOC_ERROR_INVALID_PORT; + } + + // What the PSP does with a size under 1 isn't known; give the host one datagram's worth. + if (bufferSize <= 0) { + bufferSize = PSP_ADHOC_PDP_MFS; + } + + if (!HasFreeAdhocSocketSlot()) { + return SCE_NET_ADHOC_ERROR_SOCKET_ID_NOT_AVAIL; + } + if (isPDPPortInUse(port)) { // FIXME: When PORT_IN_USE error occured it seems the index to the socket id also increased, which means it tries to create & bind the socket first and then closes it due to failed to bind DEBUG_LOG(Log::sceNet, "%s: port in-use", __func__); @@ -2428,6 +2471,11 @@ static int sceNetAdhocctlGetParameter(u32 paramAddr) { return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_NOT_INITIALIZED); } + // Only in a group (pspautotests net/adhoc/adhocctl). + if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED) { + return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_DISCONNECTED, "not in a group"); + } + auto ptr = PSPPointer::Create(paramAddr); if (!ptr.IsValid()) return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_INVALID_ARG); @@ -2460,6 +2508,28 @@ int sceNetAdhocPdpSend(MIPSState *mips, int id, const char *mac, u32 port, void SceNetEtherAddr * daddr = (SceNetEtherAddr *)mac; uint16_t dport = (uint16_t)port; + // Checked in this order on the PSP (pspautotests net/adhoc/pdp). + if (daddr == NULL || (flag & ~ADHOC_F_NONBLOCK) != 0) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ARG, "invalid arg"); + } + if (!netAdhocInited) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_NOT_INITIALIZED, "not initialized"); + } + if (id <= 0 || id > MAX_SOCKET || adhocSockets[id - 1] == NULL) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_SOCKET_ID, "invalid socket id"); + } + // Broadcast is the only multicast address allowed. + if (isZeroMAC(daddr) || ((daddr->data[0] & 1) != 0 && !isBroadcastMAC(daddr))) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ADDR, "invalid address"); + } + if (dport == 0 || dport == 0xFFFF) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_PORT, "invalid port"); + } + // NULL data is only accepted with a length of 0. + if (len < 0 || len > PSP_ADHOC_PDP_MTU || (data == NULL && len != 0)) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_DATALEN, "invalid data length"); + } + //if (dport < 7) dport += 1341; // Really should flatten this with early outs, all this indentation is making me dizzy. @@ -2478,7 +2548,7 @@ int sceNetAdhocPdpSend(MIPSState *mips, int id, const char *mac, u32 port, void socket->nonblocking = flag; // Valid Data Buffer - if (data != NULL) { + if (data != NULL || len == 0) { // Valid Destination Address if (daddr != NULL && !isZeroMAC(daddr)) { // Log Destination @@ -2556,7 +2626,7 @@ int sceNetAdhocPdpSend(MIPSState *mips, int id, const char *mac, u32 port, void DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpSend[%i:%u]: Sent %u bytes to %s:%u\n", id, getLocalPort(pdpsocket.id), sent, ip2str(target.sin_addr).c_str(), ntohs(target.sin_port)); // Success - return 0; // sent; // MotorStorm will try to resend if return value is not 0 + return 0; // 0, not the length, as on the PSP (pspautotests net/adhoc/pdp). MotorStorm resends otherwise. } // Non-Blocking @@ -2566,8 +2636,8 @@ int sceNetAdhocPdpSend(MIPSState *mips, int id, const char *mac, u32 port, void // Does PDP can Timeout? There is no concept of Timeout when sending UDP due to no ACK, but might happen if the socket buffer is full, not sure about PDP since some games did use the timeout arg return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_TIMEOUT, "timeout?"); // SCE_NET_ADHOC_ERROR_INVALID_ADDR; } - VERBOSE_LOG(Log::sceNet, "sceNetAdhocPdpSend[%i:%u]: Unknown Target Peer %s:%u (faking success)\n", id, getLocalPort(pdpsocket.id), mac2str(daddr).c_str(), ntohs(target.sin_port)); - return 0; // faking success + VERBOSE_LOG(Log::sceNet, "sceNetAdhocPdpSend[%i:%u]: Unknown Target Peer %s:%u (nobody there)\n", id, getLocalPort(pdpsocket.id), mac2str(daddr).c_str(), ntohs(target.sin_port)); + return 0; // The PSP sends it into the air and returns 0 (pspautotests net/adhoc/pdp). } // Broadcast Target @@ -2712,6 +2782,29 @@ int sceNetAdhocPdpRecv(MIPSState *mips, int id, void *addr, void * port, void *b SceNetEtherAddr *saddr = (SceNetEtherAddr *)addr; uint16_t * sport = (uint16_t *)port; //Looking at Quake3 sourcecode (net_adhoc.c) this is an "int" (32bit) but changing here to 32bit will cause FF-Type0 to see duplicated Host (thinking it was from a different host) int * len = (int *)dataLength; + + // Checked in this order on the PSP (pspautotests net/adhoc/pdp). + if (buf == NULL || len == NULL || (flag & ~ADHOC_F_NONBLOCK) != 0) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ARG, "invalid arg"); + } + if (!netAdhocInited) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_NOT_INITIALIZED, "not initialized"); + } + if (id <= 0 || id > MAX_SOCKET || adhocSockets[id - 1] == NULL) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_SOCKET_ID, "invalid socket id"); + } + // The sender's address and port are optional. A blocking receive writes them later, so these + // can't be locals. + static SceNetEtherAddr discardedMac; + static uint16_t discardedPort; + if (saddr == NULL) { + saddr = &discardedMac; + } + if (sport == NULL) { + sport = &discardedPort; + port = sport; + } + if (netAdhocInited) { // Valid Socket ID if (id > 0 && id <= MAX_SOCKET && adhocSockets[id - 1] != NULL) { @@ -2882,7 +2975,7 @@ int sceNetAdhocPdpRecv(MIPSState *mips, int id, void *addr, void * port, void *b *sport = ntohs(sin.sin_port) - portOffset; // Save Length - *len = received; // Kurok homebrew seems to use the new value of len than returned value as data length + *len = received; // The PSP writes the received length back (pspautotests net/adhoc/pdp). // Update last recv timestamp, may cause disconnection not detected properly tho peerlock.lock(); @@ -2894,7 +2987,7 @@ int sceNetAdhocPdpRecv(MIPSState *mips, int id, void *addr, void * port, void *b //_freeNetworkLock(); // Return Success. According to pspsdk-1.0+beta2 returned value is Number of bytes received, but JPCSP returning 0? - return 0; //received; // Returning number of bytes received will cause KH BBS unable to see the game event/room + return 0; // 0, not the length, as on the PSP (pspautotests net/adhoc/pdp). KH BBS can't see rooms otherwise. } // Free Network Lock @@ -3152,6 +3245,10 @@ int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock int sceNetAdhocPollSocket(MIPSState *mips, u32 socketStructAddr, int count, int timeout, int nonblock) { // timeout in microseconds DEBUG_LOG_REPORT_ONCE(sceNetAdhocPollSocket, Log::sceNet, "UNTESTED sceNetAdhocPollSocket(%08x, %i, %i, %i) at %08x", socketStructAddr, count, timeout, nonblock, mips->pc); + // pspautotests net/adhoc/poll + if ((nonblock & ~ADHOC_F_NONBLOCK) != 0) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ARG, "invalid flag"); + } // Library is initialized if (netAdhocInited) { @@ -3272,8 +3369,8 @@ int NetAdhocPdp_Delete(int id, int unknown) { return SCE_NET_ADHOC_ERROR_INVALID_SOCKET_ID; } - // Invalid Argument - return SCE_NET_ADHOC_ERROR_INVALID_ARG; + // Out of range (pspautotests net/adhoc/pdp) + return SCE_NET_ADHOC_ERROR_INVALID_SOCKET_ID; } // Library is uninitialized @@ -3289,6 +3386,10 @@ int NetAdhocPdp_Delete(int id, int unknown) { static int sceNetAdhocPdpDelete(MIPSState *mips, int id, int unknown) { // WLAN might be disabled in the middle of successfull multiplayer, but we still need to cleanup right? INFO_LOG(Log::sceNet, "sceNetAdhocPdpDelete(%d, %d) at %08x", id, unknown, mips->pc); + // The flag must be 0, checked first (pspautotests net/adhoc/pdp). + if (unknown != 0) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ARG, "invalid flag"); + } /* if (!g_Config.bEnableWlan) { return 0; @@ -3326,7 +3427,8 @@ int sceNetAdhocctlScan(MIPSState *mips) { int us = adhocDefaultDelay; // FIXME: When tested with JPCSP + official prx files it seems when adhocctl in a connected state (ie. joined to a group) attempting to create/connect/join/scan will return a success (without doing anything?) if ((adhocctlState == ADHOCCTL_STATE_CONNECTED) || (adhocctlState == ADHOCCTL_STATE_GAMEMODE)) { - // TODO: Valhalla Knights 2 need handler notification, but need to test this on games that doesn't use Adhocctl Handler too (not sure if there are games like that tho) + // Returns 0 and reports ALREADY_CONNECTED through the handlers, as on the PSP (pspautotests + // net/adhoc/adhocctl). Valhalla Knights 2 needs the event. notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, SCE_NET_ADHOCCTL_ERROR_ALREADY_CONNECTED); hleEatMicro(500); return hleLogDebug(Log::sceNet, 0); @@ -3476,7 +3578,12 @@ int sceNetAdhocctlGetScanInfo(MIPSState *mips, u32 sizeAddr, u32 bufAddr) { // TODO: Should we allow the same handler to be added more than once? // FIXME: Do all Adhocctl HLE returning 0 and expecting error code through callback handler if there were error, instead of returning error code through the HLE ? static u32 sceNetAdhocctlAddHandler(u32 handlerPtr, u32 handlerArg) { - bool foundHandler = false; + if (!netAdhocctlInited) { + return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_NOT_INITIALIZED, "adhocctl not initialized"); + } + + // The same function can be added more than once, and gets a new ID each time (pspautotests + // net/adhoc/adhocctl). u32 retval = 0; AdhocctlHandler handler; memset(&handler, 0, sizeof(handler)); @@ -3487,14 +3594,7 @@ static u32 sceNetAdhocctlAddHandler(u32 handlerPtr, u32 handlerArg) { handler.entryPoint = handlerPtr; handler.argument = handlerArg; - for (auto it = adhocctlHandlers.begin(); it != adhocctlHandlers.end(); ++it) { - if (it->second.entryPoint == handlerPtr) { - foundHandler = true; - break; - } - } - - if (!foundHandler && Memory::IsValidAddress(handlerPtr)) { + if (Memory::IsValidAddress(handlerPtr)) { if (adhocctlHandlers.size() >= MAX_ADHOCCTL_HANDLERS) { ERROR_LOG(Log::sceNet, "UNTESTED sceNetAdhocctlAddHandler(%x, %x): Too many handlers", handlerPtr, handlerArg); retval = SCE_NET_ADHOCCTL_ERROR_TOO_MANY_HANDLERS; @@ -3502,9 +3602,6 @@ static u32 sceNetAdhocctlAddHandler(u32 handlerPtr, u32 handlerArg) { } adhocctlHandlers[retval] = handler; INFO_LOG(Log::sceNet, "UNTESTED sceNetAdhocctlAddHandler(%x, %x): added handler %d", handlerPtr, handlerArg, retval); - } else if(foundHandler) { - ERROR_LOG(Log::sceNet, "UNTESTED sceNetAdhocctlAddHandler(%x, %x): Same handler already exists", handlerPtr, handlerArg); - retval = 0; //Faking success } else { ERROR_LOG(Log::sceNet, "UNTESTED sceNetAdhocctlAddHandler(%x, %x): Invalid handler", handlerPtr, handlerArg); retval = SCE_NET_ADHOCCTL_ERROR_INVALID_ARG; @@ -3615,7 +3712,7 @@ static u32 sceNetAdhocctlDelHandler(u32 handlerID) { adhocctlHandlers.erase(handlerID); return hleLogInfo(Log::sceNet, 0); } else { - return hleLogWarning(Log::sceNet, 0, "Invalid Handler ID"); + return hleLogWarning(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_ID_NOT_FOUND, "Invalid Handler ID"); } } @@ -3872,7 +3969,7 @@ int NetAdhocctl_Create(const char *groupName) { } // Invalid Argument - return SCE_NET_ADHOC_ERROR_INVALID_ARG; + return SCE_NET_ADHOCCTL_ERROR_INVALID_ARG; } // Library uninitialized return SCE_NET_ADHOCCTL_ERROR_NOT_INITIALIZED; @@ -3949,7 +4046,7 @@ int NetAdhocctl_CreateEnterGameMode(const char* group_name, int game_type, int n if (!netAdhocctlInited) return SCE_NET_ADHOCCTL_ERROR_NOT_INITIALIZED; - if (!Memory::IsValidAddress(membersAddr)) + if (group_name == NULL || !Memory::IsValidAddress(membersAddr)) return SCE_NET_ADHOCCTL_ERROR_INVALID_ARG; if (game_type < ADHOCCTL_GAMETYPE_1A || game_type > ADHOCCTL_GAMETYPE_2A || num_members < 2 || num_members > 16 || (game_type == ADHOCCTL_GAMETYPE_1A && num_members > 4)) @@ -4025,7 +4122,7 @@ static int sceNetAdhocctlJoinEnterGameMode(MIPSState *mips, const char * group_n if (!netAdhocctlInited) return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_NOT_INITIALIZED, "not initialized"); - if (!hostMac) + if (!group_name || !hostMac) return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_INVALID_ARG, "invalid arg"); deleteAllGMB(); @@ -4063,7 +4160,10 @@ int sceNetAdhocctlCreateEnterGameModeMin(MIPSState *mips, const char *group_name if (group_name) memcpy(grpName, group_name, ADHOCCTL_GROUPNAME_LEN); // For logging purpose, must not be truncated WARN_LOG_REPORT_ONCE(sceNetAdhocctlCreateEnterGameModeMin, Log::sceNet, "UNTESTED sceNetAdhocctlCreateEnterGameModeMin(%s, %i, %i, %i, %08x, %d, %i) at %08x", grpName, game_type, min_members, num_members, membersAddr, timeout, flag, mips->pc); - // We don't really need the Minimum User Check + // The minimum isn't enforced while waiting, but it is checked (pspautotests net/adhoc/gamemode). + if (min_members < 2 || min_members > num_members) { + return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_INVALID_ARG, "bad minimum"); + } return hleLogDebug(Log::sceNet, NetAdhocctl_CreateEnterGameMode(group_name, game_type, num_members, membersAddr, timeout, flag)); } @@ -4493,7 +4593,8 @@ static int ptp_open_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, con *slot = internal; INFO_LOG(Log::sceNet, "%s: created ptp socket with id %d", __func__, i + 1); - // Initiate PtpConnect (ie. The Warriors seems to try to PtpSend right after PtpOpen without trying to PtpConnect first) + // PtpOpen starts connecting by itself: on the PSP the client is ESTABLISHED right after it (pspautotests + // net/adhoc/ptp). The Warriors sends without calling PtpConnect. NetAdhocPtp_Connect(i + 1, 0, 1, false); return i + 1; @@ -4519,6 +4620,25 @@ static int sceNetAdhocPtpOpen(MIPSState *mips, const char *srcmac, int sport, co SceNetEtherAddr* saddr = (SceNetEtherAddr*)srcmac; SceNetEtherAddr* daddr = (SceNetEtherAddr*)dstmac; bool isClient = false; + + // pspautotests net/adhoc/ptp. Zero sizes and retransmit settings are accepted. + if (saddr == NULL || daddr == NULL || flag != 0) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ARG, "invalid arg"); + } + if (netAdhocInited && (isZeroMAC(saddr) || (saddr->data[0] & 1) != 0 || isZeroMAC(daddr))) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ADDR, "invalid address"); + } + if (netAdhocInited && (dport == 0 || dport == 0xFFFF)) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_PORT, "invalid port"); + } + // What the PSP does with a buffer size under 1 isn't known; give the host one segment's worth. + if (bufsize <= 0) { + bufsize = PSP_ADHOC_PTP_MSS; + } + if (netAdhocInited && !HasFreeAdhocSocketSlot()) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_SOCKET_ID_NOT_AVAIL, "out of socket IDs"); + } + // Library is initialized if (netAdhocInited) { // Some games (ie. DBZ Shin Budokai 2) might be getting the saddr/srcmac content from SaveState and causing problems if current MAC is different :( So we try to fix it here @@ -4541,7 +4661,7 @@ static int sceNetAdhocPtpOpen(MIPSState *mips, const char *srcmac, int sport, co } // Valid Arguments - if (bufsize > 0 && rexmt_int > 0 && rexmt_cnt > 0) { + if (bufsize > 0 && rexmt_int >= 0 && rexmt_cnt >= 0) { if (serverHasRelay) return ptp_open_postoffice(saddr, sport, daddr, dport, bufsize); @@ -4645,7 +4765,7 @@ static int sceNetAdhocPtpOpen(MIPSState *mips, const char *srcmac, int sport, co // Switch to non-blocking for futher usage changeBlockingMode(tcpsocket, 1); - // Initiate PtpConnect (ie. The Warriors seems to try to PtpSend right after PtpOpen without trying to PtpConnect first) + // PtpOpen starts connecting by itself, as on the PSP (see ptp_open_postoffice). // TODO: Need to handle ECONNREFUSED better on non-Windows, if there are games that never called PtpConnect and only relies on [blocking?] PtpOpen to get connected NetAdhocPtp_Connect(i + 1, rexmt_int, 1, false); @@ -4829,7 +4949,8 @@ static int sceNetAdhocPtpAccept(MIPSState *mips, int id, u32 peerMacAddrPtr, u32 // Library is initialized if (netAdhocInited) { - // TODO: Validate Arguments. GTA:VCS seems to use 0/null for the peerPortPtr, and Bomberman Panic Bomber is using null/0 on both peerMacAddrPtr & peerPortPtr, so i guess it's optional. + // The peer's address and port are both optional (pspautotests net/adhoc/alerts passes NULL for + // both). GTA: VCS and Bomberman Panic Bomber leave them out. if (true) { // FIXME: Not sure what kind of arguments considered as invalid (need to be tested on a homebrew), might be the flag? // Valid Socket if (id > 0 && id <= MAX_SOCKET && adhocSockets[id - 1] != NULL) { @@ -4933,7 +5054,8 @@ int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_SOCKET_ALERTED, "socket alerted"); } - // Phantasy Star Portable 2 will try to reconnect even when previous connect already success, so we should return success too if it's already connected + // Connecting a connected socket returns 0 on the PSP (pspautotests net/adhoc/alerts). Phantasy Star + // Portable 2 does that. if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED) return 0; @@ -5120,8 +5242,8 @@ int NetAdhocPtp_Close(int id, int unknown) { return SCE_NET_ADHOC_ERROR_INVALID_SOCKET_ID; } - // Invalid Argument - return SCE_NET_ADHOC_ERROR_INVALID_ARG; + // Out of range (pspautotests net/adhoc/ptp) + return SCE_NET_ADHOC_ERROR_INVALID_SOCKET_ID; } // Library is uninitialized @@ -5136,6 +5258,10 @@ int NetAdhocPtp_Close(int id, int unknown) { */ static int sceNetAdhocPtpClose(MIPSState *mips, int id, int unknown) { INFO_LOG(Log::sceNet,"sceNetAdhocPtpClose(%d,%d) at %08x",id,unknown,mips->pc); + // The flag must be 0 (pspautotests net/adhoc/ptp). + if (unknown != 0) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ARG, "invalid flag"); + } /*if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); }*/ @@ -5209,6 +5335,22 @@ static int sceNetAdhocPtpListen(MIPSState *mips, const char *srcmac, int sport, // Library is initialized SceNetEtherAddr * saddr = (SceNetEtherAddr *)srcmac; bool isClient = false; + + // pspautotests net/adhoc/ptp. Zero sizes, retransmit settings and backlog are accepted. + if (saddr == NULL || flag != 0) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ARG, "invalid arg"); + } + if (netAdhocInited && (isZeroMAC(saddr) || (saddr->data[0] & 1) != 0)) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ADDR, "invalid address"); + } + // What the PSP does with a buffer size under 1 isn't known; give the host one segment's worth. + if (bufsize <= 0) { + bufsize = PSP_ADHOC_PTP_MSS; + } + if (netAdhocInited && !HasFreeAdhocSocketSlot()) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_SOCKET_ID_NOT_AVAIL, "out of socket IDs"); + } + if (netAdhocInited) { // Some games (ie. DBZ Shin Budokai 2) might be getting the saddr/srcmac content from SaveState and causing problems :( So we try to fix it here if (saddr != NULL) { @@ -5230,7 +5372,7 @@ static int sceNetAdhocPtpListen(MIPSState *mips, const char *srcmac, int sport, } // Valid Arguments - if (bufsize > 0 && rexmt_int > 0 && rexmt_cnt > 0 && backlog > 0) + if (bufsize > 0 && rexmt_int >= 0 && rexmt_cnt >= 0 && backlog >= 0) { if (serverHasRelay) return ptp_listen_postoffice(saddr, sport, bufsize); @@ -5392,6 +5534,9 @@ static int sceNetAdhocPtpSend(MIPSState *mips, int id, u32 dataAddr, u32 dataSiz int * len = dataSizeAddr ? (int *)Memory::GetPointerOrException(dataSizeAddr) : nullptr; const char * data = Memory::GetCharPointer(dataAddr); + if (len == NULL) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ARG, "invalid arg"); + } // Library is initialized if (netAdhocInited) { // Valid Socket @@ -5403,6 +5548,13 @@ static int sceNetAdhocPtpSend(MIPSState *mips, int id, u32 dataAddr, u32 dataSiz // Connected Socket if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED || ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) { + // A length of 0 sends nothing; NULL data is a length error (pspautotests net/adhoc/ptp). + if (*len == 0) { + return hleLogDebug(Log::sceNet, 0, "nothing to send"); + } + if (*len < 0 || data == NULL) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_DATALEN, "invalid data length"); + } // Valid Arguments if (data != NULL && len != NULL && *len > 0) { // Schedule Timeout Removal @@ -6122,6 +6274,10 @@ static int sceNetAdhocctlGetGameModeInfo(u32 infoAddr) { if (!Memory::IsValidAddress(infoAddr)) return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_INVALID_ARG, "invalid arg"); + // pspautotests net/adhoc/gamemode + if (!netAdhocGameModeEntered) + return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_NOT_ENTER_GAMEMODE, "not in game mode"); + SceNetAdhocctlGameModeInfo* gmInfo = (SceNetAdhocctlGameModeInfo*)Memory::GetPointerOrException(infoAddr); // Writes number of participants and each participating MAC address into infoAddr/gmInfo gmInfo->num = static_cast(gameModeMacs.size()); @@ -6154,9 +6310,15 @@ static int sceNetAdhocctlGetPeerList(MIPSState *mips, u32 sizeAddr, u32 bufAddr) // Initialized Library if (netAdhocctlInited) { + // Outside a group, an error from the auth library (pspautotests net/adhoc/adhocctl). + if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED) { + if (buflen != NULL) { + *buflen = 0; + } + return hleLogError(Log::sceNet, 0x80410682, "not in a group"); + } // Minimum Arguments if (buflen != NULL) { - // FIXME: Sometimes returing 0x80410682 when Adhocctl is still BUSY or before AdhocctlGetState became ADHOCCTL_STATE_CONNECTED or related to Auth/Library ? // Multithreading Lock peerlock.lock(); diff --git a/Core/HLE/sceNetAdhocMatching.cpp b/Core/HLE/sceNetAdhocMatching.cpp index 20e0468e74..8bfab8140a 100644 --- a/Core/HLE/sceNetAdhocMatching.cpp +++ b/Core/HLE/sceNetAdhocMatching.cpp @@ -17,6 +17,7 @@ #include "Core/HLE/sceNetAdhocMatching.h" +#include "Core/System.h" #include "Core/HLE/sceNetAdhoc.h" #include "Core/HLE/NetAdhocCommon.h" @@ -650,7 +651,9 @@ void actOnHelloPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * se DataToHexString(10, 0, (u8*)opt, optlen, &hellohex); DEBUG_LOG(Log::sceNet, "HELLO Dump (%d bytes):\n%s", optlen, hellohex.c_str()); - // Spawn Hello Event. FIXME: HELLO event should not be triggered in the middle of joining? This will cause Bleach 7 to Cancel the join request + // Spawn Hello Event. Not from a peer in the middle of joining: on the PSP a client gets no hellos from + // the host it's asking to join (pspautotests net/adhoc/pair/matching3), and Bleach 7 cancels the join + // if it gets one. spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_HELLO, sendermac, optlen, opt); } } @@ -1609,6 +1612,9 @@ int NetAdhocMatching_Stop(int matchingId) { int sceNetAdhocMatchingStop(MIPSState *mips, int matchingId) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingStop(%i) at %08x", matchingId, mips->pc); + if (!findMatchingContext(matchingId)) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "invalid id"); + } return NetAdhocMatching_Stop(matchingId); } @@ -1680,6 +1686,14 @@ int NetAdhocMatching_Delete(int matchingId) { int sceNetAdhocMatchingDelete(MIPSState *mips, int matchingId) { // WLAN might be disabled in the middle of successfull multiplayer, but we still need to cleanup right? + SceNetAdhocMatchingContext *item = findMatchingContext(matchingId); + if (!item) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "invalid id"); + } + // It has to be stopped first (pspautotests net/adhoc/matching). + if (item->running) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_IS_RUNNING, "still running"); + } NetAdhocMatching_Delete(matchingId); @@ -1756,13 +1770,19 @@ static int sceNetAdhocMatchingCreate(MIPSState *mips, int mode, int maxnum, int return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_MAXNUM, "adhoc matching invalid maxnum"); } - if (rxbuflen < 1) { + // The limits below are from pspautotests net/adhoc/matching. + if (rxbuflen < 6 * maxnum + 4) { // Invalid Receive Buffer Size return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_RXBUF_TOO_SHORT, "adhoc matching rxbuf too short"); } if (mode < 1 || mode > 3) { - // InvalidERROR_NET_Arguments + return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_MODE, "adhoc matching invalid mode"); + } + + // Only a client may leave the hello and keep-alive intervals at 0. + const bool sendsHello = mode == PSP_ADHOC_MATCHING_MODE_PARENT || mode == PSP_ADHOC_MATCHING_MODE_P2P; + if ((sendsHello && (hello_int == 0 || keepalive_int == 0)) || init_count < 0 || rexmt_int == 0) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG, "adhoc matching invalid arg"); } @@ -1774,6 +1794,15 @@ static int sceNetAdhocMatchingCreate(MIPSState *mips, int mode, int maxnum, int return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_PORT_IN_USE, "adhoc matching port in use"); } + // At most 15 contexts at a time (pspautotests net/adhoc/limits). + int contextCount = 0; + for (item = contexts; item != NULL; item = item->next) { + contextCount++; + } + if (contextCount >= 15) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_ID_NOT_AVAIL, "too many contexts"); + } + // Allocate Context Memory SceNetAdhocMatchingContext *context = new SceNetAdhocMatchingContext(); @@ -1856,8 +1885,15 @@ int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int SceNetAdhocMatchingContext* item = findMatchingContext(matchingId); if (item == NULL) { - // return SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID; //Faking success to prevent GTA:VCS from stuck unable to choose host/join menu - return hleLogDebug(Log::sceNet, 0); + // GTA: VCS gets stuck unable to choose host/join otherwise. + if (PSP_CoreParameter().compat.flags().MatchingStartUnknownIdSucceeds) { + return hleLogDebug(Log::sceNet, 0, "faking success"); + } + return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "invalid id"); + } + + if (item->running) { + return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_IS_RUNNING, "already running"); } //sceNetAdhocMatchingSetHelloOpt(matchingId, optLen, optDataAddr); //SetHelloOpt only works when context is running @@ -1921,9 +1957,10 @@ static int sceNetAdhocMatchingStart(MIPSState *mips, int matchingId, int evthPri return retval; } -// With params for Partition ID for the event & input handler stack -static int sceNetAdhocMatchingStart2(MIPSState *mips, int matchingId, int evthPri, int evthPartitionId, int evthStack, int inthPri, int inthPartitionId, int inthStack, int optLen, u32 optDataAddr) { - WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingStart2(%i, %i, %i, %i, %i, %i, %i, %i, %08x) at %08x", matchingId, evthPri, evthPartitionId, evthStack, inthPri, inthPartitionId, inthStack, optLen, optDataAddr, mips->pc); +// With params for Partition ID for the event & input handler stack. Each comes after its stack size +// (pspautotests net/adhoc/matching). +static int sceNetAdhocMatchingStart2(MIPSState *mips, int matchingId, int evthPri, int evthStack, int evthPartitionId, int inthPri, int inthStack, int inthPartitionId, int optLen, u32 optDataAddr) { + WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingStart2(%i, %i, %i, %i, %i, %i, %i, %i, %08x) at %08x", matchingId, evthPri, evthStack, evthPartitionId, inthPri, inthStack, inthPartitionId, optLen, optDataAddr, mips->pc); if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } @@ -2101,10 +2138,11 @@ int NetAdhocMatching_CancelTargetWithOpt(int matchingId, const char* macAddress, SceNetAdhocMatchingMemberInternal* peer = findPeer(context, (SceNetEtherAddr*)target); if (peer == NULL) { - // Peer not found - //return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_UNKNOWN_TARGET, "adhocmatching unknown target"); - // Faking success to prevent the game (ie. Soul Calibur) to repeatedly calling this function when the other player is disconnected - return hleLogDebug(Log::sceNet, 0); + // Soulcalibur: Broken Destiny calls this over and over when the other player has gone, unless it succeeds. + if (PSP_CoreParameter().compat.flags().MatchingCancelUnknownTargetSucceeds) { + return hleLogDebug(Log::sceNet, 0, "faking success"); + } + return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_UNKNOWN_TARGET, "adhocmatching unknown target"); } // Valid Peer Mode @@ -2173,12 +2211,17 @@ int sceNetAdhocMatchingGetHelloOpt(int matchingId, u32 optLenAddr, u32 optDataAd SceNetAdhocMatchingContext * item = findMatchingContext(matchingId); + if (item != NULL && !item->running) { + peerlock.unlock(); + return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_RUNNING, "not running"); + } + if (item != NULL) { - // Get OptData - *optlen = item->hellolen; - if ((*optlen > 0) && Memory::IsValidAddress(optDataAddr)) { + // Copies at most the game's buffer size, and leaves the length alone (pspautotests net/adhoc/matching). + const int copyLen = std::min((int)*optlen, (int)item->hellolen); + if (copyLen > 0 && Memory::IsValidRange(optDataAddr, copyLen)) { uint8_t * optdata = Memory::GetPointerWriteUnchecked(optDataAddr); - memcpy(optdata, item->hello, *optlen); + memcpy(optdata, item->hello, copyLen); } //else return SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG; } @@ -2629,7 +2672,9 @@ void __NetMatchingCallbacks() { //(int matchingId) } DEBUG_LOG(Log::sceNet, "AdhocMatching - Remaining Events: %zu", matchingEvents.size()); auto peer = findPeer(context, (SceNetEtherAddr*)Memory::GetPointerOrException(args[2])); - // Discard HELLO Events when in the middle of joining, as some games (ie. Super Pocket Tennis) might tried to join again (TODO: Need to confirm whether sceNetAdhocMatchingSelectTarget supposed to be blocking the current thread or not) + // Discard HELLO events from a peer in the middle of joining: on the PSP a client gets none from the host + // it's asking to join (pspautotests net/adhoc/pair/matching3), and Super Pocket Tennis tries to join + // again if it gets one. if (peer == NULL || (args[1] != PSP_ADHOC_MATCHING_EVENT_HELLO || (peer->state != PSP_ADHOC_MATCHING_PEER_OUTGOING_REQUEST && peer->state != PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST && peer->state != PSP_ADHOC_MATCHING_PEER_CANCEL_IN_PROGRESS))) { DEBUG_LOG(Log::sceNet, "AdhocMatchingCallback: [ID=%i][EVENT=%i][%s]", args[0], args[1], mac2str((SceNetEtherAddr *)Memory::GetPointerOrException(args[2])).c_str()); diff --git a/Core/HLE/sceNetResolver.cpp b/Core/HLE/sceNetResolver.cpp index cf2a78c903..1743b0123d 100644 --- a/Core/HLE/sceNetResolver.cpp +++ b/Core/HLE/sceNetResolver.cpp @@ -62,19 +62,23 @@ struct NetResolver { static int g_currentNetResolverId = 1; static std::unordered_map g_netResolvers; -// NOTE: It starts as true, needed for Outrun 2006. -static bool g_netResolverInitialized = true; +// Only decides what a second sceNetResolverInit returns: the other calls work without Init, also +// after Term (pspautotests net/inet/offline). Outrun 2006 relies on that. +static bool g_netResolverInitialized = false; static int sceNetResolverInit() { // Hardcoded mHostToAlias entries here have been moved to the infra-dns.json file. + if (g_netResolverInitialized) { + return hleLogError(Log::sceNet, 0x80000020, "already initialized"); + } g_netResolverInitialized = true; return hleLogInfo(Log::sceNet, 0); } -// Also called on savestate load, so go back to the boot state, where it's running. +// Also called on savestate load, so go back to the boot state. void __NetResolverShutdown() { g_netResolvers.clear(); - g_netResolverInitialized = true; + g_netResolverInitialized = false; } static int sceNetResolverTerm() { @@ -85,6 +89,17 @@ static int sceNetResolverTerm() { // TODO: Add DoState, store the ID gen and active resolvers. +// Checked after the resolver ID (pspautotests net/inet/offline). +static int CheckNtoAArgs(u32 hostnamePtr, u32 inAddrPtr) { + if (!Memory::IsValidAddress(hostnamePtr) || !Memory::IsValidRange(inAddrPtr, 4)) { + return SCE_NET_RESOLVER_ERROR_INVALID_PTR; + } + if (Memory::ReadUnchecked_U8(hostnamePtr) == 0) { + return 0x80410418; // An empty name. + } + return 0; +} + // Note: timeouts are in seconds static int NetResolver_StartNtoA(NetResolver *resolver, u32 hostnamePtr, u32 inAddrPtr, int timeout, int retry) { std::string hostname = std::string(safe_string(Memory::GetCharPointer(hostnamePtr))); @@ -124,15 +139,15 @@ static int NetResolver_StartNtoA(NetResolver *resolver, u32 hostnamePtr, u32 inA } static int sceNetResolverStartNtoA(int resolverId, u32 hostnamePtr, u32 inAddrPtr, int timeout, int retry) { - if (!g_netResolverInitialized) { - return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_STOPPED, "Resolver subsystem not running"); - } - auto iter = g_netResolvers.find(resolverId); if (iter == g_netResolvers.end()) { return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_BAD_ID, "Bad Resolver Id: %i", resolverId); } + if (int error = CheckNtoAArgs(hostnamePtr, inAddrPtr)) { + return hleLogError(Log::sceNet, error); + } + for (int attempt = 0; attempt < retry; ++attempt) { const int status = NetResolver_StartNtoA(&iter->second, hostnamePtr, inAddrPtr, timeout, retry); if (status >= 0) { @@ -143,22 +158,26 @@ static int sceNetResolverStartNtoA(int resolverId, u32 hostnamePtr, u32 inAddrPt } static int sceNetResolverStartNtoAAsync(int resolverId, u32 hostnamePtr, u32 inAddrPtr, int timeout, int retry) { - if (!g_netResolverInitialized) { - return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_STOPPED, "Resolver subsystem not running"); - } - auto iter = g_netResolvers.find(resolverId); if (iter == g_netResolvers.end()) { return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_BAD_ID, "Bad Resolver Id: %i", resolverId); } + if (int error = CheckNtoAArgs(hostnamePtr, inAddrPtr)) { + return hleLogError(Log::sceNet, error); + } + const int status = NetResolver_StartNtoA(&iter->second, hostnamePtr, inAddrPtr, timeout, retry); return hleLogDebugOrError(Log::sceNet, status); } static int sceNetResolverPollAsync(int resolverId, u32 unknown) { - if (!g_netResolverInitialized) { - return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_STOPPED, "Resolver subsystem not running"); + // pspautotests net/inet/offline + if (g_netResolvers.find(resolverId) == g_netResolvers.end()) { + return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_BAD_ID, "Bad Resolver Id: %i", resolverId); + } + if (!Memory::IsValidRange(unknown, 4)) { + return hleLogError(Log::sceNet, 0x80410419, "NULL status"); } // TODO: Implement after confirming that this returns the state of resolver.isRunning @@ -166,8 +185,8 @@ static int sceNetResolverPollAsync(int resolverId, u32 unknown) { } static int sceNetResolverWaitAsync(int resolverId, u32 unknown) { - if (!g_netResolverInitialized) { - return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_STOPPED, "Resolver subsystem not running"); + if (g_netResolvers.find(resolverId) == g_netResolvers.end()) { + return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_BAD_ID, "Bad Resolver Id: %i", resolverId); } // TODO: Implement after confirming that this blocks current thread until resolver.isRunning flips to false @@ -175,17 +194,11 @@ static int sceNetResolverWaitAsync(int resolverId, u32 unknown) { } static int sceNetResolverStartAtoN(int resolverId, u32 inAddr, u32 hostnamePtr, int hostnameLength, int timeout, int retry) { - if (!g_netResolverInitialized) { - return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_STOPPED, "Resolver subsystem not running"); - } // TODO: Implement via getnameinfo return hleLogError(Log::sceNet, 0, "UNIMPL"); } static int sceNetResolverStartAtoNAsync(int resolverId, u32 inAddr, u32 hostnamePtr, int hostnameLength, int timeout, int retry) { - if (!g_netResolverInitialized) { - return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_STOPPED, "Resolver subsystem not running"); - } return hleLogError(Log::sceNet, 0, "UNIMPL"); } @@ -193,10 +206,13 @@ static int sceNetResolverCreate(u32 resolverIdPtr, u32 bufferPtr, int bufferLen) if (!Memory::IsValidRange(resolverIdPtr, 4)) return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_INVALID_PTR, "Invalid Ptr: %08x", resolverIdPtr); - if (Memory::IsValidRange(bufferPtr, 4) && bufferLen < 1) + // A buffer is required (pspautotests net/inet/offline). + if (!Memory::IsValidAddress(bufferPtr)) + return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_INVALID_PTR, "Invalid buffer: %08x", bufferPtr); + + if (bufferLen < 1) return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_INVALID_BUFLEN, "Invalid Buffer Length: %i", bufferLen); - // Outrun calls this without init. Something else must have initialized it, unclear what. // TODO: Consider using SceUidManager instead of this 1-indexed id - not sure what the PSP does. Actually, probably not using SceUID. // TODO: Implement SCE_NET_RESOLVER_ERROR_ID_MAX (possibly 32?) @@ -213,17 +229,14 @@ static int sceNetResolverCreate(u32 resolverIdPtr, u32 bufferPtr, int bufferLen) } static int sceNetResolverStop(u32 resolverId) { - if (!g_netResolverInitialized) { - return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_STOPPED, "Resolver subsystem not running"); - } - const auto resolverIter = g_netResolvers.find(resolverId); if (resolverIter == g_netResolvers.end()) { return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_BAD_ID, "Bad Resolver Id: %i", resolverId); } - if (resolverIter->second.isRunning) { + // Nothing to stop (pspautotests net/inet/offline). + if (!resolverIter->second.isRunning) { return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_ALREADY_STOPPED, "Resolver Already Stopped (Id: %i)", resolverId); } @@ -232,10 +245,6 @@ static int sceNetResolverStop(u32 resolverId) { } static int sceNetResolverDelete(u32 resolverId) { - if (!g_netResolverInitialized) { - return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_STOPPED, "Resolver subsystem not running"); - } - const auto resolverIter = g_netResolvers.find(resolverId); if (resolverIter == g_netResolvers.end()) { return hleLogError(Log::sceNet, SCE_NET_RESOLVER_ERROR_BAD_ID, "Bad Resolver Id: %i", resolverId); diff --git a/assets/compat.ini b/assets/compat.ini index 67f4143157..13ec14f43d 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -2276,3 +2276,23 @@ POPS00000 = true # are moved back half a pixel, which draws the same at 1x. Above 1x they otherwise cover half a pixel more # and read their neighbours' texels: seams between tiles. The PS1 emulator draws its sprites like that. POPS00000 = true + +[MatchingCancelUnknownTargetSucceeds] +# sceNetAdhocMatchingCancelTarget(WithOpt) on a MAC that isn't a peer succeeds instead of failing with +# UNKNOWN_TARGET, as the PSP does. Soulcalibur: Broken Destiny calls it over and over once the other +# player has gone, unless it succeeds. +ULUS10457 = true +ULES01298 = true +ULJS00202 = true + +[MatchingStartUnknownIdSucceeds] +# sceNetAdhocMatchingStart on a context ID that doesn't exist succeeds instead of failing with INVALID_ID, +# as the PSP does. GTA: Vice City Stories gets stuck unable to choose host or join otherwise. +ULUS10160 = true +ULES00502 = true +ULES00503 = true +ULJM05297 = true +ULJM05395 = true +ULJM05884 = true +NPJH50827 = true +ULET00417 = true