Add support for custom ports with Sunshine

This commit is contained in:
Cameron Gutman 2022-09-23 22:48:43 -05:00
parent 3f00f25a39
commit 0325a3b88c
7 changed files with 147 additions and 110 deletions

View File

@ -166,109 +166,128 @@ static int load_cert(const char* keyDirectory) {
return GS_OK;
}
static int load_server_status(PSERVER_DATA server) {
static int load_serverinfo(PSERVER_DATA server, bool https) {
uuid_t uuid;
char uuid_str[UUID_STRLEN];
int ret;
char url[4096];
int ret = GS_INVALID;
char *pairedText = NULL;
char *currentGameText = NULL;
char *stateText = NULL;
char *serverCodecModeSupportText = NULL;
char *httpsPortText = NULL;
uuid_generate_random(uuid);
uuid_unparse(uuid, uuid_str);
snprintf(url, sizeof(url), "%s://%s:%d/serverinfo?uniqueid=%s&uuid=%s",
https ? "https" : "http", server->serverInfo.address, https ? server->httpsPort : server->httpPort, unique_id, uuid_str);
PHTTP_DATA data = http_create_data();
if (data == NULL) {
ret = GS_OUT_OF_MEMORY;
goto cleanup;
}
if (http_request(url, data) != GS_OK) {
ret = GS_IO_ERROR;
goto cleanup;
}
if (xml_status(data->memory, data->size) == GS_ERROR) {
ret = GS_ERROR;
goto cleanup;
}
if (xml_search(data->memory, data->size, "currentgame", &currentGameText) != GS_OK) {
goto cleanup;
}
if (xml_search(data->memory, data->size, "PairStatus", &pairedText) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "appversion", (char**) &server->serverInfo.serverInfoAppVersion) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "state", &stateText) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "ServerCodecModeSupport", &serverCodecModeSupportText) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "gputype", &server->gpuType) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "GsVersion", &server->gsVersion) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "GfeVersion", (char**) &server->serverInfo.serverInfoGfeVersion) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "HttpsPort", &httpsPortText) != GS_OK)
goto cleanup;
if (xml_modelist(data->memory, data->size, &server->modes) != GS_OK)
goto cleanup;
// These fields are present on all version of GFE that this client supports
if (!strlen(currentGameText) || !strlen(pairedText) || !strlen(server->serverInfo.serverInfoAppVersion) || !strlen(stateText))
goto cleanup;
server->paired = pairedText != NULL && strcmp(pairedText, "1") == 0;
server->currentGame = currentGameText == NULL ? 0 : atoi(currentGameText);
server->supports4K = serverCodecModeSupportText != NULL;
server->serverMajorVersion = atoi(server->serverInfo.serverInfoAppVersion);
server->httpsPort = atoi(httpsPortText);
if (!server->httpsPort)
server->httpsPort = 47984;
if (strstr(stateText, "_SERVER_BUSY") == NULL) {
// After GFE 2.8, current game remains set even after streaming
// has ended. We emulate the old behavior by forcing it to zero
// if streaming is not active.
server->currentGame = 0;
}
ret = GS_OK;
cleanup:
if (data != NULL)
http_free_data(data);
if (pairedText != NULL)
free(pairedText);
if (currentGameText != NULL)
free(currentGameText);
if (serverCodecModeSupportText != NULL)
free(serverCodecModeSupportText);
if (httpsPortText != NULL)
free(httpsPortText);
return ret;
}
static int load_server_status(PSERVER_DATA server) {
int ret;
int i;
i = 0;
do {
char *pairedText = NULL;
char *currentGameText = NULL;
char *stateText = NULL;
char *serverCodecModeSupportText = NULL;
/* Fetch the HTTPS port if we don't have one yet */
if (!server->httpsPort) {
ret = load_serverinfo(server, false);
if (ret != GS_OK)
return ret;
}
ret = GS_INVALID;
uuid_generate_random(uuid);
uuid_unparse(uuid, uuid_str);
// Modern GFE versions don't allow serverinfo to be fetched over HTTPS if the client
// is not already paired. Since we can't pair without knowing the server version, we
// make another request over HTTP if the HTTPS request fails. We can't just use HTTP
// for everything because it doesn't accurately tell us if we're paired.
snprintf(url, sizeof(url), "%s://%s:%d/serverinfo?uniqueid=%s&uuid=%s",
i == 0 ? "https" : "http", server->serverInfo.address, i == 0 ? server->httpsPort : server->httpPort, unique_id, uuid_str);
PHTTP_DATA data = http_create_data();
if (data == NULL) {
ret = GS_OUT_OF_MEMORY;
goto cleanup;
}
if (http_request(url, data) != GS_OK) {
ret = GS_IO_ERROR;
goto cleanup;
}
if (xml_status(data->memory, data->size) == GS_ERROR) {
ret = GS_ERROR;
goto cleanup;
}
if (xml_search(data->memory, data->size, "currentgame", &currentGameText) != GS_OK) {
goto cleanup;
}
if (xml_search(data->memory, data->size, "PairStatus", &pairedText) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "appversion", (char**) &server->serverInfo.serverInfoAppVersion) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "state", &stateText) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "ServerCodecModeSupport", &serverCodecModeSupportText) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "gputype", &server->gpuType) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "GsVersion", &server->gsVersion) != GS_OK)
goto cleanup;
if (xml_search(data->memory, data->size, "GfeVersion", (char**) &server->serverInfo.serverInfoGfeVersion) != GS_OK)
goto cleanup;
if (xml_modelist(data->memory, data->size, &server->modes) != GS_OK)
goto cleanup;
// These fields are present on all version of GFE that this client supports
if (!strlen(currentGameText) || !strlen(pairedText) || !strlen(server->serverInfo.serverInfoAppVersion) || !strlen(stateText))
goto cleanup;
server->paired = pairedText != NULL && strcmp(pairedText, "1") == 0;
server->currentGame = currentGameText == NULL ? 0 : atoi(currentGameText);
server->supports4K = serverCodecModeSupportText != NULL;
server->serverMajorVersion = atoi(server->serverInfo.serverInfoAppVersion);
if (strstr(stateText, "_SERVER_BUSY") == NULL) {
// After GFE 2.8, current game remains set even after streaming
// has ended. We emulate the old behavior by forcing it to zero
// if streaming is not active.
server->currentGame = 0;
}
ret = GS_OK;
cleanup:
if (data != NULL)
http_free_data(data);
if (pairedText != NULL)
free(pairedText);
if (currentGameText != NULL)
free(currentGameText);
if (serverCodecModeSupportText != NULL)
free(serverCodecModeSupportText);
i++;
} while (ret != GS_OK && i < 2);
// Modern GFE versions don't allow serverinfo to be fetched over HTTPS if the client
// is not already paired. Since we can't pair without knowing the server version, we
// make another request over HTTP if the HTTPS request fails. We can't just use HTTP
// for everything because it doesn't accurately tell us if we're paired.
ret = GS_INVALID;
for (i = 0; i < 2 && ret != GS_OK; i++) {
ret = load_serverinfo(server, i == 0);
}
if (ret == GS_OK && !server->unsupported) {
if (server->serverMajorVersion > MAX_SUPPORTED_GFE_VERSION) {
@ -790,7 +809,9 @@ int gs_quit_app(PSERVER_DATA server) {
return ret;
}
int gs_init(PSERVER_DATA server, char *address, const char *keyDirectory, int log_level, bool unsupported) {
int gs_init(PSERVER_DATA server, char *address, unsigned short httpPort, const char *keyDirectory, int log_level, bool unsupported) {
char* portSeparator;
mkdirtree(keyDirectory);
if (load_unique_id(keyDirectory) != GS_OK)
return GS_FAILED;
@ -803,7 +824,7 @@ int gs_init(PSERVER_DATA server, char *address, const char *keyDirectory, int lo
LiInitializeServerInformation(&server->serverInfo);
server->serverInfo.address = address;
server->unsupported = unsupported;
server->httpPort = 47989;
server->httpsPort = 47984;
server->httpPort = httpPort ? httpPort : 47989;
server->httpsPort = 0; /* Populated by load_server_status() */
return load_server_status(server);
}

View File

@ -42,7 +42,7 @@ typedef struct _SERVER_DATA {
unsigned short httpsPort;
} SERVER_DATA, *PSERVER_DATA;
int gs_init(PSERVER_DATA server, char* address, const char *keyDirectory, int logLevel, bool unsupported);
int gs_init(PSERVER_DATA server, char* address, unsigned short httpPort, const char *keyDirectory, int logLevel, bool unsupported);
int gs_start_app(PSERVER_DATA server, PSTREAM_CONFIGURATION config, int appId, bool sops, bool localaudio, int gamepad_mask);
int gs_applist(PSERVER_DATA server, PAPP_LIST *app_list);
int gs_unpair(PSERVER_DATA server);

View File

@ -33,6 +33,11 @@
static AvahiSimplePoll *simple_poll = NULL;
struct cb_ctx {
char* address;
unsigned short* port;
};
static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
if (state == AVAHI_CLIENT_FAILURE) {
gs_error = "Server connection failure";
@ -43,12 +48,14 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
static void resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) {
if (event == AVAHI_RESOLVER_FOUND) {
if (userdata != NULL) {
avahi_address_snprint(userdata, AVAHI_ADDRESS_STR_MAX, address);
struct cb_ctx* ctx = userdata;
avahi_address_snprint(ctx->address, AVAHI_ADDRESS_STR_MAX, address);
*ctx->port = port;
avahi_simple_poll_quit(simple_poll);
} else {
char strAddress[AVAHI_ADDRESS_STR_MAX];
avahi_address_snprint(strAddress, sizeof(strAddress), address);
printf(" %s (%s)\n", host_name, strAddress);
printf(" %s (%s:%u)\n", host_name, strAddress, port);
}
}
@ -73,7 +80,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
}
}
void gs_discover_server(char* dest) {
void gs_discover_server(char* dest, unsigned short* port) {
AvahiClient *client = NULL;
AvahiServiceBrowser *sb = NULL;
@ -89,7 +96,10 @@ void gs_discover_server(char* dest) {
goto cleanup;
}
if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, "_nvstream._tcp", NULL, 0, browse_callback, dest))) {
struct cb_ctx ctx;
ctx.address = dest;
ctx.port = port;
if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, "_nvstream._tcp", NULL, 0, browse_callback, &ctx))) {
gs_error = "Failed to create service browser";
goto cleanup;
}

