diff --git a/Makefile b/Makefile index d02af909e..d22207e60 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ endif FRONTEND_OBJS = pad.o xparam.o fntsys.o renderman.o menusys.o OSDHistory.o system.o lang.o lang_internal.o config.o hdd.o dialogs.o \ dia.o ioman.o texcache.o themes.o supportbase.o bdmsupport.o ethsupport.o hddsupport.o zso.o lz4.o \ - appsupport.o gui.o guigame.o vmc_groups.o textures.o opl.o atlas.o nbns.o httpclient.o gsm.o cheatman.o sound.o ps2cnf.o + appsupport.o gui.o guigame.o vmc_groups.o textures.o opl.o atlas.o nbns.o httpclient.o gsm.o cheatman.o sound.o ps2cnf.o announce.o IOP_OBJS = iomanx.o filexio.o ps2fs.o usbd.o bdmevent.o \ bdm.o bdmfs_fatfs.o usbmass_bd.o iLinkman.o IEEE1394_bd.o mx4sio_bd.o \ diff --git a/include/announce.h b/include/announce.h new file mode 100644 index 000000000..7d764b880 --- /dev/null +++ b/include/announce.h @@ -0,0 +1,8 @@ +#ifndef __ANNOUNCE_H +#define __ANNOUNCE_H + +// Broadcasts a PS2RichPresence LAUNCH packet over UDP so companion apps on the LAN can react to the game being started (e.g. Discord rich presence). +// Packet spec: https://github.com/brkzlr/PS2RichPresence/blob/master/protocol/ps2rp_protocol.h +void announceGameLaunch(const char *titleId, const char *displayName); + +#endif diff --git a/include/config.h b/include/config.h index 3cf4be790..c8b2cc814 100644 --- a/include/config.h +++ b/include/config.h @@ -111,6 +111,7 @@ enum CONFIG_INDEX { #define CONFIG_OPL_DEFAULT_BGM_PATH "default_bgm_path" #define CONFIG_OPL_XSENSITIVITY "x_sensitivity" #define CONFIG_OPL_YSENSITIVITY "y_sensitivity" +#define CONFIG_OPL_ANNOUNCE_GAMES "announce_games" // Network config keys #define CONFIG_NET_ETH_LINKM "eth_linkmode" diff --git a/include/opl.h b/include/opl.h index 0d56a6b94..ced2db486 100644 --- a/include/opl.h +++ b/include/opl.h @@ -197,6 +197,9 @@ extern char gETHPrefix[32]; extern int gRememberLastPlayed; +// Announce game launches over the network (PS2RichPresence protocol) +extern int gEnableAnnounce; + // Last Played Auto Start extern int KeyPressedOnce; extern int gAutoStartLastPlayed; diff --git a/src/announce.c b/src/announce.c new file mode 100644 index 000000000..13df16fd6 --- /dev/null +++ b/src/announce.c @@ -0,0 +1,55 @@ +#include "include/opl.h" +#include "include/announce.h" +#include "include/ethsupport.h" + +// PS2RichPresence announce protocol, version 1. +// The wire format is kept in sync with the spec at: https://github.com/brkzlr/PS2RichPresence/blob/master/protocol/ps2rp_protocol.h +#define ANNOUNCE_PORT 50003 +#define ANNOUNCE_PROTOCOL_VERSION 1 +#define ANNOUNCE_HEADER_SIZE 20 +#define ANNOUNCE_TITLE_ID_SIZE 12 +#define ANNOUNCE_MAX_NAME_LENGTH 255 +#define ANNOUNCE_EVENT_LAUNCH 0 +#define ANNOUNCE_SOURCE_OPL 2 + +void announceGameLaunch(const char *titleId, const char *displayName) +{ + u8 ip[4], netmask[4], gateway[4], broadcast[4]; + u8 packet[ANNOUNCE_HEADER_SIZE + ANNOUNCE_MAX_NAME_LENGTH]; + struct sockaddr_in addr; + int sock, nameLength, i; + + if (!gEnableAnnounce) + return; + + if (ethGetNetConfig(ip, netmask, gateway) < 0) + return; + + memcpy(&packet[0], "PS2R", 4); + packet[4] = ANNOUNCE_PROTOCOL_VERSION; + packet[5] = ANNOUNCE_EVENT_LAUNCH; + packet[6] = ANNOUNCE_SOURCE_OPL; + nameLength = displayName ? strlen(displayName) : 0; + if (nameLength > ANNOUNCE_MAX_NAME_LENGTH) + nameLength = ANNOUNCE_MAX_NAME_LENGTH; + packet[7] = (u8)nameLength; + memset(&packet[8], 0, ANNOUNCE_TITLE_ID_SIZE); + if (titleId) + strncpy((char *)&packet[8], titleId, ANNOUNCE_TITLE_ID_SIZE - 1); + memcpy(&packet[ANNOUNCE_HEADER_SIZE], displayName, nameLength); + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) + return; + + for (i = 0; i < 4; i++) + broadcast[i] = ip[i] | ~netmask[i]; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(ANNOUNCE_PORT); + addr.sin_addr.s_addr = broadcast[0] | (broadcast[1] << 8) | (broadcast[2] << 16) | ((u32)broadcast[3] << 24); + + sendto(sock, packet, ANNOUNCE_HEADER_SIZE + nameLength, 0, (struct sockaddr *)&addr, sizeof(addr)); + disconnect(sock); +} diff --git a/src/bdmsupport.c b/src/bdmsupport.c index b181dec6a..ea3f96d8a 100644 --- a/src/bdmsupport.c +++ b/src/bdmsupport.c @@ -10,6 +10,7 @@ #include "include/system.h" #include "include/extern_irx.h" #include "include/cheatman.h" +#include "include/announce.h" #include "include/sound.h" #include "modules/iopcore/common/cdvd_config.h" @@ -540,6 +541,8 @@ void bdmLaunchGame(item_list_t *itemList, int id, config_set_t *configSet) settings->hddIsLBA48 = pDeviceData->bdmHddIsLBA48; } + announceGameLaunch(game->startup, game->name); + if (gAutoLaunchBDMGame == NULL) deinit(NO_EXCEPTION, itemList->mode); // CAREFUL: deinit will call bdmCleanUp, so bdmGames/game will be freed else { diff --git a/src/ethsupport.c b/src/ethsupport.c index f0462fa42..b05c9698e 100644 --- a/src/ethsupport.c +++ b/src/ethsupport.c @@ -11,6 +11,7 @@ #include "include/system.h" #include "include/extern_irx.h" #include "include/cheatman.h" +#include "include/announce.h" #include "modules/iopcore/common/cdvd_config.h" #define NEWLIB_PORT_AWARE @@ -703,6 +704,9 @@ static void ethLaunchGame(item_list_t *itemList, int id, config_set_t *configSet if (configGetStrCopy(configSet, CONFIG_ITEM_ALTSTARTUP, filename, sizeof(filename)) == 0) strcpy(filename, game->startup); + + announceGameLaunch(game->startup, game->name); + deinit(NO_EXCEPTION, ETH_MODE); // CAREFUL: deinit will call ethCleanUp, so ethGames/game will be freed settings->common.fakemodule_flags |= FAKE_MODULE_FLAG_DEV9; diff --git a/src/hddsupport.c b/src/hddsupport.c index 1f3eaefa8..a79282ec1 100644 --- a/src/hddsupport.c +++ b/src/hddsupport.c @@ -11,6 +11,7 @@ #include "include/system.h" #include "include/extern_irx.h" #include "include/cheatman.h" +#include "include/announce.h" #include "modules/iopcore/common/cdvd_config.h" #define NEWLIB_PORT_AWARE @@ -598,6 +599,8 @@ void hddLaunchGame(item_list_t *itemList, int id, config_set_t *configSet) } } + announceGameLaunch(game->startup, game->name); + if (gAutoLaunchGame == NULL) deinit(NO_EXCEPTION, HDD_MODE); // CAREFUL: deinit will call hddCleanUp, so hddGames/game will be freed else { diff --git a/src/opl.c b/src/opl.c index a6d8fa320..1d60912a4 100644 --- a/src/opl.c +++ b/src/opl.c @@ -173,6 +173,7 @@ int gScrollSpeed; char gExitPath[256]; int gEnableDebug; int gPS2Logo; +int gEnableAnnounce; int gDefaultDevice; int gEnableWrite; char gBDMPrefix[32]; @@ -963,6 +964,7 @@ static void _loadConfig() configGetInt(configOPL, CONFIG_OPL_BOOT_SND_VOLUME, &gBootSndVolume); configGetInt(configOPL, CONFIG_OPL_BGM_VOLUME, &gBGMVolume); configGetStrCopy(configOPL, CONFIG_OPL_DEFAULT_BGM_PATH, gDefaultBGMPath, sizeof(gDefaultBGMPath)); + configGetInt(configOPL, CONFIG_OPL_ANNOUNCE_GAMES, &gEnableAnnounce); } } @@ -1135,6 +1137,7 @@ static void _saveConfig() configSetStr(configOPL, CONFIG_OPL_DEFAULT_BGM_PATH, gDefaultBGMPath); configSetInt(configOPL, CONFIG_OPL_XSENSITIVITY, gXSensitivity); configSetInt(configOPL, CONFIG_OPL_YSENSITIVITY, gYSensitivity); + configSetInt(configOPL, CONFIG_OPL_ANNOUNCE_GAMES, gEnableAnnounce); configSetInt(configOPL, CONFIG_OPL_SWAP_SEL_BUTTON, gSelectButton == KEY_CIRCLE ? 0 : 1); } @@ -1740,6 +1743,7 @@ static void setDefaults(void) gAutoRefresh = 0; gEnableDebug = 0; gPS2Logo = 0; + gEnableAnnounce = 0; gHDDGameListCache = 0; gEnableWrite = 0; gRememberLastPlayed = 0; @@ -1898,6 +1902,7 @@ static void miniInit(int mode) config_set_t *configOPL = configGetByType(CONFIG_OPL); configGetInt(configOPL, CONFIG_OPL_PS2LOGO, &gPS2Logo); + configGetInt(configOPL, CONFIG_OPL_ANNOUNCE_GAMES, &gEnableAnnounce); configGetStrCopy(configOPL, CONFIG_OPL_EXIT_PATH, gExitPath, sizeof(gExitPath)); configGetInt(configOPL, CONFIG_OPL_HDD_SPINDOWN, &gHDDSpindown); if (mode == BDM_MODE) {