diff --git a/Makefile.port b/Makefile.port index 85e4bee9c..1c0adafbd 100644 --- a/Makefile.port +++ b/Makefile.port @@ -305,6 +305,9 @@ $(G_DIR)/tiles/%.h: $(A_DIR)/tiles/%.json $(BIN): $(O_FILES) $(LD) $(COMMON_CFLAGS) $(OPT_LVL) -o $(BIN) $^ $(LDFLAGS) +ifneq ($(CV2PDB),) + $(CV2PDB) $(BIN) +endif $(B_DIR)/%.o: %.c @mkdir -p $(dir $@) diff --git a/port/include/net/net.h b/port/include/net/net.h index db0e02616..4fe583b70 100644 --- a/port/include/net/net.h +++ b/port/include/net/net.h @@ -5,7 +5,9 @@ #include "constants.h" #include "net/netbuf.h" -#define NET_PROTOCOL_VER 9 +#define NET_PROTOCOL_VER 10 + +#define NET_QUERY_MAGIC "PDQM\x01" #define NET_MAX_CLIENTS MAX_PLAYERS #define NET_MAX_NAME MAX_PLAYERNAME @@ -105,6 +107,9 @@ struct netclient { extern s32 g_NetMode; +extern s32 g_NetJoinLatch; +extern s32 g_NetHostLatch; + // net frame, ticks at 60 fps, starts at 0 when the server is started extern u32 g_NetTick; extern u32 g_NetNextSyncId; @@ -145,6 +150,8 @@ void netServerStageStart(void); void netServerStageEnd(void); void netServerKick(struct netclient *cl, const u32 reason); +struct netclient *netClientForPlayerNum(s32 playernum); + void netClientSyncRng(void); void netClientSettingsChanged(void); diff --git a/port/include/system.h b/port/include/system.h index 9c2721bb5..672c27220 100644 --- a/port/include/system.h +++ b/port/include/system.h @@ -29,6 +29,7 @@ f32 sysGetSeconds(void); void sysFatalError(const char *fmt, ...) __attribute__((noreturn)); +s32 sysLogIsOpen(void); void sysLogPrintf(s32 level, const char *fmt, ...); void sysGetExecutablePath(char *outPath, const u32 outLen); diff --git a/port/include/video.h b/port/include/video.h index ab7e34575..2b39cf46c 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -27,6 +27,8 @@ s32 videoGetWidth(void); s32 videoGetHeight(void); f32 videoGetAspect(void); s32 videoGetFullscreen(void); +s32 videoGetMaximizeWindow(void); +void videoSetMaximizeWindow(s32 fs); u32 videoGetTextureFilter(void); u32 videoGetTextureFilter2D(void); @@ -34,6 +36,7 @@ void videoSetWindowOffset(s32 x, s32 y); void videoSetFullscreen(s32 fs); void videoSetTextureFilter(u32 filter); void videoSetTextureFilter2D(u32 filter); +void videoCapFramerate(s32 limit); s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize); void videoSetFramebuffer(s32 target); diff --git a/port/src/crash.c b/port/src/crash.c index 77a8228ed..dc0536589 100644 --- a/port/src/crash.c +++ b/port/src/crash.c @@ -10,7 +10,8 @@ #include "system.h" #include "platform.h" -#define CRASH_MAX_MSG 4096 +#define CRASH_LOG_FNAME "pd.crash.log" +#define CRASH_MAX_MSG 8192 #define CRASH_MAX_SYM 256 #define CRASH_MAX_FRAMES 32 #define CRASH_MSG(...) \ @@ -79,8 +80,8 @@ static void crashStackTrace(char *msg, PEXCEPTION_POINTERS exinfo) if (SymGetLineFromAddr64(process, (uintptr_t)exinfo->ExceptionRecord->ExceptionAddress, &disp, &line)) { CRASH_MSG(": %s:%lu+%lu", line.FileName, line.LineNumber, disp); } - CRASH_MSG("\nMODULE: %p\n", crashGetModuleBase(exinfo->ExceptionRecord->ExceptionAddress)); - CRASH_MSG("MAIN MODULE: %p\n", crashGetModuleBase(crashInit)); + CRASH_MSG("\nMODULE: [%p]\n", crashGetModuleBase(exinfo->ExceptionRecord->ExceptionAddress)); + CRASH_MSG("MAIN MODULE: [%p]\n", crashGetModuleBase(crashInit)); CRASH_MSG("\nBACKTRACE:\n"); char symbuf[sizeof(SYMBOL_INFO) + CRASH_MAX_SYM * sizeof(TCHAR)]; @@ -99,6 +100,10 @@ static void crashStackTrace(char *msg, PEXCEPTION_POINTERS exinfo) if (SymFromAddr(process, stackframe.AddrPC.Offset, &disp64, sym)) { CRASH_MSG(": %s+%llu", sym->Name, disp64); + } else if (process, stackframe.AddrPC.Offset) { + const uintptr_t modbase = (uintptr_t)crashGetModuleBase((void *)(uintptr_t)stackframe.AddrPC.Offset); + const uintptr_t modofs = (uintptr_t)stackframe.AddrPC.Offset - modbase; + CRASH_MSG(": [%p]+%p", (void *)modbase, (void *)modofs); } if(SymGetLineFromAddr64(process, stackframe.AddrPC.Offset, &disp, &line)) { @@ -135,6 +140,15 @@ static long __stdcall crashHandler(PEXCEPTION_POINTERS exinfo) crashStackTrace(msg, exinfo); + // open log file for the crash dump if one hasn't been opened yet + if (!sysLogIsOpen()) { + FILE *f = fopen(CRASH_LOG_FNAME, "wb"); + if (f) { + fprintf(f, "Crash!\n\n%s", msg); + fclose(f); + } + } + sysFatalError("Crash!\n\n%s", msg); return EXCEPTION_CONTINUE_EXECUTION; diff --git a/port/src/main.c b/port/src/main.c index c241abdbb..b17b40afe 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -41,6 +41,10 @@ s32 g_TickRateDiv = 1; s32 g_SkipIntro = false; +s32 g_FileAutoSelect = -1; + +extern s32 g_StageNum; + s32 bootGetMemSize(void) { return (s32)g_OsMemSize; @@ -133,6 +137,8 @@ int main(int argc, const char **argv) g_StageNum = sysArgGetInt("--boot-stage", STAGE_TITLE); + g_FileAutoSelect = sysArgGetInt("--profile", -1); + if (g_StageNum == STAGE_TITLE && (sysArgCheck("--skip-intro") || g_SkipIntro)) { // shorthand for --boot-stage 0x26 g_StageNum = STAGE_CITRAINING; @@ -141,10 +147,23 @@ int main(int argc, const char **argv) g_StageNum = STAGE_TITLE; } + if (g_NetJoinLatch || g_NetHostLatch) { + if (g_FileAutoSelect < 0) { + // default to profile 0 if going into a net game + g_FileAutoSelect = 0; + } + // skip the intro if going into a net game + g_StageNum = STAGE_CITRAINING; + } + if (g_StageNum != STAGE_TITLE) { sysLogPrintf(LOG_NOTE, "boot stage set to 0x%02x", g_StageNum); } + if (g_FileAutoSelect >= 0) { + sysLogPrintf(LOG_NOTE, "player profile set to %d", g_FileAutoSelect); + } + mainProc(); return 0; diff --git a/port/src/net/net.c b/port/src/net/net.c index 8a8a2252b..f2e4d9399 100644 --- a/port/src/net/net.c +++ b/port/src/net/net.c @@ -17,19 +17,29 @@ #include "game/bondgun.h" #include "game/game_1531a0.h" #include "game/game_0b0fd0.h" +#include "game/title.h" +#include "game/menu.h" +#include "game/pdmode.h" +#include "game/mplayer/mplayer.h" #include "lib/main.h" #include "lib/vi.h" #include "config.h" #include "system.h" #include "console.h" +#include "fs.h" +#include "romdata.h" #include "utils.h" s32 g_NetMode = NETMODE_NONE; +s32 g_NetHostLatch = false; +s32 g_NetJoinLatch = false; + u32 g_NetServerUpdateRate = 1; u32 g_NetServerInRate = 128 * 1024; u32 g_NetServerOutRate = 128 * 1024; u32 g_NetServerPort = NET_DEFAULT_PORT; +s32 g_NetServerInfoQuery = true; u32 g_NetClientUpdateRate = 1; u32 g_NetClientInRate = 128 * 1024; @@ -80,27 +90,20 @@ static s32 netParseAddr(ENetAddress *out, const char *str) char *host = tmp; char *port = NULL; + char *colon = strrchr(tmp, ':'); - if (tmp[0] == '[') { - // ipv6 with port: [ADDR]:PORT - host = tmp + 1; // skip [ - port = strrchr(host, ']'); // find ] - if (port) { - if (port[1] != ':' || !isdigit(port[2])) { - return false; - } - *port = '\0'; // terminate ip - port += 2; // skip ]: - } - } else { - // ipv4 or hostname - port = strrchr(host, ':'); - if (port) { - if (!isdigit(port[1])) { - return false; - } - *port = '\0'; // terminate address - ++port; // skip : + // if there is a : in the address string, there could be a port value + // otherwise it's an ip or hostname with default port + if (colon > tmp) { + if (tmp[0] == '[' && colon[-1] == ']' && isdigit(colon[1])) { + // ipv6 with port: [ADDR]:PORT + colon[-1] = '\0'; // terminate ip + host = tmp + 1; // skip [ + port = colon + 1; // skip : + } else if (isdigit(colon[1]) && strchr(host, ':') == colon) { + // ipv4 or hostname with port + colon[0] = '\0'; // terminate ip + port = colon + 1; // skip : } } @@ -330,6 +333,82 @@ static inline const char *netGetDisconnectReason(const u32 reason) return msgs[0]; } +static void netServerQueryResponse(ENetAddress *address) +{ + static u8 data[256]; + static ENetBuffer ebuf; + struct netbuf buf = { .data = data, .size = sizeof(data) }; + const u8 flags = (g_NetLocalClient && g_NetLocalClient->state > CLSTATE_LOBBY) + | (0 << 1); // TODO: this will indicate coop/anti/etc + const char *modDir = fsGetModDir(); + if (!modDir) { + modDir = ""; + } + + netbufStartWrite(&buf); + netbufWriteData(&buf, NET_QUERY_MAGIC, sizeof(NET_QUERY_MAGIC) - 1); + netbufWriteU16(&buf, 0); // space for size + netbufWriteU32(&buf, NET_PROTOCOL_VER); + netbufWriteU8(&buf, flags); + netbufWriteU8(&buf, g_NetNumClients); + netbufWriteU8(&buf, g_NetMaxClients); + netbufWriteU8(&buf, g_StageNum); + netbufWriteU8(&buf, g_MpSetup.scenario); + netbufWriteStr(&buf, g_NetLocalClient ? g_NetLocalClient->settings.name : ""); + netbufWriteStr(&buf, g_RomName); + netbufWriteStr(&buf, modDir); + netbufWriteU16(&buf, 0); // space for checksum + + ebuf.data = buf.data; + ebuf.dataLength = buf.wp; + + // rewrite size + buf.wp = sizeof(NET_QUERY_MAGIC) - 1; + netbufWriteU16(&buf, ebuf.dataLength); + + // calculate and rewrite checksum + buf.wp = ebuf.dataLength - sizeof(u16); + u16 crc = 0xFFFF; + u16 x; + for (u32 i = 0; i < buf.wp; ++i) { + x = crc >> 8 ^ buf.data[i]; + x ^= x >> 4; + crc += (crc << 8) ^ (x << 12) ^ (x << 5) ^ x; + } + netbufWriteU16(&buf, crc); + + enet_socket_send(g_NetHost->socket, address, &ebuf, 1); +} + +static s32 netServerConnectionlessPacket(ENetEvent *event, ENetAddress *address, u8 *rxdata, s32 rxlen) +{ + if (rxdata && rxlen >= 5) { + if (!memcmp(rxdata, NET_QUERY_MAGIC, sizeof(NET_QUERY_MAGIC) - 1)) { + // this is a query packet, respond with server status + sysLogPrintf(LOG_NOTE | LOGFLAG_NOCON, "NET: query request from %s, responding", netFormatAddr(address)); + netServerQueryResponse(address); + return 1; + } + } + // probably a normal packet, pass through to enet + return 0; +} + +struct netclient *netClientForPlayerNum(s32 playernum) +{ + s32 slot = 0; + for (s32 i = 0; i < g_NetMaxClients; ++i) { + struct netclient *cl = &g_NetClients[i]; + if (cl->state >= CLSTATE_LOBBY) { + if (slot == playernum) { + return cl; + } + ++slot; + } + } + return NULL; +} + void netInit(void) { if (enet_initialize() < 0) { @@ -337,6 +416,26 @@ void netInit(void) return; } + const s32 argmaxclients = sysArgGetInt("--maxclients", -1); + if (argmaxclients > 0 && argmaxclients <= NET_MAX_CLIENTS) { + g_NetMaxClients = argmaxclients; + } + + const s32 argport = sysArgGetInt("--port", -1); + if (argport > 0 && argport < 0x10000) { + g_NetServerPort = argport; + } + + const char *argjoin = sysArgGetString("--connect"); + if (argjoin) { + strncpy(g_NetLastJoinAddr, argjoin, sizeof(g_NetLastJoinAddr) - 1); + g_NetJoinLatch = true; + } + + if (sysArgCheck("--host")) { + g_NetHostLatch = true; + } + g_NetInit = true; } @@ -354,6 +453,10 @@ s32 netStartServer(u16 port, s32 maxclients) return -2; } + if (g_NetServerInfoQuery) { + enet_host_set_intercept_callback(g_NetHost, netServerConnectionlessPacket); + } + netClientResetAll(); g_NetMaxClients = maxclients; @@ -439,6 +542,8 @@ s32 netStartClient(const char *addr) return -3; } + enet_host_set_intercept_callback(g_NetHost, NULL); + // save the address since it appears to be valid strncpy(g_NetLastJoinAddr, addr, NET_MAX_ADDR); @@ -479,6 +584,9 @@ s32 netDisconnect(void) return -1; } + // stop responding to connectionless packets + enet_host_set_intercept_callback(g_NetHost, NULL); + const bool wasingame = (g_NetLocalClient->state >= CLSTATE_GAME); for (s32 i = 0; i < NET_MAX_CLIENTS + 1; ++i) { @@ -504,8 +612,24 @@ s32 netDisconnect(void) sysLogPrintf(LOG_CHAT, "NET: disconnected"); if (wasingame) { + // skip the "want to save" dialog for all players + for (s32 i = 0; i < MAX_PLAYERS; ++i) { + if (g_Vars.players[i]) { + g_PlayerConfigsArray[i].options |= OPTION_ASKEDSAVEPLAYER; + } + } + // end the stage immediately mainEndStage(); + // try to drop back to main menu with 1 player + mpSetPaused(MPPAUSEMODE_UNPAUSED); g_MpSetup.chrslots = 1; + g_Vars.mplayerisrunning = false; + g_Vars.normmplayerisrunning = false; + g_Vars.lvmpbotlevel = 0; + titleSetNextStage(STAGE_CITRAINING); + setNumPlayers(1); + titleSetNextMode(TITLEMODE_SKIP); + mainChangeToStage(STAGE_CITRAINING); } return 0; @@ -709,7 +833,7 @@ void netStartFrame(void) if (cl) { netServerEvDisconnect(cl); } else { - sysLogPrintf(LOG_WARNING, "NET: disconnect from %s without attached client", netFormatPeerAddr(ev.peer)); + sysLogPrintf(LOG_WARNING | LOGFLAG_NOCON, "NET: disconnect from %s without attached client", netFormatPeerAddr(ev.peer)); --g_NetNumClients; } } @@ -728,7 +852,7 @@ void netStartFrame(void) netbufReset(&cl->in); } } else if (!isClient) { - sysLogPrintf(LOG_WARNING, "NET: receive from %s without attached client", netFormatPeerAddr(ev.peer)); + sysLogPrintf(LOG_WARNING | LOGFLAG_NOCON, "NET: receive from %s without attached client", netFormatPeerAddr(ev.peer)); } } enet_packet_dispose(ev.packet); @@ -827,18 +951,28 @@ u32 netSend(struct netclient *dstcl, struct netbuf *buf, const s32 reliable, con void netPlayersAllocate(void) { + s32 playernum = 0; + + if (g_NetMode == NETMODE_CLIENT) { + // we always put the local player at index 0, even client-side + // which means that clientside we have to put the server's player into our slot + const s32 svplayernum = g_NetLocalClient->playernum; + g_NetLocalClient->playernum = 0; + g_NetClients[0].playernum = svplayernum; + } + for (s32 i = 0; i < g_NetMaxClients; ++i) { struct netclient *cl = &g_NetClients[i]; if (cl->state < CLSTATE_LOBBY) { continue; } - if (cl == g_NetLocalClient) { - // we always put the local player at index 0, even client-side - cl->playernum = 0; - } else { - // which means that clientside we have to put the server's player into our slot - cl->playernum = (g_NetMode == NETMODE_CLIENT && i == 0) ? g_NetLocalClient->id : cl->id; + if (g_NetMode == NETMODE_SERVER) { + // on the server allocate players sequentially + cl->playernum = playernum++; + } + + if (cl != g_NetLocalClient) { // disable controls for the remote pawns and set their settings // TODO: backup the player configs or something struct mpplayerconfig *cfg = &g_PlayerConfigsArray[cl->playernum]; @@ -850,16 +984,18 @@ void netPlayersAllocate(void) cfg->options = g_PlayerConfigsArray[0].options & OPTION_PAINTBALL; cfg->options |= cl->settings.options & ~OPTION_PAINTBALL; // don't enable toggle aim, invert pitch or lookahead for remote players - cfg->options &= ~(OPTION_AIMCONTROL | OPTION_LOOKAHEAD | OPTION_ASKEDSAVEPLAYER); - cfg->options |= OPTION_FORWARDPITCH; + cfg->options &= ~(OPTION_AIMCONTROL | OPTION_LOOKAHEAD); + cfg->options |= OPTION_FORWARDPITCH | OPTION_ASKEDSAVEPLAYER; } - cl->player = g_Vars.players[cl->playernum]; cl->config = &g_PlayerConfigsArray[cl->playernum]; cl->config->client = cl; cl->config->handicap = 0x80; - cl->player->client = cl; - cl->player->isremote = (cl != g_NetLocalClient); + cl->player = g_Vars.players[cl->playernum]; + if (cl->player) { + cl->player->client = cl; + cl->player->isremote = (cl != g_NetLocalClient); + } } } @@ -896,9 +1032,14 @@ void netSyncIdsAllocate(void) // HACK: when we're a client, we'll need to swap our player and server player's props // because of what we do in netPlayersAllocate if (g_NetMode == NETMODE_CLIENT) { - const u16 sid = g_Vars.players[g_NetLocalClient->id]->prop->syncid; - g_Vars.players[g_NetLocalClient->id]->prop->syncid = g_Vars.players[0]->prop->syncid; - g_Vars.players[0]->prop->syncid = sid; + if (!g_NetLocalClient->player || !g_NetLocalClient->player->prop) { + sysLogPrintf(LOG_ERROR, "NET: no props allocated for players?"); + netDisconnect(); + return; + } + const u16 sid = g_NetClients[0].player->prop->syncid; + g_NetClients[0].player->prop->syncid = g_NetLocalClient->player->prop->syncid; + g_NetLocalClient->player->prop->syncid = sid; } sysLogPrintf(LOG_NOTE, "NET: last initial syncid: %u", g_NetNextSyncId); @@ -981,4 +1122,5 @@ PD_CONSTRUCTOR static void netConfigInit(void) configRegisterUInt("Net.Server.InRate", &g_NetServerInRate, 0, 10 * 1024 * 1024); configRegisterUInt("Net.Server.OutRate", &g_NetServerOutRate, 0, 10 * 1024 * 1024); configRegisterUInt("Net.Server.UpdateFrames", &g_NetServerUpdateRate, 0, 60); + configRegisterInt("Net.Server.AllowInfoQuery", &g_NetServerInfoQuery, 0, 1); } diff --git a/port/src/net/netmenu.c b/port/src/net/netmenu.c index c9e944c41..779beb84d 100644 --- a/port/src/net/netmenu.c +++ b/port/src/net/netmenu.c @@ -56,7 +56,7 @@ static char *menuhandlerHostPortValue(struct menuitem *item) return tmp; } -static MenuItemHandlerResult menuhandlerHostStart(s32 operation, struct menuitem *item, union handlerdata *data) +MenuItemHandlerResult menuhandlerHostStart(s32 operation, struct menuitem *item, union handlerdata *data) { if (operation == MENUOP_SET) { if (netStartServer(g_NetMenuPort, g_NetMenuMaxPlayers) == 0) { @@ -292,7 +292,7 @@ static MenuItemHandlerResult menuhandlerJoinAddress(s32 operation, struct menuit return 0; } -static MenuItemHandlerResult menuhandlerJoinStart(s32 operation, struct menuitem *item, union handlerdata *data) +MenuItemHandlerResult menuhandlerJoinStart(s32 operation, struct menuitem *item, union handlerdata *data) { if (operation == MENUOP_SET) { if (netStartClient(g_NetJoinAddr) == 0) { @@ -350,7 +350,7 @@ struct menudialogdef g_NetJoinMenuDialog = { /* main */ -static MenuItemHandlerResult menuhandlerHostGame(s32 operation, struct menuitem *item, union handlerdata *data) +MenuItemHandlerResult menuhandlerHostGame(s32 operation, struct menuitem *item, union handlerdata *data) { if (operation == MENUOP_SET) { g_NetMenuPort = g_NetServerPort; @@ -360,7 +360,7 @@ static MenuItemHandlerResult menuhandlerHostGame(s32 operation, struct menuitem return 0; } -static MenuItemHandlerResult menuhandlerJoinGame(s32 operation, struct menuitem *item, union handlerdata *data) +MenuItemHandlerResult menuhandlerJoinGame(s32 operation, struct menuitem *item, union handlerdata *data) { if (operation == MENUOP_SET) { if (g_NetJoinAddr[0] == '\0') { diff --git a/port/src/net/netmsg.c b/port/src/net/netmsg.c index 414b01798..56508c8e9 100644 --- a/port/src/net/netmsg.c +++ b/port/src/net/netmsg.c @@ -424,8 +424,9 @@ u32 netmsgSvcStageStartWrite(struct netbuf *dst) for (s32 i = 0; i < g_NetMaxClients; ++i) { struct netclient *ncl = &g_NetClients[i]; if (ncl->state) { - ncl->settings.team = g_PlayerConfigsArray[ncl->id].base.team; + ncl->settings.team = ncl->config->base.team; netbufWriteU8(dst, ncl->id); + netbufWriteU8(dst, ncl->playernum); netbufWriteU8(dst, ncl->settings.team); netbufWriteU16(dst, ncl->settings.options); netbufWriteU8(dst, ncl->settings.bodynum); @@ -490,6 +491,7 @@ u32 netmsgSvcStageStartRead(struct netbuf *src, struct netclient *srccl) for (u8 i = 0; i < numplayers; ++i) { const u8 id = netbufReadU8(src); struct netclient *ncl = &g_NetClients[id]; + ncl->playernum = netbufReadU8(src); ncl->settings.team = netbufReadU8(src); if (ncl != g_NetLocalClient) { ncl->id = id; @@ -506,7 +508,7 @@ u32 netmsgSvcStageStartRead(struct netbuf *src, struct netclient *srccl) return 3; } } else { - // skip our own settings except for the team + // skip our own settings except for the team and player number netbufReadU16(src); netbufReadU8(src); netbufReadU8(src); @@ -523,16 +525,17 @@ u32 netmsgSvcStageStartRead(struct netbuf *src, struct netclient *srccl) } // set teams on the player configs, but swap teams with the server player + // because we haven't swapped player numbers yet (see netPlayersAlloc) for (u32 i = 0; i < NET_MAX_CLIENTS; ++i) { struct netclient *ncl = &g_NetClients[i]; if (ncl->state) { u32 playernum = 0; if (ncl->id == 0) { - playernum = g_NetLocalClient->id; + playernum = g_NetLocalClient->playernum; } else if (ncl == g_NetLocalClient) { - playernum = 0; + playernum = g_NetClients[0].playernum; } else { - playernum = ncl->id; + playernum = ncl->playernum; } g_PlayerConfigsArray[playernum].base.team = ncl->settings.team; } @@ -577,12 +580,14 @@ u32 netmsgSvcStageEndRead(struct netbuf *src, struct netclient *srccl) for (s32 i = 0; i < g_NetMaxClients; ++i) { struct netclient *ncl = &g_NetClients[i]; if (ncl->state) { - ncl->state = CLSTATE_LOBBY; + ncl->state = CLSTATE_DISCONNECTED; ncl->player = NULL; ncl->config = NULL; } } + g_NetLocalClient->state = CLSTATE_LOBBY; + g_NumReasonsToEndMpMatch = 1; mainEndStage(); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 6a39ad982..dd999bee4 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -945,29 +945,17 @@ static MenuItemHandlerResult menuhandlerCrosshairSway(s32 operation, struct menu return 0; } -static MenuItemHandlerResult menuhandlerCrosshairSize(s32 operation, struct menuitem *item, union handlerdata *data) +static MenuItemHandlerResult menuhandlerCrosshairR(s32 operation, struct menuitem* item, union handlerdata* data) { - switch (operation) { - case MENUOP_GETSLIDER: - data->slider.value = g_PlayerExtCfg[g_ExtMenuPlayer].crosshairsize; - break; - case MENUOP_SET: - g_PlayerExtCfg[g_ExtMenuPlayer].crosshairsize = data->slider.value; - break; - } + u32 newColor; - return 0; -} - -static MenuItemHandlerResult menuhandlerCrosshair_R(s32 operation, struct menuitem* item, union handlerdata* data) -{ switch (operation) { case MENUOP_GETSLIDER: data->slider.value = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour >> 24) & 0xFF; break; case MENUOP_SET: - u32 newColor = g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFFFFFF | data->slider.value << 24; + newColor = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFFFFFF) | data->slider.value << 24; g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour = newColor; break; } @@ -975,15 +963,17 @@ static MenuItemHandlerResult menuhandlerCrosshair_R(s32 operation, struct menuit return 0; } -static MenuItemHandlerResult menuhandlerCrosshair_G(s32 operation, struct menuitem* item, union handlerdata* data) +static MenuItemHandlerResult menuhandlerCrosshairG(s32 operation, struct menuitem* item, union handlerdata* data) { + u32 newColor; + switch (operation) { case MENUOP_GETSLIDER: data->slider.value = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour >> 16) & 0xFF; break; case MENUOP_SET: - u32 newColor = g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFF00FFFF | data->slider.value << 16; + newColor = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFF00FFFF) | data->slider.value << 16; g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour = newColor; break; } @@ -991,15 +981,17 @@ static MenuItemHandlerResult menuhandlerCrosshair_G(s32 operation, struct menuit return 0; } -static MenuItemHandlerResult menuhandlerCrosshair_B(s32 operation, struct menuitem* item, union handlerdata* data) +static MenuItemHandlerResult menuhandlerCrosshairB(s32 operation, struct menuitem* item, union handlerdata* data) { + u32 newColor; + switch (operation) { case MENUOP_GETSLIDER: data->slider.value = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour >> 8) & 0xFF; break; case MENUOP_SET: - u32 newColor = g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFFFF00FF | data->slider.value << 8; + newColor = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFFFF00FF) | data->slider.value << 8; g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour = newColor; break; } @@ -1007,15 +999,17 @@ static MenuItemHandlerResult menuhandlerCrosshair_B(s32 operation, struct menuit return 0; } -static MenuItemHandlerResult menuhandlerCrosshair_A(s32 operation, struct menuitem* item, union handlerdata* data) +static MenuItemHandlerResult menuhandlerCrosshairA(s32 operation, struct menuitem* item, union handlerdata* data) { + u32 newColor; + switch (operation) { case MENUOP_GETSLIDER: data->slider.value = g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFF; break; case MENUOP_SET: - u32 newColor = g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFFFFFF00 | data->slider.value; + newColor = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFFFFFF00) | data->slider.value; g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour = newColor; break; } @@ -1032,6 +1026,20 @@ static MenuItemHandlerResult menuhandlerCrosshairColorPreview(s32 operation, str return 0; } +static MenuItemHandlerResult menuhandlerCrosshairSize(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GETSLIDER: + data->slider.value = g_PlayerExtCfg[g_ExtMenuPlayer].crosshairsize; + break; + case MENUOP_SET: + g_PlayerExtCfg[g_ExtMenuPlayer].crosshairsize = data->slider.value; + break; + } + + return 0; +} + struct menuitem g_ExtendedGameCrosshairColourMenuItems[] = { { MENUITEMTYPE_SLIDER, @@ -1039,7 +1047,7 @@ struct menuitem g_ExtendedGameCrosshairColourMenuItems[] = { MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, (uintptr_t)"Red", 255, - menuhandlerCrosshair_R, + menuhandlerCrosshairR, }, { MENUITEMTYPE_SLIDER, @@ -1047,7 +1055,7 @@ struct menuitem g_ExtendedGameCrosshairColourMenuItems[] = { MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, (uintptr_t)"Green", 255, - menuhandlerCrosshair_G, + menuhandlerCrosshairG, }, { MENUITEMTYPE_SLIDER, @@ -1055,7 +1063,7 @@ struct menuitem g_ExtendedGameCrosshairColourMenuItems[] = { MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, (uintptr_t)"Blue", 255, - menuhandlerCrosshair_B, + menuhandlerCrosshairB, }, { MENUITEMTYPE_SLIDER, @@ -1063,7 +1071,7 @@ struct menuitem g_ExtendedGameCrosshairColourMenuItems[] = { MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, (uintptr_t)"Alpha", 255, - menuhandlerCrosshair_A, + menuhandlerCrosshairA, }, { MENUITEMTYPE_SEPARATOR, diff --git a/port/src/pdmain.c b/port/src/pdmain.c index b0c5e27c4..b08870abd 100644 --- a/port/src/pdmain.c +++ b/port/src/pdmain.c @@ -585,7 +585,7 @@ void mainEndStage(void) s32 prevplayernum = g_Vars.currentplayernum; s32 i; - for (i = 0; i < PLAYERCOUNT(); i++) { + for (i = 0; i < LOCALPLAYERCOUNT(); i++) { setCurrentPlayerNum(i); endscreenPushCoop(); } @@ -596,7 +596,7 @@ void mainEndStage(void) s32 prevplayernum = g_Vars.currentplayernum; s32 i; - for (i = 0; i < PLAYERCOUNT(); i++) { + for (i = 0; i < LOCALPLAYERCOUNT(); i++) { setCurrentPlayerNum(i); endscreenPushAnti(); } diff --git a/port/src/pdsched.c b/port/src/pdsched.c index 02c4f4240..234c43154 100644 --- a/port/src/pdsched.c +++ b/port/src/pdsched.c @@ -243,7 +243,12 @@ void schedStartFrame(OSSched *sc) { videoStartFrame(); if (g_Vars.diffframe60) { - netStartFrame(); + if (g_NetMode) { + videoCapFramerate(120); + netStartFrame(); + } else { + videoCapFramerate(0); + } } } diff --git a/port/src/system.c b/port/src/system.c index 432fc0a39..14666bbb9 100644 --- a/port/src/system.c +++ b/port/src/system.c @@ -12,6 +12,7 @@ #include "system.h" #define LOG_FNAME "pd.log" +#define CRASHLOG_FNAME "pd.crash.log" #define USEC_IN_SEC 1000000ULL static u64 startTick = 0; @@ -20,6 +21,24 @@ static char logPath[2048]; static s32 sysArgc; static const char **sysArgv; +static inline void sysLogSetPath(const char *fname) +{ + // figure out where the log is and clear it + // try working dir first + snprintf(logPath, sizeof(logPath), "./%s", fname); + FILE *f = fopen(logPath, "wb"); + if (!f) { + // try home dir + sysGetHomePath(logPath, sizeof(logPath) - 1); + strncat(logPath, "/", sizeof(logPath) - 1); + strncat(logPath, fname, sizeof(logPath) - 1); + f = fopen(logPath, "wb"); + } + if (f) { + fclose(f); + } +} + void sysInitArgs(s32 argc, const char **argv) { sysArgc = argc; @@ -31,19 +50,7 @@ void sysInit(void) startTick = sysGetMicroseconds(); if (sysArgCheck("--log")) { - // figure out where the log is and clear it - // try working dir first - snprintf(logPath, sizeof(logPath), "./%s", LOG_FNAME); - FILE *f = fopen(logPath, "wb"); - if (!f) { - // try home dir - sysGetHomePath(logPath, sizeof(logPath) - 1); - strncat(logPath, "/" LOG_FNAME, sizeof(logPath) - 1); - f = fopen(logPath, "wb"); - } - if (f) { - fclose(f); - } + sysLogSetPath(LOG_FNAME); } #ifdef VERSION_HASH @@ -103,6 +110,11 @@ float sysGetSeconds(void) return (f32)t / 1000000.f; } +s32 sysLogIsOpen(void) +{ + return (logPath[0] != '\0'); +} + void sysLogPrintf(s32 level, const char *fmt, ...) { static const char *prefix[3] = { diff --git a/port/src/video.c b/port/src/video.c index 18da94fc0..4e9244234 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -202,6 +202,14 @@ void videoSetTextureFilter2D(u32 filter) texFilter2D = !!filter; } +void videoCapFramerate(s32 limit) +{ + if (vidFramerateLimit > 0 && vidFramerateLimit < limit) { + limit = vidFramerateLimit; + } + wmAPI->set_target_fps(limit ? limit : vidFramerateLimit); +} + s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize) { return gfx_create_framebuffer(w, h, upscale, autoresize); diff --git a/src/game/bondmove.c b/src/game/bondmove.c index a27aaf40a..2b6742684 100644 --- a/src/game/bondmove.c +++ b/src/game/bondmove.c @@ -112,6 +112,10 @@ static void bgunProcessInputAltButton(struct movedata *data, s8 contpad, s32 i) static inline void bmoveProcessRemoteInput(const bool allowc1buttons) { struct player *pl = g_Vars.currentplayer; + if (!pl->client) { + return; + } + struct netplayermove *inmove = &pl->client->inmove[0]; struct netplayermove *inmoveprev = &pl->client->inmove[1]; s32 moveticks = inmove->tick - inmoveprev->tick; @@ -899,14 +903,17 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i const f32 mlookscale = g_Vars.lvupdate240 ? (4.f / (f32)g_Vars.lvupdate240) : 4.f; const bool allowmlook = (g_Vars.currentplayernum == 0) && (allowc1x || allowc1y); bool allowmcross = false; +#endif - if (g_Vars.currentplayer->isremote) { + controlmode = optionsGetControlMode(g_Vars.currentplayerstats->mpindex); + +#ifndef PLATFORM_N64 + if (g_Vars.currentplayer->isremote || controlmode == CONTROLMODE_NA) { bmoveProcessRemoteInput(allowc1buttons); return; } #endif - controlmode = optionsGetControlMode(g_Vars.currentplayerstats->mpindex); weaponnum = bgunGetWeaponNum(HAND_RIGHT); canmanualzoom = weaponHasAimFlag(weaponnum, INVAIMFLAG_MANUALZOOM); contpad1 = optionsGetContpadNum1(g_Vars.currentplayerstats->mpindex); @@ -1421,6 +1428,9 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i aimoffhist[i] = !aimonhist[i]; } +#ifdef AVOID_UB + if (numsamples) +#endif g_Vars.currentplayer->insightaimmode = aimonhist[numsamples - 1]; } diff --git a/src/game/challenge.c b/src/game/challenge.c index 167eae8aa..cfba07b4c 100644 --- a/src/game/challenge.c +++ b/src/game/challenge.c @@ -265,8 +265,10 @@ void challengePerformSanityChecks(void) g_Vars.mphilltime = 10; } } else if (!challengeIsFeatureUnlocked(MPFEATURE_8BOTS)) { - // Limit to 4 players and 4 simulants -#if MAX_PLAYERS > 4 + // Limit to 4 players and 4 simulants in local games +#ifndef PLATFORM_N64 + g_MpSetup.chrslots &= (g_NetMode ? 0x0fff : 0x0f0f); +#elif MAX_PLAYERS > 4 g_MpSetup.chrslots &= 0x0f0f; #else g_MpSetup.chrslots &= 0x00ff; diff --git a/src/game/filemgr.c b/src/game/filemgr.c index a5f904bfe..3cee8c3c0 100644 --- a/src/game/filemgr.c +++ b/src/game/filemgr.c @@ -2592,6 +2592,16 @@ MenuItemHandlerResult filemgrChooseAgentListMenuHandler(s32 operation, struct me } } } + +#ifndef PLATFORM_N64 + if (g_FileAutoSelect >= 0 && g_FileLists[0] && g_FileLists[0]->numfiles > 0) { + const u32 tmp = data->list.value; + data->list.value = g_FileAutoSelect; + g_FileAutoSelect = -1; + filemgrChooseAgentListMenuHandler(MENUOP_SET, item, data); + data->list.value = tmp; + } +#endif break; case MENUOP_GETOPTIONCOUNT: data->list.value = g_FileLists[0]->numfiles + 1; diff --git a/src/game/game_0b0fd0.c b/src/game/game_0b0fd0.c index 5293e8500..8540c4a50 100644 --- a/src/game/game_0b0fd0.c +++ b/src/game/game_0b0fd0.c @@ -398,9 +398,11 @@ u16 weaponGetFileNum(s32 weaponnum) } if (weapon) { +#ifdef PLATFORM_N64 if (PLAYERCOUNT() >= 2) { return weapon->lo_model; } +#endif return weapon->hi_model; } diff --git a/src/game/lv.c b/src/game/lv.c index 988e5f760..28f2ac004 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -349,7 +349,7 @@ void lvReset(s32 stagenum) } if (g_Vars.mplayerisrunning == false) { - g_Vars.playerstats[0].mpindex = 4; + g_Vars.playerstats[0].mpindex = MAX_PLAYERS; g_PlayerConfigsArray[MAX_PLAYERS].contpad1 = 0; g_PlayerConfigsArray[MAX_PLAYERS].contpad2 = 1; } @@ -1700,7 +1700,7 @@ Gfx *lvRender(Gfx *gdl) artifactsTick(); #ifndef PLATFORM_N64 - if ((g_NetMode && i) || i > MAX_LOCAL_PLAYERS) { + if ((g_NetMode && i) || i >= MAX_LOCAL_PLAYERS) { gdl = savedgdl; } #endif diff --git a/src/game/mainmenu.c b/src/game/mainmenu.c index 3c28cb34a..1e84f2daf 100644 --- a/src/game/mainmenu.c +++ b/src/game/mainmenu.c @@ -45,6 +45,10 @@ struct menudialogdef g_CinemaMenuDialog; #ifndef PLATFORM_N64 extern struct menudialogdef g_ExtendedMenuDialog; extern struct menudialogdef g_NetMenuDialog; +extern MenuItemHandlerResult menuhandlerJoinGame(s32 operation, struct menuitem *item, union handlerdata *data); +extern MenuItemHandlerResult menuhandlerJoinStart(s32 operation, struct menuitem *item, union handlerdata *data); +extern MenuItemHandlerResult menuhandlerHostGame(s32 operation, struct menuitem *item, union handlerdata *data); +extern MenuItemHandlerResult menuhandlerHostStart(s32 operation, struct menuitem *item, union handlerdata *data); #endif char *menuTextCurrentStageName(struct menuitem *item) @@ -2686,7 +2690,7 @@ struct menuitem g_MissionDisplayOptionsMenuItems[] = { 0, 0, L_OPTIONS_205, // "Sight on Screen" - 0x00000004, + MAX_PLAYERS, menuhandlerSightOnScreen, }, { @@ -2694,7 +2698,7 @@ struct menuitem g_MissionDisplayOptionsMenuItems[] = { 0, 0, L_OPTIONS_206, // "Always Show Target" - 0x00000004, + MAX_PLAYERS, menuhandlerAlwaysShowTarget, }, { @@ -2702,7 +2706,7 @@ struct menuitem g_MissionDisplayOptionsMenuItems[] = { 0, 0, L_OPTIONS_207, // "Show Zoom Range" - 0x00000004, + MAX_PLAYERS, menuhandlerShowZoomRange, }, { @@ -2710,7 +2714,7 @@ struct menuitem g_MissionDisplayOptionsMenuItems[] = { 0, 0, L_OPTIONS_208, // "Ammo on Screen" - 0x00000004, + MAX_PLAYERS, menuhandlerAmmoOnScreen, }, { @@ -2718,7 +2722,7 @@ struct menuitem g_MissionDisplayOptionsMenuItems[] = { 0, 0, L_OPTIONS_209, // "Show Gun Function" - 0x00000004, + MAX_PLAYERS, menuhandlerShowGunFunction, }, { @@ -2726,7 +2730,7 @@ struct menuitem g_MissionDisplayOptionsMenuItems[] = { 0, 0, L_OPTIONS_210, // "Paintball" - 0x00000004, + MAX_PLAYERS, menuhandlerPaintball, }, { @@ -2734,7 +2738,7 @@ struct menuitem g_MissionDisplayOptionsMenuItems[] = { 0, 0, L_MPWEAPONS_169, // "In-Game Subtitles" - 0x00000004, + MAX_PLAYERS, menuhandlerInGameSubtitles, }, { @@ -2742,7 +2746,7 @@ struct menuitem g_MissionDisplayOptionsMenuItems[] = { 0, 0, L_MPWEAPONS_168, // "Cutscene Subtitles" - 0x00000004, + MAX_PLAYERS, menuhandlerCutsceneSubtitles, }, { @@ -2750,7 +2754,7 @@ struct menuitem g_MissionDisplayOptionsMenuItems[] = { 0, 0, L_OPTIONS_212, // "Show Mission Time" - 0x00000004, + MAX_PLAYERS, menuhandlerShowMissionTime, }, { @@ -2787,7 +2791,7 @@ struct menuitem g_2PMissionDisplayOptionsVMenuItems[] = { 0, 0, L_MPWEAPONS_145, // "Sight on Screen" - 0x00000004, + MAX_PLAYERS, menuhandlerSightOnScreen, }, { @@ -2795,7 +2799,7 @@ struct menuitem g_2PMissionDisplayOptionsVMenuItems[] = { 0, 0, L_MPWEAPONS_146, // "Target" - 0x00000004, + MAX_PLAYERS, menuhandlerAlwaysShowTarget, }, { @@ -2803,7 +2807,7 @@ struct menuitem g_2PMissionDisplayOptionsVMenuItems[] = { 0, 0, L_MPWEAPONS_147, // "Zoom Range" - 0x00000004, + MAX_PLAYERS, menuhandlerShowZoomRange, }, { @@ -2811,7 +2815,7 @@ struct menuitem g_2PMissionDisplayOptionsVMenuItems[] = { 0, 0, L_MPWEAPONS_148, // "Show Ammo" - 0x00000004, + MAX_PLAYERS, menuhandlerAmmoOnScreen, }, { @@ -2819,7 +2823,7 @@ struct menuitem g_2PMissionDisplayOptionsVMenuItems[] = { 0, 0, L_MPWEAPONS_149, // "Gun Function" - 0x00000004, + MAX_PLAYERS, menuhandlerShowGunFunction, }, { @@ -2827,7 +2831,7 @@ struct menuitem g_2PMissionDisplayOptionsVMenuItems[] = { 0, 0, L_MPWEAPONS_150, // "Paintball" - 0x00000004, + MAX_PLAYERS, menuhandlerPaintball, }, { @@ -2839,7 +2843,7 @@ struct menuitem g_2PMissionDisplayOptionsVMenuItems[] = { #else L_MPWEAPONS_169, // "In-Game Subtitles" #endif - 0x00000004, + MAX_PLAYERS, menuhandlerInGameSubtitles, }, { @@ -2851,7 +2855,7 @@ struct menuitem g_2PMissionDisplayOptionsVMenuItems[] = { #else L_MPWEAPONS_168, // "Cutscene Subtitles" #endif - 0x00000004, + MAX_PLAYERS, menuhandlerCutsceneSubtitles, }, { @@ -2859,7 +2863,7 @@ struct menuitem g_2PMissionDisplayOptionsVMenuItems[] = { 0, 0, L_MPWEAPONS_152, // "Mission Time" - 0x00000004, + MAX_PLAYERS, menuhandlerShowMissionTime, }, { @@ -2896,7 +2900,7 @@ struct menuitem g_CiDisplayMenuItems[] = { 0, 0, L_OPTIONS_205, // "Sight on Screen" - 0x00000004, + MAX_PLAYERS, menuhandlerSightOnScreen, }, { @@ -2904,7 +2908,7 @@ struct menuitem g_CiDisplayMenuItems[] = { 0, 0, L_OPTIONS_206, // "Always Show Target" - 0x00000004, + MAX_PLAYERS, menuhandlerAlwaysShowTarget, }, { @@ -2912,7 +2916,7 @@ struct menuitem g_CiDisplayMenuItems[] = { 0, 0, L_OPTIONS_207, // "Show Zoom Range" - 0x00000004, + MAX_PLAYERS, menuhandlerShowZoomRange, }, { @@ -2920,7 +2924,7 @@ struct menuitem g_CiDisplayMenuItems[] = { 0, 0, L_OPTIONS_208, // "Ammo on Screen" - 0x00000004, + MAX_PLAYERS, menuhandlerAmmoOnScreen, }, { @@ -2928,7 +2932,7 @@ struct menuitem g_CiDisplayMenuItems[] = { 0, 0, L_OPTIONS_209, // "Show Gun Function" - 0x00000004, + MAX_PLAYERS, menuhandlerShowGunFunction, }, { @@ -2936,7 +2940,7 @@ struct menuitem g_CiDisplayMenuItems[] = { 0, 0, L_OPTIONS_210, // "Paintball" - 0x00000004, + MAX_PLAYERS, menuhandlerPaintball, }, { @@ -2944,7 +2948,7 @@ struct menuitem g_CiDisplayMenuItems[] = { 0, 0, L_MPWEAPONS_169, // "In-Game Subtitles" - 0x00000004, + MAX_PLAYERS, menuhandlerInGameSubtitles, }, { @@ -2952,7 +2956,7 @@ struct menuitem g_CiDisplayMenuItems[] = { 0, 0, L_MPWEAPONS_168, // "Cutscene Subtitles" - 0x00000004, + MAX_PLAYERS, menuhandlerCutsceneSubtitles, }, { @@ -2960,7 +2964,7 @@ struct menuitem g_CiDisplayMenuItems[] = { 0, 0, L_OPTIONS_212, // "Show Mission Time" - 0x00000004, + MAX_PLAYERS, menuhandlerShowMissionTime, }, { @@ -2999,7 +3003,7 @@ struct menuitem g_CiDisplayPlayer2MenuItems[] = { 0, 0, L_OPTIONS_205, // "Sight on Screen" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerSightOnScreen, }, { @@ -3007,7 +3011,7 @@ struct menuitem g_CiDisplayPlayer2MenuItems[] = { 0, 0, L_OPTIONS_206, // "Always Show Target" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerAlwaysShowTarget, }, { @@ -3015,7 +3019,7 @@ struct menuitem g_CiDisplayPlayer2MenuItems[] = { 0, 0, L_OPTIONS_207, // "Show Zoom Range" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerShowZoomRange, }, { @@ -3023,7 +3027,7 @@ struct menuitem g_CiDisplayPlayer2MenuItems[] = { 0, 0, L_OPTIONS_208, // "Ammo on Screen" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerAmmoOnScreen, }, { @@ -3031,7 +3035,7 @@ struct menuitem g_CiDisplayPlayer2MenuItems[] = { 0, 0, L_OPTIONS_209, // "Show Gun Function" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerShowGunFunction, }, { @@ -3039,7 +3043,7 @@ struct menuitem g_CiDisplayPlayer2MenuItems[] = { 0, 0, L_OPTIONS_210, // "Paintball" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerPaintball, }, { @@ -3047,7 +3051,7 @@ struct menuitem g_CiDisplayPlayer2MenuItems[] = { 0, 0, L_MPWEAPONS_169, // "In-Game Subtitles" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerInGameSubtitles, }, { @@ -3055,7 +3059,7 @@ struct menuitem g_CiDisplayPlayer2MenuItems[] = { 0, 0, L_MPWEAPONS_168, // "Cutscene Subtitles" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerCutsceneSubtitles, }, { @@ -3063,7 +3067,7 @@ struct menuitem g_CiDisplayPlayer2MenuItems[] = { 0, 0, L_OPTIONS_212, // "Show Mission Time" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerShowMissionTime, }, { @@ -3108,7 +3112,7 @@ struct menuitem g_MissionControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_195, // "Reverse Pitch" - 0x00000004, + MAX_PLAYERS, menuhandlerReversePitch, }, { @@ -3116,7 +3120,7 @@ struct menuitem g_MissionControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_196, // "Look Ahead" - 0x00000004, + MAX_PLAYERS, menuhandlerLookAhead, }, { @@ -3124,7 +3128,7 @@ struct menuitem g_MissionControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_197, // "Head Roll" - 0x00000004, + MAX_PLAYERS, menuhandlerHeadRoll, }, { @@ -3132,7 +3136,7 @@ struct menuitem g_MissionControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_198, // "Auto-Aim" - 0x00000004, + MAX_PLAYERS, menuhandlerAutoAim, }, { @@ -3140,7 +3144,7 @@ struct menuitem g_MissionControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_199, // "Aim Control" - 0x00000004, + MAX_PLAYERS, menuhandlerAimControl, }, { @@ -3186,7 +3190,7 @@ struct menuitem g_CiControlOptionsMenuItems2[] = { 0, 0, L_MPWEAPONS_271, // "" - 0x00000004, + MAX_PLAYERS, menuhandlerReversePitch, }, { @@ -3194,7 +3198,7 @@ struct menuitem g_CiControlOptionsMenuItems2[] = { 0, 0, L_MPWEAPONS_272, // "" - 0x00000004, + MAX_PLAYERS, menuhandlerLookAhead, }, { @@ -3202,7 +3206,7 @@ struct menuitem g_CiControlOptionsMenuItems2[] = { 0, 0, L_MPWEAPONS_273, // "" - 0x00000004, + MAX_PLAYERS, menuhandlerHeadRoll, }, { @@ -3210,7 +3214,7 @@ struct menuitem g_CiControlOptionsMenuItems2[] = { 0, 0, L_MPWEAPONS_274, // "" - 0x00000004, + MAX_PLAYERS, menuhandlerAutoAim, }, { @@ -3218,7 +3222,7 @@ struct menuitem g_CiControlOptionsMenuItems2[] = { 0, 0, L_MPWEAPONS_275, // "" - 0x00000004, + MAX_PLAYERS, menuhandlerAimControl, }, { @@ -3264,7 +3268,7 @@ struct menuitem g_CiControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_195, // "Reverse Pitch" - 0x00000004, + MAX_PLAYERS, menuhandlerReversePitch, }, { @@ -3272,7 +3276,7 @@ struct menuitem g_CiControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_196, // "Look Ahead" - 0x00000004, + MAX_PLAYERS, menuhandlerLookAhead, }, { @@ -3280,7 +3284,7 @@ struct menuitem g_CiControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_197, // "Head Roll" - 0x00000004, + MAX_PLAYERS, menuhandlerHeadRoll, }, { @@ -3288,7 +3292,7 @@ struct menuitem g_CiControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_198, // "Auto-Aim" - 0x00000004, + MAX_PLAYERS, menuhandlerAutoAim, }, { @@ -3296,7 +3300,7 @@ struct menuitem g_CiControlOptionsMenuItems[] = { 0, 0, L_OPTIONS_199, // "Aim Control" - 0x00000004, + MAX_PLAYERS, menuhandlerAimControl, }, { @@ -3341,7 +3345,7 @@ struct menuitem g_CiControlPlayer2MenuItems[] = { 0, 0, L_OPTIONS_195, // "Reverse Pitch" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerReversePitch, }, { @@ -3349,7 +3353,7 @@ struct menuitem g_CiControlPlayer2MenuItems[] = { 0, 0, L_OPTIONS_196, // "Look Ahead" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerLookAhead, }, { @@ -3357,7 +3361,7 @@ struct menuitem g_CiControlPlayer2MenuItems[] = { 0, 0, L_OPTIONS_197, // "Head Roll" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerHeadRoll, }, { @@ -3365,7 +3369,7 @@ struct menuitem g_CiControlPlayer2MenuItems[] = { 0, 0, L_OPTIONS_198, // "Auto-Aim" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerAutoAim, }, { @@ -3373,7 +3377,7 @@ struct menuitem g_CiControlPlayer2MenuItems[] = { 0, 0, L_OPTIONS_199, // "Aim Control" - 0x00000005, + MAX_PLAYERS + 1, menuhandlerAimControl, }, { @@ -4881,6 +4885,17 @@ MenuDialogHandlerResult menudialogMainMenu(s32 operation, struct menudialogdef * g_MissionConfig.iscoop = false; g_MissionConfig.isanti = false; } +#ifndef PLATFORM_N64 + if (g_NetJoinLatch) { + menuhandlerJoinGame(MENUOP_SET, NULL, NULL); + menuhandlerJoinStart(MENUOP_SET, NULL, NULL); + g_NetJoinLatch = false; + } else if (g_NetHostLatch) { + menuhandlerHostGame(MENUOP_SET, NULL, NULL); + menuhandlerHostStart(MENUOP_SET, NULL, NULL); + g_NetHostLatch = false; + } +#endif break; } diff --git a/src/game/menu.c b/src/game/menu.c index a5f3498ad..045c548dd 100644 --- a/src/game/menu.c +++ b/src/game/menu.c @@ -5379,7 +5379,7 @@ Gfx *menuRender(Gfx *gdl) #ifndef PLATFORM_N64 if (g_NetMode) { if (g_NetMode == NETMODE_SERVER) { - sprintf(text, "Server: %d/%d", g_NetNumClients, g_NetMaxClients); + sprintf(text, "Server: %d/%d %04x", g_NetNumClients, g_NetMaxClients, g_MpSetup.chrslots); } else { sprintf(text, "Client: ID %u", g_NetLocalClient->id); } diff --git a/src/game/menutick.c b/src/game/menutick.c index 73e19ee13..71288dcae 100644 --- a/src/game/menutick.c +++ b/src/game/menutick.c @@ -321,6 +321,18 @@ void menuTick(void) } } +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER && g_MenuData.unk008 == -1) { + s32 slot = 1; + for (i = 1; i < g_NetMaxClients; ++i) { + if (g_NetClients[i].state >= CLSTATE_LOBBY) { + g_MpSetup.chrslots |= (1 << slot); + ++slot; + } + } + } +#endif + mpCalculateLockIfLastWinnerOrLoser(); challengePerformSanityChecks(); } diff --git a/src/game/mplayer/mplayer.c b/src/game/mplayer/mplayer.c index 0f6b2da0e..871560210 100644 --- a/src/game/mplayer/mplayer.c +++ b/src/game/mplayer/mplayer.c @@ -192,9 +192,11 @@ void mpStartMatch(void) if (g_NetMode == NETMODE_SERVER) { g_MpSetup.chrslots &= 0xff00; g_MpSetup.chrslots |= 1; + s32 slot = 1; for (s32 i = 1; i < g_NetMaxClients; ++i) { if (g_NetClients[i].state >= CLSTATE_LOBBY) { - g_MpSetup.chrslots |= (1 << i); + g_MpSetup.chrslots |= (1 << slot); + ++slot; } } } @@ -3001,13 +3003,6 @@ struct mpchrconfig *mpGetChrConfigBySlotNum(s32 slot) } } -#ifndef PLATFORM_N64 - // ignore chrslots while setting up a netgame - if (!result && g_NetMode && slot < g_NetMaxClients && g_NetClients[slot].state >= CLSTATE_LOBBY) { - result = &g_PlayerConfigsArray[slot].base; - } -#endif - return result; } @@ -3323,7 +3318,7 @@ s32 func0f18d074(s32 index) for (i = 0; i < MAX_BOTS; i++) { if (&g_BotConfigsArray[i].base == g_MpAllChrConfigPtrs[index]) { - return i + 4; + return i + MAX_PLAYERS; } } diff --git a/src/game/mplayer/setup.c b/src/game/mplayer/setup.c index f9aed5955..2aa1f3297 100644 --- a/src/game/mplayer/setup.c +++ b/src/game/mplayer/setup.c @@ -23,6 +23,9 @@ #include "data.h" #include "gbiex.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#endif struct menuitem g_MpCharacterMenuItems[]; struct menudialogdef g_MpAddSimulantMenuDialog; @@ -2297,6 +2300,15 @@ MenuItemHandlerResult menuhandlerMpHandicapPlayer(s32 operation, struct menuitem char *mpMenuTextHandicapPlayerName(struct menuitem *item) { if (g_MpSetup.chrslots & (1 << item->param)) { +#ifndef PLATFORM_N64 + if (g_NetMode) { + // use client names directly, as the config names are not set yet + struct netclient *cl = netClientForPlayerNum(item->param); + if (cl) { + return cl->settings.name; + } + } +#endif return g_PlayerConfigsArray[item->param].base.name; } @@ -2598,6 +2610,40 @@ struct menuitem g_MpHandicapsMenuItems[] = { 0x000000ff, menuhandlerMpHandicapPlayer, }, +#if MAX_PLAYERS > 4 + { + MENUITEMTYPE_SLIDER, + 4, + MENUITEMFLAG_LESSLEFTPADDING | MENUITEMFLAG_LOCKABLEMINOR, + (uintptr_t)&mpMenuTextHandicapPlayerName, + 0x000000ff, + menuhandlerMpHandicapPlayer, + }, + { + MENUITEMTYPE_SLIDER, + 5, + MENUITEMFLAG_LESSLEFTPADDING | MENUITEMFLAG_LOCKABLEMINOR, + (uintptr_t)&mpMenuTextHandicapPlayerName, + 0x000000ff, + menuhandlerMpHandicapPlayer, + }, + { + MENUITEMTYPE_SLIDER, + 6, + MENUITEMFLAG_LESSLEFTPADDING | MENUITEMFLAG_LOCKABLEMINOR, + (uintptr_t)&mpMenuTextHandicapPlayerName, + 0x000000ff, + menuhandlerMpHandicapPlayer, + }, + { + MENUITEMTYPE_SLIDER, + 7, + MENUITEMFLAG_LESSLEFTPADDING | MENUITEMFLAG_LOCKABLEMINOR, + (uintptr_t)&mpMenuTextHandicapPlayerName, + 0x000000ff, + menuhandlerMpHandicapPlayer, + }, +#endif { MENUITEMTYPE_SEPARATOR, 0, @@ -3407,6 +3453,15 @@ char *mpMenuTextChrNameForTeamSetup(struct menuitem *item) struct mpchrconfig *mpchr = mpGetChrConfigBySlotNum(item->param); if (mpchr) { +#ifndef PLATFORM_N64 + if (g_NetMode) { + // use client names directly, as the config names are not set yet + struct netclient *cl = netClientForPlayerNum(item->param); + if (cl) { + return cl->settings.name; + } + } +#endif return mpchr->name; } diff --git a/src/game/player.c b/src/game/player.c index 3f63da803..4c6c540b1 100644 --- a/src/game/player.c +++ b/src/game/player.c @@ -184,7 +184,10 @@ u32 var80070748 = 0; u32 var8007074c = 0; bool g_PlayersWithControl[] = { + true, true, true, true, +#if MAX_PLAYERS > 4 true, true, true, true +#endif }; bool g_PlayerInvincible = false; @@ -3507,6 +3510,11 @@ void playerTick(bool arg0) bool pause = false; f32 newspeed; +#ifndef PLATFORM_N64 + if (mode == CONTROLMODE_NA) { + // TODO + } else +#endif if (mode == CONTROLMODE_23 || mode == CONTROLMODE_24 || mode == CONTROLMODE_22 @@ -5886,10 +5894,6 @@ f32 playerGetZoomFovMult(s32 playernum) return g_PlayerExtCfg[playernum % MAX_LOCAL_PLAYERS].fovzoommult; } -#endif - -#if MAX_PLAYERS > 4 - s32 playerGetCount(void) { s32 count = 0; @@ -5899,4 +5903,16 @@ s32 playerGetCount(void) return count; } +s32 playerGetLocalCount(void) +{ + if (g_NetMode) { + return 1; + } + const s32 playercount = playerGetCount(); + if (playercount > MAX_LOCAL_PLAYERS) { + return MAX_LOCAL_PLAYERS; + } + return playercount; +} + #endif diff --git a/src/game/playerreset.c b/src/game/playerreset.c index 896601929..6e941dd9c 100644 --- a/src/game/playerreset.c +++ b/src/game/playerreset.c @@ -148,6 +148,12 @@ void playerReset(void) g_PlayersWithControl[1] = true; g_PlayersWithControl[2] = true; g_PlayersWithControl[3] = true; +#if MAX_PLAYERS > 4 + g_PlayersWithControl[4] = true; + g_PlayersWithControl[5] = true; + g_PlayersWithControl[6] = true; + g_PlayersWithControl[7] = true; +#endif g_PlayerInvincible = false; playerSetTickMode(TICKMODE_GE_FADEIN); diff --git a/src/game/timing.c b/src/game/timing.c index aabeec728..9a7c8e380 100644 --- a/src/game/timing.c +++ b/src/game/timing.c @@ -49,7 +49,7 @@ void frametimeCalculate(void) #ifdef PLATFORM_N64 g_Vars.mininc60 = 1; #else - g_Vars.mininc60 = g_NetMode ? 1 : g_TickRateDiv; + g_Vars.mininc60 = g_TickRateDiv; #endif frametimeApply(diffframe60, diffframe240, count); diff --git a/src/include/constants.h b/src/include/constants.h index 5a514a207..217d26a87 100644 --- a/src/include/constants.h +++ b/src/include/constants.h @@ -4728,7 +4728,7 @@ enum weaponnum { #define NETMODE_SERVER 1 #define NETMODE_CLIENT 2 -#define LOCALPLAYERCOUNT() (g_NetMode ? 1 : PLAYERCOUNT()) +#define LOCALPLAYERCOUNT() playerGetLocalCount() #endif diff --git a/src/include/data.h b/src/include/data.h index 535d3773c..327816ae2 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -552,6 +552,7 @@ extern bool g_BlurFbDirty; extern s32 g_TickRateDiv; extern s32 g_MusicDisableMpDeath; extern s32 g_BgunGeMuzzleFlashes; +extern s32 g_FileAutoSelect; extern s32 g_NetMode; @@ -562,6 +563,7 @@ extern s32 g_NetMode; #define TEX_FILTER_2D g_TexFilter2D s32 playerGetCount(void); +s32 playerGetLocalCount(void); #else // PLATFORM_N64 diff --git a/src/include/game/player.h b/src/include/game/player.h index 21f12d0cf..5783f579c 100644 --- a/src/include/game/player.h +++ b/src/include/game/player.h @@ -110,6 +110,7 @@ struct sndstate *playerSndStart(s32 arg0, s16 sound, struct sndstate **handle, s #if MAX_PLAYERS > 4 s32 playerGetCount(void); +s32 playerGetLocalCount(void); #endif #ifndef PLATFORM_N64 diff --git a/tools/query.py b/tools/query.py new file mode 100644 index 000000000..4187e18ad --- /dev/null +++ b/tools/query.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +# simple client for server status queries + +import sys, os, socket, struct, time + +DEFAULT_PORT = 27100 +QUERY_MAGIC = b"PDQM\x01" +MAX_WAIT = 5.0 + +def checksum(data): + crc = 0xFFFF + for b in data[:-2]: + x = crc >> 8 ^ b + x ^= x >> 4 + crc += (crc << 8) ^ (x << 12) ^ (x << 5) ^ x + crc &= 0xFFFF + return crc + +def eat_string(data): + strlen = struct.unpack_from("[:]") + sys.exit(1) + +addrstr = sys.argv[1].strip() +host = addrstr +port = None + +if addrstr.startswith('[') and ("]:" in addrstr): + # [ipv6]:port + host, sep, port = addrstr.rpartition(':') +elif addrstr.count(':') == 1: + # possibly ipv4:port or hostname:port + host, sep, port = addrstr.rpartition(':') + +host = host.strip('[]') + +if port != None: + port = int(port) +else: + port = DEFAULT_PORT + +sockfam = socket.AF_INET +if ':' in host: + sockfam = socket.AF_INET6 + +sock = socket.socket(sockfam, socket.SOCK_DGRAM) +sock.settimeout(MAX_WAIT) + +# send query magic to server +sock.sendto(QUERY_MAGIC, (host, port)) + +# wait for response +data = sock.recv(256) + +# check magic +if data[:5] != QUERY_MAGIC: + print("invalid magic: expected", QUERY_MAGIC, "got", data[:5]) + sys.exit(1) + +# check checksum +chkremote = struct.unpack(" len(data): + print("invalid size: expected", len(data), "got", datalen) + sys.exit(1) + +data = data[7:datalen - 2] + +# unpack fixed size part of the response +msgdata = struct.unpack_from("