View File

@ -21,4 +21,4 @@
#define MAX_ADDRESS_SIZE 40
void gs_discover_server(char* dest);
void gs_discover_server(char* dest, unsigned short* port);

View File

@ -73,6 +73,7 @@ static struct option long_options[] = {
{"debug", no_argument, NULL, 'Z'},
{"nomouseemulation", no_argument, NULL, '4'},
{"pin", required_argument, NULL, '5'},
{"port", required_argument, NULL, '6'},
{0, 0, 0, 0},
};
@ -248,6 +249,9 @@ static void parse_argument(int c, char* value, PCONFIGURATION config) {
case '5':
config->pin = atoi(value);
break;
case '6':
config->port = atoi(value);
break;
case 1:
if (config->action == NULL)
config->action = value;
@ -369,6 +373,7 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) {
config->rotate = 0;
config->codec = CODEC_UNSPECIFIED;
config->pin = 0;
config->port = 47989;
config->inputsCount = 0;
config->mapping = get_path("gamecontrollerdb.txt", getenv("XDG_DATA_DIRS"));
@ -386,7 +391,7 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) {
} else {
int option_index = 0;
int c;
while ((c = getopt_long_only(argc, argv, "-abc:d:efg:h:i:j:k:lm:no:p:q:r:s:tu:v:w:xy45:", long_options, &option_index)) != -1) {
while ((c = getopt_long_only(argc, argv, "-abc:d:efg:h:i:j:k:lm:no:p:q:r:s:tu:v:w:xy45:6:", long_options, &option_index)) != -1) {
parse_argument(c, optarg, config);
}
}

View File

@ -47,7 +47,8 @@ typedef struct _CONFIGURATION {
char* inputs[MAX_INPUTS];
int inputsCount;
enum codecs codec;
int pin;
int pin;
unsigned short port;
} CONFIGURATION, *PCONFIGURATION;
extern bool inputAdded;

View File

@ -263,7 +263,7 @@ int main(int argc, char* argv[]) {
}
config.address[0] = 0;
printf("Searching for server...\n");
gs_discover_server(config.address);
gs_discover_server(config.address, &config.port);
if (config.address[0] == 0) {
fprintf(stderr, "Autodiscovery failed. Specify an IP address next time.\n");
exit(-1);
@ -279,7 +279,7 @@ int main(int argc, char* argv[]) {
printf("Connecting to %s...\n", config.address);
int ret;
if ((ret = gs_init(&server, config.address, config.key_dir, config.debug_level, config.unsupported)) == GS_OUT_OF_MEMORY) {
if ((ret = gs_init(&server, config.address, config.port, config.key_dir, config.debug_level, config.unsupported)) == GS_OUT_OF_MEMORY) {
fprintf(stderr, "Not enough memory\n");
exit(-1);
} else if (ret == GS_ERROR) {