diff --git a/.gitmodules b/.gitmodules index 9f0893b..ddf2e6a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "third_party/json"] path = third_party/json url = https://github.com/nlohmann/json.git +[submodule "third_party/libuuid"] + path = third_party/libuuid + url = https://github.com/fadedreamz/libuuid.git diff --git a/Makefile b/Makefile index 757c3bc..ea934c0 100755 --- a/Makefile +++ b/Makefile @@ -206,7 +206,7 @@ DEFINES += -DNANOGUI_USE_OPENGL -DNVG_STB_IMAGE_IMPLEMENTATION -DNANOGUI_NO_GLFW -DHAS_SOCKLEN_T -DHAS_POLL -DHAS_FCNTL -D_GNU_SOURCE -D__LIBRETRO__ CFLAGS += -Wall -pedantic $(fpic) -std=gnu11 $(DEFINES) -CXXFLAGS += -std=gnu++17 -fno-permissive $(DEFINES) +CXXFLAGS += -std=gnu++17 $(DEFINES) LIBS += -lcrypto -lssl -lcurl -lz -lexpat -lopus \ -lavcodec -lavformat -lavutil -lavdevice -lstdc++ -lswresample diff --git a/Makefile.libnx b/Makefile.libnx new file mode 100644 index 0000000..f19a1a8 --- /dev/null +++ b/Makefile.libnx @@ -0,0 +1,341 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- +TARGET := moonlight +BUILD := build +SOURCES := libgamestream src/nanogui_resources src src/streaming src/streaming/ffmpeg src/streaming/video src/ui/windows src/ui/buttons src/ui \ + third_party/moonlight-common-c/enet third_party/moonlight-common-c/reedsolomon third_party/moonlight-common-c/src \ + third_party/nanogui/ext/nanovg/src third_party/nanogui/src +DATA := data +INCLUDES := include +#ROMFS := romfs + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +M_INCLUDES := \ + -I$(TOPDIR)/src -I$(TOPDIR)/src/streaming \ + -I$(TOPDIR)/src/streaming/ffmpeg -I$(TOPDIR)/src/streaming/video -I$(TOPDIR)/src/streaming/audio \ + -I$(TOPDIR)/src/nanogui_resources \ + -I$(TOPDIR)/src/ui -I$(TOPDIR)/src/ui/buttons -I$(TOPDIR)/src/ui/windows \ + -I$(TOPDIR)/libgamestream \ + -I$(TOPDIR)/third_party/moonlight-common-c/reedsolomon \ + -I$(TOPDIR)/third_party/moonlight-common-c/src \ + -I$(TOPDIR)/third_party/moonlight-common-c/enet/include \ + -I$(TOPDIR)/third_party/nanogui/include \ + -I$(TOPDIR)/third_party/nanogui/ext/nanovg/src \ + -I$(TOPDIR)/third_party/json/single_include/nlohmann + +DEFINES := -DNANOGUI_USE_OPENGL -DNVG_STB_IMAGE_IMPLEMENTATION -DNANOGUI_NO_GLFW \ + -DHAS_SOCKLEN_T -DHAS_POLL -DHAS_FCNTL -D_GNU_SOURCE + +CFLAGS := -g -Wall -O0 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) $(M_INCLUDES) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -std=gnu++17 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto \ + -lavcodec -lavutil -lopus -lssl -lcrypto -lbz2 -lz -lexpat -lm \ + -lglad -lEGL -lglapi -ldrm_nouveau -lglfw3 \ + -lnx -lwebp -lswresample -lavformat -lvpx \ + -L/Users/rock88/Documents/Projects/Switch/moonlight-switch-new/deps/lib + +LIBGAMESTREAM_SOURCES = \ + client.c \ + http.c \ + mkcert.c \ + xml.c + +MOONLIGHT_LIBRETRO_C_SOURCES = \ + nanogui_resources.c \ + moonlight_libnx.c \ + moonlight_glfw.c + +MOONLIGHT_LIBRETRO_CXX_SOURCES = \ + AddHostWindow.cpp \ + ContentWindow.cpp \ + MainWindow.cpp \ + StreamWindow.cpp \ + AppListWindow.cpp \ + SettingsWindow.cpp \ + AddHostButton.cpp \ + AppButton.cpp \ + HostButton.cpp \ + Application.cpp \ + LoadingOverlay.cpp \ + GameStreamClient.cpp \ + Settings.cpp \ + InputController.cpp \ + MoonlightSession.cpp \ + FFmpegVideoDecoder.cpp \ + GLVideoRenderer.cpp + +MOONLIGHT_COMMON_C_SOURCES = \ + callbacks.c \ + compress.c \ + host.c \ + list.c \ + packet.c \ + peer.c \ + protocol.c \ + unix.c \ + win32.c \ + rs.c \ + AudioStream.c \ + ByteBuffer.c \ + Connection.c \ + ControlStream.c \ + FakeCallbacks.c \ + InputStream.c \ + LinkedBlockingQueue.c \ + Misc.c \ + Platform.c \ + PlatformSockets.c \ + RtpFecQueue.c \ + RtpReorderQueue.c \ + RtspConnection.c \ + RtspParser.c \ + SdpGenerator.c \ + SimpleStun.c \ + VideoDepacketizer.c \ + VideoStream.c + +NANOGUI_C_SOURCES = \ + nanovg.c + +NANOGUI_CXX_SOURCES = \ + widget.cpp \ + button.cpp \ + common.cpp \ + screen.cpp \ + checkbox.cpp \ + vscrollpanel.cpp \ + colorpicker.cpp \ + textarea.cpp \ + shader_gl.cpp \ + canvas.cpp \ + window.cpp \ + graph.cpp \ + popup.cpp \ + layout.cpp \ + texture.cpp \ + texture_gl.cpp \ + tabwidget.cpp \ + shader.cpp \ + imageview.cpp \ + progressbar.cpp \ + combobox.cpp \ + theme.cpp \ + traits.cpp \ + label.cpp \ + opengl.cpp \ + renderpass_gl.cpp \ + imagepanel.cpp \ + colorwheel.cpp \ + messagedialog.cpp \ + textbox.cpp \ + slider.cpp \ + popupbutton.cpp + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(LIBGAMESTREAM_SOURCES) $(MOONLIGHT_LIBRETRO_C_SOURCES) $(MOONLIGHT_COMMON_C_SOURCES) $(NANOGUI_C_SOURCES) +CPPFILES := $(MOONLIGHT_LIBRETRO_CXX_SOURCES) $(NANOGUI_CXX_SOURCES) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.libnx + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... +ifeq ($(strip $(APP_JSON)),) + @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf +else + @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf +endif + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(APP_JSON)),) + +all : $(OUTPUT).nro + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +else + +all : $(OUTPUT).nsp + +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).nso : $(OUTPUT).elf + +endif + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/dbg.sh b/dbg.sh new file mode 100755 index 0000000..355336e --- /dev/null +++ b/dbg.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/opt/devkitpro/devkitA64/bin/aarch64-none-elf-addr2line -e moonlight.elf -f -p -C -a $1 diff --git a/libgamestream/client.c b/libgamestream/client.c index 41472ac..66d6efe 100644 --- a/libgamestream/client.c +++ b/libgamestream/client.c @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -38,6 +37,7 @@ #include #include #include +#include "Log.h" #define UNIQUE_FILE_NAME "uniqueid.dat" #define P12_FILE_NAME "client.p12" @@ -58,6 +58,35 @@ static EVP_PKEY *privateKey; const char* gs_error; +typedef unsigned char uuid_t[16]; +typedef uuid_t uuid; + +uid_t getuid() { + return 1; +} + +void simple_uuid_generate_random(uuid_t out) { + static bool once = false; + if (!once) { + srand(time(NULL)); + once = true; + } + + for (int i = 0; i < 16; i++) { + out[i] = (rand() % 16) * 10 + rand() % 16; + } +} + +void simple_uuid_unparse(const uuid_t uu, char *out) { + sprintf(out, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", + uu[0], uu[1], uu[2], uu[3], uu[4], uu[5], uu[6], uu[7], + uu[8], uu[9], uu[10], uu[11], uu[12], uu[13], uu[14], uu[15]); + LOG_FMT("out = %s\n", out); +} + +#define uuid_generate_random simple_uuid_generate_random +#define uuid_unparse simple_uuid_unparse + int mkdirtree(const char* directory) { char buffer[PATH_MAX]; char* p = buffer; @@ -86,211 +115,211 @@ int mkdirtree(const char* directory) { return 0; } - -static int load_unique_id(const char* keyDirectory) { - char uniqueFilePath[PATH_MAX]; - snprintf(uniqueFilePath, PATH_MAX, "%s/%s", keyDirectory, UNIQUE_FILE_NAME); - - FILE *fd = fopen(uniqueFilePath, "r"); - if (fd == NULL) { - unsigned char unique_data[UNIQUEID_BYTES]; - RAND_bytes(unique_data, UNIQUEID_BYTES); - for (int i = 0; i < UNIQUEID_BYTES; i++) { - sprintf(unique_id + (i * 2), "%02x", unique_data[i]); - } - fd = fopen(uniqueFilePath, "w"); - if (fd == NULL) - return GS_FAILED; - - fwrite(unique_id, UNIQUEID_CHARS, 1, fd); - } else { - fread(unique_id, UNIQUEID_CHARS, 1, fd); - } - fclose(fd); - unique_id[UNIQUEID_CHARS] = 0; - - return GS_OK; -} - -static int load_cert(const char* keyDirectory) { - char certificateFilePath[PATH_MAX]; - snprintf(certificateFilePath, PATH_MAX, "%s/%s", keyDirectory, CERTIFICATE_FILE_NAME); - - char keyFilePath[PATH_MAX]; - snprintf(&keyFilePath[0], PATH_MAX, "%s/%s", keyDirectory, KEY_FILE_NAME); - - FILE *fd = fopen(certificateFilePath, "r"); - if (fd == NULL) { - printf("Generating certificate..."); - CERT_KEY_PAIR cert = mkcert_generate(); - printf("done\n"); - - char p12FilePath[PATH_MAX]; - snprintf(p12FilePath, PATH_MAX, "%s/%s", keyDirectory, P12_FILE_NAME); - - mkcert_save(certificateFilePath, p12FilePath, keyFilePath, cert); - mkcert_free(cert); - fd = fopen(certificateFilePath, "r"); - } - - if (fd == NULL) { - gs_error = "Can't open certificate file"; - return GS_FAILED; - } - - if (!(cert = PEM_read_X509(fd, NULL, NULL, NULL))) { - gs_error = "Error loading cert into memory"; - return GS_FAILED; - } - - rewind(fd); - - int c; - int length = 0; - while ((c = fgetc(fd)) != EOF) { - sprintf(cert_hex + length, "%02x", c); - length += 2; - } - cert_hex[length] = 0; - - fclose(fd); - - fd = fopen(keyFilePath, "r"); - if (fd == NULL) { - gs_error = "Error loading key into memory"; - return GS_FAILED; - } - - PEM_read_PrivateKey(fd, &privateKey, NULL, NULL); - fclose(fd); - - return GS_OK; -} - -static int load_server_status(PSERVER_DATA server) { - - uuid_t uuid; - char uuid_str[37]; - - int ret; - char url[4096]; - int i; - - i = 0; - do { - char *pairedText = NULL; - char *currentGameText = NULL; - char *stateText = NULL; - char *serverCodecModeSupportText = NULL; - - 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. - - printf("server->serverInfo.address: %s\n", server->serverInfo.address); - printf("unique_id: %s\n", unique_id); - printf("uuid_str: %s\n", uuid_str); - - snprintf(url, sizeof(url), "%s://%s:%d/serverinfo?uniqueid=%s&uuid=%s", - i == 0 ? "https" : "http", server->serverInfo.address, i == 0 ? 47984 : 47989, 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", ¤tGameText) != 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, "hostname", &server->hostname) != 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); - - if (ret == GS_OK && !server->unsupported) { - if (server->serverMajorVersion > MAX_SUPPORTED_GFE_VERSION) { - gs_error = "Ensure you're running the latest version of Moonlight Embedded or downgrade GeForce Experience and try again"; - ret = GS_UNSUPPORTED_VERSION; - } else if (server->serverMajorVersion < MIN_SUPPORTED_GFE_VERSION) { - gs_error = "Moonlight Embedded requires a newer version of GeForce Experience. Please upgrade GFE on your PC and try again."; - ret = GS_UNSUPPORTED_VERSION; - } - } - - return ret; -} +// +//static int load_unique_id(const char* keyDirectory) { +// char uniqueFilePath[PATH_MAX]; +// snprintf(uniqueFilePath, PATH_MAX, "%s/%s", keyDirectory, UNIQUE_FILE_NAME); +// +// FILE *fd = fopen(uniqueFilePath, "r"); +// if (fd == NULL) { +// unsigned char unique_data[UNIQUEID_BYTES]; +// RAND_bytes(unique_data, UNIQUEID_BYTES); +// for (int i = 0; i < UNIQUEID_BYTES; i++) { +// sprintf(unique_id + (i * 2), "%02x", unique_data[i]); +// } +// fd = fopen(uniqueFilePath, "w"); +// if (fd == NULL) +// return GS_FAILED; +// +// fwrite(unique_id, UNIQUEID_CHARS, 1, fd); +// } else { +// fread(unique_id, UNIQUEID_CHARS, 1, fd); +// } +// fclose(fd); +// unique_id[UNIQUEID_CHARS] = 0; +// +// return GS_OK; +//} +// +//static int load_cert(const char* keyDirectory) { +// char certificateFilePath[PATH_MAX]; +// snprintf(certificateFilePath, PATH_MAX, "%s/%s", keyDirectory, CERTIFICATE_FILE_NAME); +// +// char keyFilePath[PATH_MAX]; +// snprintf(&keyFilePath[0], PATH_MAX, "%s/%s", keyDirectory, KEY_FILE_NAME); +// +// FILE *fd = fopen(certificateFilePath, "r"); +// if (fd == NULL) { +// printf("Generating certificate..."); +// CERT_KEY_PAIR cert = mkcert_generate(); +// printf("done\n"); +// +// char p12FilePath[PATH_MAX]; +// snprintf(p12FilePath, PATH_MAX, "%s/%s", keyDirectory, P12_FILE_NAME); +// +// mkcert_save(certificateFilePath, p12FilePath, keyFilePath, cert); +// mkcert_free(cert); +// fd = fopen(certificateFilePath, "r"); +// } +// +// if (fd == NULL) { +// gs_error = "Can't open certificate file"; +// return GS_FAILED; +// } +// +// if (!(cert = PEM_read_X509(fd, NULL, NULL, NULL))) { +// gs_error = "Error loading cert into memory"; +// return GS_FAILED; +// } +// +// rewind(fd); +// +// int c; +// int length = 0; +// while ((c = fgetc(fd)) != EOF) { +// sprintf(cert_hex + length, "%02x", c); +// length += 2; +// } +// cert_hex[length] = 0; +// +// fclose(fd); +// +// fd = fopen(keyFilePath, "r"); +// if (fd == NULL) { +// gs_error = "Error loading key into memory"; +// return GS_FAILED; +// } +// +// PEM_read_PrivateKey(fd, &privateKey, NULL, NULL); +// fclose(fd); +// +// return GS_OK; +//} +// +//static int load_server_status(PSERVER_DATA server) { +// +// uuid_t uuid; +// char uuid_str[37]; +// +// int ret; +// char url[4096]; +// int i; +// +// i = 0; +// do { +// char *pairedText = NULL; +// char *currentGameText = NULL; +// char *stateText = NULL; +// char *serverCodecModeSupportText = NULL; +// +// 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. +// +// printf("server->serverInfo.address: %s\n", server->serverInfo.address); +// printf("unique_id: %s\n", unique_id); +// printf("uuid_str: %s\n", uuid_str); +// +// snprintf(url, sizeof(url), "%s://%s:%d/serverinfo?uniqueid=%s&uuid=%s", +// i == 0 ? "https" : "http", server->serverInfo.address, i == 0 ? 47984 : 47989, 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", ¤tGameText) != 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, "hostname", &server->hostname) != 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); +// +// if (ret == GS_OK && !server->unsupported) { +// if (server->serverMajorVersion > MAX_SUPPORTED_GFE_VERSION) { +// gs_error = "Ensure you're running the latest version of Moonlight Embedded or downgrade GeForce Experience and try again"; +// ret = GS_UNSUPPORTED_VERSION; +// } else if (server->serverMajorVersion < MIN_SUPPORTED_GFE_VERSION) { +// gs_error = "Moonlight Embedded requires a newer version of GeForce Experience. Please upgrade GFE on your PC and try again."; +// ret = GS_UNSUPPORTED_VERSION; +// } +// } +// +// return ret; +//} static void bytes_to_hex(unsigned char *in, char *out, size_t len) { for (int i = 0; i < len; i++) { @@ -300,46 +329,50 @@ static void bytes_to_hex(unsigned char *in, char *out, size_t len) { } static int sign_it(const char *msg, size_t mlen, unsigned char **sig, size_t *slen, EVP_PKEY *pkey) { + LOG_FMT("mlen = %i\n", mlen); int result = GS_FAILED; *sig = NULL; *slen = 0; - EVP_MD_CTX *ctx = EVP_MD_CTX_create(); + EVP_MD_CTX *ctx = EVP_MD_CTX_create(); DEBUG_EMPTY_LOG if (ctx == NULL) return GS_FAILED; - const EVP_MD *md = EVP_get_digestbyname("SHA256"); + const EVP_MD *md = EVP_get_digestbyname("SHA256");DEBUG_EMPTY_LOG if (md == NULL) goto cleanup; - int rc = EVP_DigestInit_ex(ctx, md, NULL); + int rc = EVP_DigestInit_ex(ctx, md, NULL);DEBUG_EMPTY_LOG if (rc != 1) goto cleanup; - rc = EVP_DigestSignInit(ctx, NULL, md, NULL, pkey); + rc = EVP_DigestSignInit(ctx, NULL, md, NULL, pkey);DEBUG_EMPTY_LOG if (rc != 1) goto cleanup; - rc = EVP_DigestSignUpdate(ctx, msg, mlen); + rc = EVP_DigestSignUpdate(ctx, msg, mlen);DEBUG_EMPTY_LOG if (rc != 1) goto cleanup; size_t req = 0; - rc = EVP_DigestSignFinal(ctx, NULL, &req); + rc = EVP_DigestSignFinal(ctx, NULL, &req);DEBUG_EMPTY_LOG if (rc != 1 || !(req > 0)) goto cleanup; - *sig = OPENSSL_malloc(req); + *sig = OPENSSL_malloc(req);DEBUG_EMPTY_LOG if (*sig == NULL) goto cleanup; *slen = req; - rc = EVP_DigestSignFinal(ctx, *sig, slen); + rc = EVP_DigestSignFinal(ctx, *sig, slen);DEBUG_EMPTY_LOG + //LOG_FMT("msg = %s\n", msg); + LOG_FMT("rc = %i\n", rc); if (rc != 1 || req != *slen) goto cleanup; result = GS_OK; + DEBUG_EMPTY_LOG cleanup: EVP_MD_CTX_destroy(ctx); @@ -499,8 +532,8 @@ int gs_pair(PSERVER_DATA server, char* pin) { goto cleanup; } - char challenge_response_data_enc[48]; - char challenge_response_data[48]; + char challenge_response_data_enc[64]; + char challenge_response_data[64]; for (int count = 0; count < strlen(result); count += 2) { sscanf(&result[count], "%2hhx", &challenge_response_data_enc[count / 2]); } @@ -791,19 +824,19 @@ int gs_quit_app(PSERVER_DATA server) { http_free_data(data); return ret; } - -int gs_init(PSERVER_DATA server, char *address, const char *keyDirectory, int log_level, bool unsupported) { - mkdirtree(keyDirectory); - if (load_unique_id(keyDirectory) != GS_OK) - return GS_FAILED; - - if (load_cert(keyDirectory)) - return GS_FAILED; - - http_init(keyDirectory, log_level); - - LiInitializeServerInformation(&server->serverInfo); - server->serverInfo.address = address; - server->unsupported = unsupported; - return load_server_status(server); -} +// +//int gs_init(PSERVER_DATA server, char *address, const char *keyDirectory, int log_level, bool unsupported) { +// mkdirtree(keyDirectory); +// if (load_unique_id(keyDirectory) != GS_OK) +// return GS_FAILED; +// +// if (load_cert(keyDirectory)) +// return GS_FAILED; +// +// http_init(keyDirectory, log_level); +// +// LiInitializeServerInformation(&server->serverInfo); +// server->serverInfo.address = address; +// server->unsupported = unsupported; +// return load_server_status(server); +//} diff --git a/libgamestream/client.h b/libgamestream/client.h index 96a637a..1ff64ed 100644 --- a/libgamestream/client.h +++ b/libgamestream/client.h @@ -43,7 +43,7 @@ typedef struct _SERVER_DATA { } SERVER_DATA, *PSERVER_DATA; int mkdirtree(const char* directory); -int gs_init(PSERVER_DATA server, char* address, const char *keyDirectory, int logLevel, bool unsupported); +//int gs_init(PSERVER_DATA server, char* address, const char *keyDirectory, int logLevel, bool unsupported); int gs_app_boxart(PSERVER_DATA server, int app_id, char **art_data, size_t *art_data_size); 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); diff --git a/libgamestream/http.h b/libgamestream/http.h index f06ace1..eee1edc 100644 --- a/libgamestream/http.h +++ b/libgamestream/http.h @@ -29,7 +29,7 @@ typedef struct _HTTP_DATA { size_t size; } HTTP_DATA, *PHTTP_DATA; -int http_init(const char* keyDirectory, int logLevel); +//int http_init(const char* keyDirectory, int logLevel); PHTTP_DATA http_create_data(); int http_request(char* url, PHTTP_DATA data); void http_free_data(PHTTP_DATA data); diff --git a/libgamestream/mkcert.c b/libgamestream/mkcert.c index 53c72f3..0a921eb 100644 --- a/libgamestream/mkcert.c +++ b/libgamestream/mkcert.c @@ -33,31 +33,26 @@ static const int SERIAL = 0; static const int NUM_YEARS = 10; int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years); -int add_ext(X509 *cert, int nid, char *value); CERT_KEY_PAIR mkcert_generate() { BIO *bio_err; X509 *x509 = NULL; EVP_PKEY *pkey = NULL; PKCS12 *p12 = NULL; - + CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); - - OpenSSL_add_all_algorithms(); - + mkcert(&x509, &pkey, NUM_BITS, SERIAL, NUM_YEARS); - + p12 = PKCS12_create("limelight", "GameStream", pkey, x509, NULL, 0, 0, 0, 0, 0); - -#ifndef OPENSSL_NO_ENGINE - ENGINE_cleanup(); -#endif - CRYPTO_cleanup_all_ex_data(); - + if (p12 == NULL) { + printf("Error generating a valid PKCS12 certificate.\n"); + } + BIO_free(bio_err); - - return (CERT_KEY_PAIR) {x509, pkey, p12}; + + return (CERT_KEY_PAIR){x509, pkey, p12}; } void mkcert_free(CERT_KEY_PAIR certKeyPair) { @@ -70,117 +65,61 @@ void mkcert_save(const char* certFile, const char* p12File, const char* keyPairF FILE* certFilePtr = fopen(certFile, "w"); FILE* keyPairFilePtr = fopen(keyPairFile, "w"); FILE* p12FilePtr = fopen(p12File, "wb"); - + //TODO: error check PEM_write_PrivateKey(keyPairFilePtr, certKeyPair.pkey, NULL, NULL, 0, NULL, NULL); PEM_write_X509(certFilePtr, certKeyPair.x509); i2d_PKCS12_fp(p12FilePtr, certKeyPair.p12); - + fclose(p12FilePtr); fclose(certFilePtr); fclose(keyPairFilePtr); } int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) { - X509 *x; - EVP_PKEY *pk; - RSA *rsa; - X509_NAME *name = NULL; - - if (*pkeyp == NULL) { - if ((pk=EVP_PKEY_new()) == NULL) { - abort(); - return(0); - } - } else { - pk = *pkeyp; - } - - if (*x509p == NULL) { - if ((x = X509_new()) == NULL) { - goto err; - } - } else { - x = *x509p; - } - - if ((rsa = RSA_new()) == NULL) - goto err; - + X509* cert = X509_new(); + EVP_PKEY* pk = EVP_PKEY_new(); BIGNUM* bne = BN_new(); - if (bne == NULL) { - abort(); - goto err; - } - + RSA* rsa = RSA_new(); + BN_set_word(bne, RSA_F4); - if (RSA_generate_key_ex(rsa, bits, bne, NULL) == 0) { - abort(); - goto err; - } - - if (!EVP_PKEY_assign_RSA(pk, rsa)) { - abort(); - goto err; - } - - X509_set_version(x, 2); - ASN1_INTEGER_set(X509_get_serialNumber(x), serial); - X509_gmtime_adj(X509_get_notBefore(x), 0); - X509_gmtime_adj(X509_get_notAfter(x), (long)60*60*24*365*years); - X509_set_pubkey(x, pk); - - name = X509_get_subject_name(x); - - /* This function creates and adds the entry, working out the - * correct string type and performing checks on its length. - */ - X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, (unsigned char*)"NVIDIA GameStream Client", -1, -1, 0); - - /* Its self signed so set the issuer name to be the same as the - * subject. - */ - X509_set_issuer_name(x, name); - - /* Add various extensions: standard extensions */ - add_ext(x, NID_key_usage, "critical,digitalSignature,keyEncipherment"); - - add_ext(x, NID_subject_key_identifier, "hash"); - - if (!X509_sign(x, pk, EVP_sha256())) { - goto err; - } - - *x509p = x; + RSA_generate_key_ex(rsa, bits, bne, NULL); + + EVP_PKEY_assign_RSA(pk, rsa); + + X509_set_version(cert, 2); + ASN1_INTEGER_set(X509_get_serialNumber(cert), serial); +#if OPENSSL_VERSION_NUMBER < 0x10100000L + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), 60 * 60 * 24 * 365 * years); +#else + ASN1_TIME* before = ASN1_STRING_dup(X509_get0_notBefore(cert)); + ASN1_TIME* after = ASN1_STRING_dup(X509_get0_notAfter(cert)); + + X509_gmtime_adj(before, 0); + X509_gmtime_adj(after, 60 * 60 * 24 * 365 * years); + + X509_set1_notBefore(cert, before); + X509_set1_notAfter(cert, after); + + ASN1_STRING_free(before); + ASN1_STRING_free(after); +#endif + + X509_set_pubkey(cert, pk); + + X509_NAME* name = X509_get_subject_name(cert); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (const unsigned char*)"NVIDIA GameStream Client", + -1, -1, 0); + X509_set_issuer_name(cert, name); + + X509_sign(cert, pk, EVP_sha256()); + + BN_free(bne); + + *x509p = cert; *pkeyp = pk; - - return(1); -err: - return(0); -} - -/* Add extension using V3 code: we can set the config file as NULL - * because we wont reference any other sections. - */ - -int add_ext(X509 *cert, int nid, char *value) -{ - X509_EXTENSION *ex; - X509V3_CTX ctx; - /* This sets the 'context' of the extensions. */ - /* No configuration database */ - X509V3_set_ctx_nodb(&ctx); - /* Issuer and subject certs: both the target since it is self signed, - * no request and no CRL - */ - X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0); - ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value); - if (!ex) { - return 0; - } - - X509_add_ext(cert, ex, -1); - X509_EXTENSION_free(ex); + return 1; } - diff --git a/moonlight.xcodeproj/project.pbxproj b/moonlight.xcodeproj/project.pbxproj index 7cc2eea..797e746 100644 --- a/moonlight.xcodeproj/project.pbxproj +++ b/moonlight.xcodeproj/project.pbxproj @@ -85,11 +85,21 @@ 3652F08A245C8569001FABF3 /* ContentWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3652F089245C8569001FABF3 /* ContentWindow.cpp */; }; 3661D2F92469D1940060EE24 /* FFmpegVideoDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3661D2F72469D1940060EE24 /* FFmpegVideoDecoder.cpp */; }; 3661D2FF2469E0C00060EE24 /* GLVideoRenderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3661D2FD2469E0C00060EE24 /* GLVideoRenderer.cpp */; }; - 3661D3022469EFFA0060EE24 /* RetroAudioRenderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3661D3002469EFFA0060EE24 /* RetroAudioRenderer.cpp */; }; 367CD95A245DE25F00A95738 /* StreamWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 367CD958245DE25F00A95738 /* StreamWindow.cpp */; }; 36A0C0372461DBA30083289C /* AddHostButton.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A0C0352461DBA30083289C /* AddHostButton.cpp */; }; 36A0C03A2461E4C00083289C /* SettingsWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A0C0382461E4C00083289C /* SettingsWindow.cpp */; }; 36A0C03D2461F03C0083289C /* Settings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A0C03B2461F03C0083289C /* Settings.cpp */; }; + 36ADAB6A246F171D0058D1D6 /* RetroAudioRenderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3661D3002469EFFA0060EE24 /* RetroAudioRenderer.cpp */; }; + 36ADABB0246F262D0058D1D6 /* compare.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADAB90246F262D0058D1D6 /* compare.c */; }; + 36ADABB1246F262D0058D1D6 /* unpack.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADAB97246F262D0058D1D6 /* unpack.c */; }; + 36ADABB2246F262D0058D1D6 /* isnull.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADAB99246F262D0058D1D6 /* isnull.c */; }; + 36ADABB3246F262D0058D1D6 /* copy.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADAB9B246F262D0058D1D6 /* copy.c */; }; + 36ADABB4246F262D0058D1D6 /* unparse.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADAB9F246F262D0058D1D6 /* unparse.c */; }; + 36ADABB5246F262D0058D1D6 /* pack.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADABA2246F262D0058D1D6 /* pack.c */; }; + 36ADABB6246F262D0058D1D6 /* clear.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADABA5246F262D0058D1D6 /* clear.c */; }; + 36ADABB7246F262D0058D1D6 /* randutils.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADABA6246F262D0058D1D6 /* randutils.c */; }; + 36ADABB9246F262D0058D1D6 /* gen_uuid.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADABA9246F262D0058D1D6 /* gen_uuid.c */; }; + 36ADABBA246F262D0058D1D6 /* parse.c in Sources */ = {isa = PBXBuildFile; fileRef = 36ADABAA246F262D0058D1D6 /* parse.c */; }; 36B406982459F5CB005BD903 /* moonlight_glfw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36B406962459F460005BD903 /* moonlight_glfw.cpp */; }; 36D3F8442469B5C400CDEF9B /* MoonlightSession.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36D3F8422469B5C400CDEF9B /* MoonlightSession.cpp */; }; 36DBDE9A2450BCD50057C8D3 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36DBDE992450BCD50057C8D3 /* CoreFoundation.framework */; }; @@ -100,6 +110,10 @@ 36DFE0CE2459FAB100FC51CE /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36DBDEA22450C2640057C8D3 /* OpenGL.framework */; }; 36DFE217245A278700FC51CE /* glsym_gl.c in Sources */ = {isa = PBXBuildFile; fileRef = 36DFE0DB245A1FEC00FC51CE /* glsym_gl.c */; }; 36DFE218245A278900FC51CE /* rglgen.c in Sources */ = {isa = PBXBuildFile; fileRef = 36DFE0DE245A1FEC00FC51CE /* rglgen.c */; }; + 36E6378C246FFFF30032F5FB /* CryptoManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36E6378A246FFFF30032F5FB /* CryptoManager.cpp */; }; + 36E63790247010C70032F5FB /* Data.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36E6378E247010C70032F5FB /* Data.cpp */; }; + 36E63794247030290032F5FB /* NetworkClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36E63792247030290032F5FB /* NetworkClient.cpp */; }; + 36E63797247036A30032F5FB /* ServerInfoRequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36E63795247036A30032F5FB /* ServerInfoRequest.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -285,6 +299,8 @@ 3661D30B246B1C680060EE24 /* build_ffmpeg_lakka_switch.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_ffmpeg_lakka_switch.sh; sourceTree = ""; }; 367CD958245DE25F00A95738 /* StreamWindow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StreamWindow.cpp; sourceTree = ""; }; 367CD959245DE25F00A95738 /* StreamWindow.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StreamWindow.hpp; sourceTree = ""; }; + 369445A82466CE2700786D0A /* Makefile.libnx */ = {isa = PBXFileReference; explicitFileType = sourcecode.make; path = Makefile.libnx; sourceTree = ""; }; + 369445A92466E2B000786D0A /* moonlight_libnx.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = moonlight_libnx.c; sourceTree = ""; }; 36A0C0352461DBA30083289C /* AddHostButton.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AddHostButton.cpp; sourceTree = ""; }; 36A0C0362461DBA30083289C /* AddHostButton.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AddHostButton.hpp; sourceTree = ""; }; 36A0C0382461E4C00083289C /* SettingsWindow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SettingsWindow.cpp; sourceTree = ""; }; @@ -292,6 +308,22 @@ 36A0C03B2461F03C0083289C /* Settings.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Settings.cpp; sourceTree = ""; }; 36A0C03C2461F03C0083289C /* Settings.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Settings.hpp; sourceTree = ""; }; 36A0C03E2461FFF10083289C /* build_opus_lakka_switch.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_opus_lakka_switch.sh; sourceTree = ""; }; + 36ADAB90246F262D0058D1D6 /* compare.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = compare.c; sourceTree = ""; }; + 36ADAB94246F262D0058D1D6 /* uuid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uuid.h; sourceTree = ""; }; + 36ADAB96246F262D0058D1D6 /* randutils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = randutils.h; sourceTree = ""; }; + 36ADAB97246F262D0058D1D6 /* unpack.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unpack.c; sourceTree = ""; }; + 36ADAB98246F262D0058D1D6 /* uuidd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uuidd.h; sourceTree = ""; }; + 36ADAB99246F262D0058D1D6 /* isnull.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = isnull.c; sourceTree = ""; }; + 36ADAB9B246F262D0058D1D6 /* copy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = copy.c; sourceTree = ""; }; + 36ADAB9F246F262D0058D1D6 /* unparse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unparse.c; sourceTree = ""; }; + 36ADABA1246F262D0058D1D6 /* c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = c.h; sourceTree = ""; }; + 36ADABA2246F262D0058D1D6 /* pack.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pack.c; sourceTree = ""; }; + 36ADABA5246F262D0058D1D6 /* clear.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = clear.c; sourceTree = ""; }; + 36ADABA6246F262D0058D1D6 /* randutils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = randutils.c; sourceTree = ""; }; + 36ADABA8246F262D0058D1D6 /* uuid_time.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = uuid_time.c; sourceTree = ""; }; + 36ADABA9246F262D0058D1D6 /* gen_uuid.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gen_uuid.c; sourceTree = ""; }; + 36ADABAA246F262D0058D1D6 /* parse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = parse.c; sourceTree = ""; }; + 36ADABAF246F262D0058D1D6 /* uuidP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uuidP.h; sourceTree = ""; }; 36B406962459F460005BD903 /* moonlight_glfw.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moonlight_glfw.cpp; sourceTree = ""; }; 36D3F8422469B5C400CDEF9B /* MoonlightSession.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MoonlightSession.cpp; sourceTree = ""; }; 36D3F8432469B5C400CDEF9B /* MoonlightSession.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MoonlightSession.hpp; sourceTree = ""; }; @@ -316,6 +348,14 @@ 36DFE0DF245A1FEC00FC51CE /* rglgen_headers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rglgen_headers.h; sourceTree = ""; }; 36DFE0E1245A1FEC00FC51CE /* glsym_es3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glsym_es3.h; sourceTree = ""; }; 36DFE0E3245A1FEC00FC51CE /* glsym_gl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glsym_gl.h; sourceTree = ""; }; + 36E6378A246FFFF30032F5FB /* CryptoManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CryptoManager.cpp; sourceTree = ""; }; + 36E6378B246FFFF30032F5FB /* CryptoManager.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CryptoManager.hpp; sourceTree = ""; }; + 36E6378E247010C70032F5FB /* Data.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Data.cpp; sourceTree = ""; }; + 36E6378F247010C70032F5FB /* Data.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Data.hpp; sourceTree = ""; }; + 36E63792247030290032F5FB /* NetworkClient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NetworkClient.cpp; sourceTree = ""; }; + 36E63793247030290032F5FB /* NetworkClient.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NetworkClient.hpp; sourceTree = ""; }; + 36E63795247036A30032F5FB /* ServerInfoRequest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ServerInfoRequest.cpp; sourceTree = ""; }; + 36E63796247036A30032F5FB /* ServerInfoRequest.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ServerInfoRequest.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -623,9 +663,34 @@ path = video; sourceTree = ""; }; + 36ADAB8F246F262D0058D1D6 /* libuuid */ = { + isa = PBXGroup; + children = ( + 36ADAB90246F262D0058D1D6 /* compare.c */, + 36ADAB94246F262D0058D1D6 /* uuid.h */, + 36ADAB96246F262D0058D1D6 /* randutils.h */, + 36ADAB97246F262D0058D1D6 /* unpack.c */, + 36ADAB98246F262D0058D1D6 /* uuidd.h */, + 36ADAB99246F262D0058D1D6 /* isnull.c */, + 36ADAB9B246F262D0058D1D6 /* copy.c */, + 36ADAB9F246F262D0058D1D6 /* unparse.c */, + 36ADABA1246F262D0058D1D6 /* c.h */, + 36ADABA2246F262D0058D1D6 /* pack.c */, + 36ADABA5246F262D0058D1D6 /* clear.c */, + 36ADABA6246F262D0058D1D6 /* randutils.c */, + 36ADABA8246F262D0058D1D6 /* uuid_time.c */, + 36ADABA9246F262D0058D1D6 /* gen_uuid.c */, + 36ADABAA246F262D0058D1D6 /* parse.c */, + 36ADABAF246F262D0058D1D6 /* uuidP.h */, + ); + path = libuuid; + sourceTree = ""; + }; 36B406932459F41E005BD903 /* src */ = { isa = PBXGroup; children = ( + 36E6379124702FEC0032F5FB /* network */, + 36E63789246FFFDC0032F5FB /* crypto */, 36DFE0D5245A1FEC00FC51CE /* glsym */, 36DFE0CA2459FA3F00FC51CE /* nanogui_resources */, 36DFDCF12459F79000FC51CE /* ui */, @@ -640,6 +705,7 @@ 3652F084245C6CFC001FABF3 /* libretro.h */, 3652F085245C6CFC001FABF3 /* moonlight_libretro.cpp */, 36B406962459F460005BD903 /* moonlight_glfw.cpp */, + 369445A92466E2B000786D0A /* moonlight_libnx.c */, ); path = src; sourceTree = ""; @@ -680,6 +746,7 @@ isa = PBXGroup; children = ( 3602C3C0245DC7E300368900 /* Makefile */, + 369445A82466CE2700786D0A /* Makefile.libnx */, 36A0C03E2461FFF10083289C /* build_opus_lakka_switch.sh */, 3661D30B246B1C680060EE24 /* build_ffmpeg_lakka_switch.sh */, 3652F006245C2918001FABF3 /* libgamestream */, @@ -727,6 +794,7 @@ 36DFDCF62459F80600FC51CE /* third_party */ = { isa = PBXGroup; children = ( + 36ADAB8F246F262D0058D1D6 /* libuuid */, 3652F014245C292B001FABF3 /* moonlight-common-c */, 3652ECC3245B3AFF001FABF3 /* nanogui */, ); @@ -757,6 +825,28 @@ path = glsym; sourceTree = ""; }; + 36E63789246FFFDC0032F5FB /* crypto */ = { + isa = PBXGroup; + children = ( + 36E6378E247010C70032F5FB /* Data.cpp */, + 36E6378F247010C70032F5FB /* Data.hpp */, + 36E6378A246FFFF30032F5FB /* CryptoManager.cpp */, + 36E6378B246FFFF30032F5FB /* CryptoManager.hpp */, + ); + path = crypto; + sourceTree = ""; + }; + 36E6379124702FEC0032F5FB /* network */ = { + isa = PBXGroup; + children = ( + 36E63792247030290032F5FB /* NetworkClient.cpp */, + 36E63793247030290032F5FB /* NetworkClient.hpp */, + 36E63795247036A30032F5FB /* ServerInfoRequest.cpp */, + 36E63796247036A30032F5FB /* ServerInfoRequest.hpp */, + ); + path = network; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -835,11 +925,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3661D3022469EFFA0060EE24 /* RetroAudioRenderer.cpp in Sources */, + 36E63794247030290032F5FB /* NetworkClient.cpp in Sources */, 36DFE218245A278900FC51CE /* rglgen.c in Sources */, 3652F075245C292B001FABF3 /* VideoStream.c in Sources */, 3652EFE0245B3B00001FABF3 /* imageview.cpp in Sources */, 3652EFDB245B3B00001FABF3 /* texture.cpp in Sources */, + 36ADABB6246F262D0058D1D6 /* clear.c in Sources */, 36A0C03D2461F03C0083289C /* Settings.cpp in Sources */, 3652F079245C292B001FABF3 /* RtpReorderQueue.c in Sources */, 3652F065245C292B001FABF3 /* list.c in Sources */, @@ -853,8 +944,11 @@ 3652EFDE245B3B00001FABF3 /* tabwidget.cpp in Sources */, 3652F07A245C292B001FABF3 /* SdpGenerator.c in Sources */, 3652EFEB245B3B00001FABF3 /* imagepanel.cpp in Sources */, + 36ADABB7246F262D0058D1D6 /* randutils.c in Sources */, + 36E63790247010C70032F5FB /* Data.cpp in Sources */, 36A0C0372461DBA30083289C /* AddHostButton.cpp in Sources */, 3652F06E245C292B001FABF3 /* rs.c in Sources */, + 36ADABB2246F262D0058D1D6 /* isnull.c in Sources */, 3652EFD1245B3B00001FABF3 /* colorpicker.cpp in Sources */, 3652F010245C2919001FABF3 /* xml.c in Sources */, 3652F066245C292B001FABF3 /* compress.c in Sources */, @@ -877,7 +971,9 @@ 3652EFE1245B3B00001FABF3 /* progressbar.cpp in Sources */, 3652EFF5245B3B00001FABF3 /* popupbutton.cpp in Sources */, 3652EFF0245B3B00001FABF3 /* colorwheel.cpp in Sources */, + 36ADABBA246F262D0058D1D6 /* parse.c in Sources */, 3652F068245C292B001FABF3 /* unix.c in Sources */, + 36E6378C246FFFF30032F5FB /* CryptoManager.cpp in Sources */, 3652F07B245C292B001FABF3 /* RtspParser.c in Sources */, 3652EFCF245B3B00001FABF3 /* checkbox.cpp in Sources */, 36A0C03A2461E4C00083289C /* SettingsWindow.cpp in Sources */, @@ -896,25 +992,33 @@ 3652EFD6245B3B00001FABF3 /* window.cpp in Sources */, 3652F071245C292B001FABF3 /* InputStream.c in Sources */, 3652EFDD245B3B00001FABF3 /* texture_gl.cpp in Sources */, + 36ADAB6A246F171D0058D1D6 /* RetroAudioRenderer.cpp in Sources */, 3652F013245C2919001FABF3 /* http.c in Sources */, 3652F069245C292B001FABF3 /* peer.c in Sources */, 3602C3B7245D903000368900 /* HostButton.cpp in Sources */, 3652EFD0245B3B00001FABF3 /* vscrollpanel.cpp in Sources */, + 36ADABB9246F262D0058D1D6 /* gen_uuid.c in Sources */, 3652F06D245C292B001FABF3 /* win32.c in Sources */, + 36E63797247036A30032F5FB /* ServerInfoRequest.cpp in Sources */, 3652F012245C2919001FABF3 /* mkcert.c in Sources */, 3652F002245B6961001FABF3 /* AddHostWindow.cpp in Sources */, 3652EFF8245B4EE2001FABF3 /* Application.cpp in Sources */, + 36ADABB0246F262D0058D1D6 /* compare.c in Sources */, 3652F07E245C292B001FABF3 /* AudioStream.c in Sources */, 3652F076245C292B001FABF3 /* Connection.c in Sources */, 3652F011245C2919001FABF3 /* client.c in Sources */, + 36ADABB3246F262D0058D1D6 /* copy.c in Sources */, 3652EFE4245B3B00001FABF3 /* combobox.cpp in Sources */, 3603E93C246316400051287D /* InputController.cpp in Sources */, + 36ADABB1246F262D0058D1D6 /* unpack.c in Sources */, + 36ADABB5246F262D0058D1D6 /* pack.c in Sources */, 3652EFF2245B3B00001FABF3 /* textbox.cpp in Sources */, 3652F074245C292B001FABF3 /* Platform.c in Sources */, 3661D2FF2469E0C00060EE24 /* GLVideoRenderer.cpp in Sources */, 3652EFCD245B3B00001FABF3 /* widget.cpp in Sources */, 3652F073245C292B001FABF3 /* PlatformSockets.c in Sources */, 3652F08A245C8569001FABF3 /* ContentWindow.cpp in Sources */, + 36ADABB4246F262D0058D1D6 /* unparse.c in Sources */, 3652EFE5245B3B00001FABF3 /* theme.cpp in Sources */, 367CD95A245DE25F00A95738 /* StreamWindow.cpp in Sources */, 3652F07F245C292B001FABF3 /* LinkedBlockingQueue.c in Sources */, @@ -981,7 +1085,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( /usr/local/include, - "/usr/local/opt/openssl@1.1/include", + "\"/usr/local/Cellar/openssl@1.0/1.0.2t/include\"", "\"$(SRCROOT)/third_party/nanogui/include\"", "\"$(SRCROOT)/third_party/nanogui/ext/nanovg/src\"", "\"$(SRCROOT)/src/nanogui_resources\"", @@ -991,7 +1095,7 @@ ); LIBRARY_SEARCH_PATHS = ( /usr/local/lib, - "/usr/local/opt/openssl@1.1/lib", + "\"/usr/local/Cellar/openssl@1.0/1.0.2t/lib\"", ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; @@ -1071,7 +1175,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( /usr/local/include, - "/usr/local/opt/openssl@1.1/include", + "\"/usr/local/Cellar/openssl@1.0/1.0.2t/include\"", "\"$(SRCROOT)/third_party/nanogui/include\"", "\"$(SRCROOT)/third_party/nanogui/ext/nanovg/src\"", "\"$(SRCROOT)/src/nanogui_resources\"", @@ -1081,7 +1185,7 @@ ); LIBRARY_SEARCH_PATHS = ( /usr/local/lib, - "/usr/local/opt/openssl@1.1/lib", + "\"/usr/local/Cellar/openssl@1.0/1.0.2t/lib\"", ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..487532a --- /dev/null +++ b/run.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +make -f Makefile.libnx +/opt/devkitpro/tools/bin/nxlink -s moonlight.nro diff --git a/src/GameStreamClient.cpp b/src/GameStreamClient.cpp index 85a9daf..f844d3e 100644 --- a/src/GameStreamClient.cpp +++ b/src/GameStreamClient.cpp @@ -1,7 +1,7 @@ #include "GameStreamClient.hpp" +#include "ServerInfoRequest.hpp" #include "Settings.hpp" #include -#include #include #include #include @@ -9,12 +9,42 @@ #include #include #include - #include static std::mutex m_async_mutex; static std::vector> m_tasks; +#ifdef __SWITCH__ +#include +static void task_loop() { + Thread thread; + threadCreate( + &thread, + [](void* a) { + while (1) { + std::vector> m_tasks_copy; { + std::lock_guard guard(m_async_mutex); + m_tasks_copy = m_tasks; + m_tasks.clear(); + } + + for (auto task: m_tasks_copy) { + task(); + } + + usleep(500'000); + } + }, + NULL, + NULL, + 0x10000, + 0x2C, + -2 + ); + threadStart(&thread); + +} +#else static void task_loop() { auto thread = std::thread([](){ while (1) { @@ -33,6 +63,7 @@ static void task_loop() { }); thread.detach(); } +#endif void perform_async(std::function task) { std::lock_guard guard(m_async_mutex); @@ -48,15 +79,14 @@ void GameStreamClient::connect(const std::string &address, ServerCallbackworking_dir() + "/key"; - int status = gs_init(&m_server_data[address], (char *)(new std::string(address))->c_str(), key_dir.c_str(), 0, false); + int status = ServerInfoRequest::request(address, &m_server_data[address]); nanogui::async([this, address, callback, status] { - if (status == GS_OK) { + if (status == NetworkClientOK) { Settings::settings()->add_host(address); - callback(Result::success(m_server_data[address])); + callback(GSResult::success(m_server_data[address])); } else { - callback(Result::failure(gs_error != NULL ? gs_error : "Unknown error...")); + callback(GSResult::failure(gs_error != NULL ? gs_error : "Unknown error...")); } }); }); @@ -64,7 +94,7 @@ void GameStreamClient::connect(const std::string &address, ServerCallback callback) { if (m_server_data.count(address) == 0) { - callback(Result::failure("Firstly call connect()...")); + callback(GSResult::failure("Firstly call connect()...")); return; } @@ -73,9 +103,9 @@ void GameStreamClient::pair(const std::string &address, const std::string &pin, nanogui::async([callback, status] { if (status == GS_OK) { - callback(Result::success(true)); + callback(GSResult::success(true)); } else { - callback(Result::failure(gs_error != NULL ? gs_error : "Unknown error...")); + callback(GSResult::failure(gs_error != NULL ? gs_error : "Unknown error...")); } }); }); @@ -83,7 +113,7 @@ void GameStreamClient::pair(const std::string &address, const std::string &pin, void GameStreamClient::applist(const std::string &address, ServerCallback callback) { if (m_server_data.count(address) == 0) { - callback(Result::failure("Firstly call connect() & pair()...")); + callback(GSResult::failure("Firstly call connect() & pair()...")); return; } @@ -94,9 +124,9 @@ void GameStreamClient::applist(const std::string &address, ServerCallback::success(m_app_list[address])); + callback(GSResult::success(m_app_list[address])); } else { - callback(Result::failure(gs_error != NULL ? gs_error : "Unknown error...")); + callback(GSResult::failure(gs_error != NULL ? gs_error : "Unknown error...")); } }); }); @@ -104,7 +134,7 @@ void GameStreamClient::applist(const std::string &address, ServerCallback> callback) { if (m_server_data.count(address) == 0) { - callback(Result>::failure("Firstly call connect() & pair()...")); + callback(GSResult>::failure("Firstly call connect() & pair()...")); return; } @@ -116,9 +146,9 @@ void GameStreamClient::app_boxart(const std::string &address, int app_id, Server nanogui::async([this, callback, data, size, status] { if (status == GS_OK) { - callback(Result>::success(std::make_pair(data, size))); + callback(GSResult>::success(std::make_pair(data, size))); } else { - callback(Result>::failure(gs_error != NULL ? gs_error : "Unknown error...")); + callback(GSResult>::failure(gs_error != NULL ? gs_error : "Unknown error...")); } }); }); @@ -126,7 +156,7 @@ void GameStreamClient::app_boxart(const std::string &address, int app_id, Server void GameStreamClient::start(const std::string &address, STREAM_CONFIGURATION config, int app_id, ServerCallback callback) { if (m_server_data.count(address) == 0) { - callback(Result::failure("Firstly call connect() & pair()...")); + callback(GSResult::failure("Firstly call connect() & pair()...")); return; } @@ -137,9 +167,9 @@ void GameStreamClient::start(const std::string &address, STREAM_CONFIGURATION co nanogui::async([this, callback, status] { if (status == GS_OK) { - callback(Result::success(m_config)); + callback(GSResult::success(m_config)); } else { - callback(Result::failure(gs_error != NULL ? gs_error : "Unknown error...")); + callback(GSResult::failure(gs_error != NULL ? gs_error : "Unknown error...")); } }); }); @@ -147,7 +177,7 @@ void GameStreamClient::start(const std::string &address, STREAM_CONFIGURATION co void GameStreamClient::quit(const std::string &address, ServerCallback callback) { if (m_server_data.count(address) == 0) { - callback(Result::failure("Firstly call connect() & pair()...")); + callback(GSResult::failure("Firstly call connect() & pair()...")); return; } @@ -156,9 +186,9 @@ void GameStreamClient::quit(const std::string &address, ServerCallback cal nanogui::async([this, callback, status] { if (status == GS_OK) { - callback(Result::success(true)); + callback(GSResult::success(true)); } else { - callback(Result::failure(gs_error != NULL ? gs_error : "Unknown error...")); + callback(GSResult::failure(gs_error != NULL ? gs_error : "Unknown error...")); } }); }); diff --git a/src/GameStreamClient.hpp b/src/GameStreamClient.hpp index af61ef9..c61e693 100644 --- a/src/GameStreamClient.hpp +++ b/src/GameStreamClient.hpp @@ -13,13 +13,13 @@ extern "C" { extern void perform_async(std::function task); template -struct Result { +struct GSResult { public: - static Result success(T value) { + static GSResult success(T value) { return result(value, "", true); } - static Result failure(std::string error) { + static GSResult failure(std::string error) { return result(T(), error, false); } @@ -36,8 +36,8 @@ public: } private: - static Result result(T value, std::string error, bool isSuccess) { - Result result; + static GSResult result(T value, std::string error, bool isSuccess) { + GSResult result; result._value = value; result._error = error; result._isSuccess = isSuccess; @@ -49,7 +49,7 @@ private: bool _isSuccess = false; }; -template using ServerCallback = const std::function)>; +template using ServerCallback = const std::function)>; class GameStreamClient { public: diff --git a/src/crypto/CryptoManager.cpp b/src/crypto/CryptoManager.cpp new file mode 100644 index 0000000..2119c06 --- /dev/null +++ b/src/crypto/CryptoManager.cpp @@ -0,0 +1,274 @@ +#include "CryptoManager.hpp" +#include "Settings.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include "Log.h" + +static const int SHA1_HASH_LENGTH = 20; +static const int SHA256_HASH_LENGTH = 32; + +static Data* m_cert = nullptr; +static Data* m_p12 = nullptr; +static Data* m_key = nullptr; + +bool CryptoManager::certs_exists() { + if (load_certs()) { + return true; + } + return false; +} + +bool CryptoManager::load_certs() { + if (m_key == nullptr || m_cert == nullptr || m_p12 == nullptr) { + auto key_dir = Settings::settings()->working_dir() + "/key/"; + mkdirtree(key_dir.c_str()); + + Data cert = Data::read_from_file(key_dir + CERTIFICATE_FILE_NAME); + Data p12 = Data::read_from_file(key_dir + P12_FILE_NAME); + Data key = Data::read_from_file(key_dir + KEY_FILE_NAME); + + if (!cert.is_empty() && !p12.is_empty() && !key.is_empty()) { + m_cert = &cert; + m_p12 = &p12; + m_key = &key; + return true; + } + + return false; + } + return true; +} + +bool CryptoManager::generate_certs() { + auto cert_key_pair = mkcert_generate(); + + Data cert = get_cert_from_cert_key_pair(&cert_key_pair); + Data p12 = get_p12_from_cert_key_pair(&cert_key_pair); + Data key = get_key_from_cert_key_pair(&cert_key_pair); + + auto key_dir = Settings::settings()->working_dir() + "/key/"; + mkdirtree(key_dir.c_str()); + + if (!cert.is_empty() && !p12.is_empty() && !key.is_empty()) { + cert.write_to_file(key_dir + CERTIFICATE_FILE_NAME); + p12.write_to_file(key_dir + P12_FILE_NAME); + key.write_to_file(key_dir + KEY_FILE_NAME); + m_cert = &cert; + m_p12 = &p12; + m_key = &key; + return true; + } + return false; +} + +Data CryptoManager::SHA1_hash_data(Data data) { + char sha1[SHA1_HASH_LENGTH]; + SHA1((unsigned char*)data.bytes(), data.size(), (unsigned char*)sha1); + return Data(sha1, sizeof(sha1)); +} + +Data CryptoManager::SHA256_hash_data(Data data) { + char sha256[SHA256_HASH_LENGTH]; + SHA256((unsigned char*)data.bytes(), data.size(), (unsigned char*)sha256); + return Data(sha256, sizeof(sha256)); +} + +Data CryptoManager::create_AES_key_from_salt_SHA1(Data salted_pin) { + return SHA1_hash_data(salted_pin).subdata(16); +} + +Data CryptoManager::create_AES_key_from_salt_SHA256(Data salted_pin) { + return SHA256_hash_data(salted_pin).subdata(16); +} + +static int get_encrypt_size(Data data) { + // the size is the length of the data ceiling to the nearest 16 bytes + return (((int)data.size() + 15) / 16) * 16; +} + +Data CryptoManager::aes_encrypt(Data data, Data key) { + AES_KEY aes_key; + AES_set_encrypt_key((unsigned char*)key.bytes(), 128, &aes_key); + int size = get_encrypt_size(data); + unsigned char* buffer = (unsigned char*)malloc(size); + unsigned char* block_rounded_buffer = (unsigned char*)calloc(1, size); + memcpy(block_rounded_buffer, data.bytes(), data.size()); + + // AES_encrypt only encrypts the first 16 bytes so iterate the entire buffer + int block_offset = 0; + while (block_offset < size) { + AES_encrypt(block_rounded_buffer + block_offset, buffer + block_offset, &aes_key); + block_offset += 16; + } + + Data encrypted_data = Data((char*)buffer, size); + free(buffer); + free(block_rounded_buffer); + return encrypted_data; +} + +Data CryptoManager::aes_decrypt(Data data, Data key) { + AES_KEY aes_key; + AES_set_decrypt_key((unsigned char*)key.bytes(), 128, &aes_key); + unsigned char* buffer = (unsigned char*)malloc(data.size()); + + // AES_decrypt only decrypts the first 16 bytes so iterate the entire buffer + int block_offset = 0; + while (block_offset < data.size()) { + AES_decrypt((unsigned char*)data.bytes() + block_offset, buffer + block_offset, &aes_key); + block_offset += 16; + } + + Data decrypted_data = Data(buffer, data.size()); + free(buffer); + return decrypted_data; +} + +Data CryptoManager::pem_to_der(Data pem_cert_bytes) { + X509* x509; + + BIO* bio = BIO_new_mem_buf((unsigned char*)pem_cert_bytes.bytes(), pem_cert_bytes.size()); + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + BIO_free(bio); + + bio = BIO_new(BIO_s_mem()); + i2d_X509_bio(bio, x509); + + BUF_MEM* mem; + BIO_get_mem_ptr(bio, &mem); + + Data ret = Data(mem->data, mem->length); + BIO_free(bio); + return ret; +} + +bool CryptoManager::verify_signature(Data data, Data signature, Data cert) { + X509* x509; + BIO* bio = BIO_new_mem_buf(cert.bytes(), cert.size()); + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + + BIO_free(bio); + + if (!x509) { + LOG("Unable to parse certificate in memory...\n"); + return false; + } + + EVP_PKEY* pub_key = X509_get_pubkey(x509); + EVP_MD_CTX *mdctx = NULL; + mdctx = EVP_MD_CTX_create(); + EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pub_key); + EVP_DigestVerifyUpdate(mdctx, data.bytes(), data.size()); + int result = EVP_DigestVerifyFinal(mdctx, (unsigned char*)signature.bytes(), signature.size()); + + X509_free(x509); + EVP_PKEY_free(pub_key); + EVP_MD_CTX_destroy(mdctx); + return result > 0; +} + +Data CryptoManager::sign_data(Data data, Data key) { + BIO* bio = BIO_new_mem_buf(key.bytes(), key.size()); + + EVP_PKEY* pkey; + pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + + BIO_free(bio); + + if (!pkey) { + LOG("Unable to parse private key in memory!"); + exit(-1); + } + + EVP_MD_CTX *mdctx = NULL; + mdctx = EVP_MD_CTX_create(); + EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey); + EVP_DigestSignUpdate(mdctx, data.bytes(), data.size()); + size_t slen; + EVP_DigestSignFinal(mdctx, NULL, &slen); + unsigned char* signature = (unsigned char*)malloc(slen); + int result = EVP_DigestSignFinal(mdctx, signature, &slen); + + EVP_PKEY_free(pkey); + EVP_MD_CTX_destroy(mdctx); + + if (result <= 0) { + free(signature); + LOG("Unable to sign data!"); + exit(-1); + } + + Data signed_data = Data(signature, slen); + free(signature); + return signed_data; +} + +Data CryptoManager::get_signature_from_cert(Data cert) { + BIO* bio = BIO_new_mem_buf(cert.bytes(), cert.size()); + X509* x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + + if (!x509) { + LOG("Unable to parse certificate in memory!\n"); + exit(-1); + } + +#if (OPENSSL_VERSION_NUMBER < 0x10002000L) + ASN1_BIT_STRING *asn_signature = x509->signature; +#elif (OPENSSL_VERSION_NUMBER < 0x10100000L) + ASN1_BIT_STRING *asn_signature; + X509_get0_signature(&asn_signature, NULL, x509); +#else + const ASN1_BIT_STRING *asn_signature; + X509_get0_signature(&asn_signature, NULL, x509); +#endif + + Data sig = Data(asn_signature->data, asn_signature->length); + X509_free(x509); + return sig; +} + +Data CryptoManager::get_key_from_cert_key_pair(PCERT_KEY_PAIR cert_key_pair) { + BIO* bio = BIO_new(BIO_s_mem()); + +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) + PEM_write_bio_PrivateKey(bio, cert_key_pair->pkey, NULL, NULL, 0, NULL, NULL); +#else + PEM_write_bio_PrivateKey_traditional(bio, cert_key_pair->pkey, NULL, NULL, 0, NULL, NULL); +#endif + + BUF_MEM* mem; + BIO_get_mem_ptr(bio, &mem); + Data data = Data(mem->data, mem->length); + BIO_free(bio); + return data; +} + +Data CryptoManager::get_p12_from_cert_key_pair(PCERT_KEY_PAIR cert_key_pair) { + BIO* bio = BIO_new(BIO_s_mem()); + + i2d_PKCS12_bio(bio, cert_key_pair->p12); + + BUF_MEM* mem; + BIO_get_mem_ptr(bio, &mem); + Data data = Data(mem->data, mem->length); + BIO_free(bio); + return data; +} + +Data CryptoManager::get_cert_from_cert_key_pair(PCERT_KEY_PAIR cert_key_pair) { + BIO* bio = BIO_new(BIO_s_mem()); + + PEM_write_bio_X509(bio, cert_key_pair->x509); + + BUF_MEM* mem; + BIO_get_mem_ptr(bio, &mem); + Data data = Data(mem->data, mem->length); + BIO_free(bio); + return data; +} diff --git a/src/crypto/CryptoManager.hpp b/src/crypto/CryptoManager.hpp new file mode 100644 index 0000000..896746d --- /dev/null +++ b/src/crypto/CryptoManager.hpp @@ -0,0 +1,33 @@ +#include +#include "Data.hpp" + +extern "C" { + #include "mkcert.h" + #include "client.h" +} + +#pragma once + +#define CERTIFICATE_FILE_NAME "client.pem" +#define P12_FILE_NAME "client.p12" +#define KEY_FILE_NAME "key.pem" + +class CryptoManager { +public: + static bool certs_exists(); + static bool load_certs(); + static bool generate_certs(); + static Data SHA1_hash_data(Data data); + static Data SHA256_hash_data(Data data); + static Data create_AES_key_from_salt_SHA1(Data salted_pin); + static Data create_AES_key_from_salt_SHA256(Data salted_pin); + static Data aes_encrypt(Data data, Data key); + static Data aes_decrypt(Data data, Data key); + static Data pem_to_der(Data pem_cert_bytes); + static bool verify_signature(Data data, Data signature, Data cert); + static Data sign_data(Data data, Data key); + static Data get_signature_from_cert(Data cert); + static Data get_key_from_cert_key_pair(PCERT_KEY_PAIR cert_key_pair); + static Data get_p12_from_cert_key_pair(PCERT_KEY_PAIR cert_key_pair); + static Data get_cert_from_cert_key_pair(PCERT_KEY_PAIR cert_key_pair); +}; diff --git a/src/crypto/Data.cpp b/src/crypto/Data.cpp new file mode 100644 index 0000000..54d7d5e --- /dev/null +++ b/src/crypto/Data.cpp @@ -0,0 +1,106 @@ +#include "Data.hpp" +#include +#include +#include "Log.h" + +Data::Data(char* bytes, size_t size) { + if (bytes && size > 0) { + m_bytes = (char *)malloc(sizeof(char) * size); + memcpy(m_bytes, bytes, sizeof(char) * size); + m_size = size; + } +} + +Data::Data(size_t capacity) { + if (capacity > 0) { + m_bytes = (char *)malloc(sizeof(char) * capacity); + m_size = capacity; + } +} + +Data::~Data() { + if (m_bytes) { + free(m_bytes); + } +} + +Data Data::subdata(size_t size) { + if (size > m_size) { + LOG("Invalid data length...\n"); + exit(-1); + } + return Data(m_bytes, size); +} + +Data::Data(const Data& that): Data(that.m_bytes, that.m_size) {} + +Data& Data::operator=(const Data& that) { + if (this != &that) { + if (m_bytes) { + free(m_bytes); + } + + m_bytes = (char *)malloc(sizeof(char) * that.m_size); + memcpy(m_bytes, that.m_bytes, sizeof(char) * that.m_size); + m_size = that.m_size; + } + return *this; +} + +Data Data::random_bytes(size_t size) { + char* bytes = (char*)malloc(sizeof(char) * size); + arc4random_buf(bytes, size); + Data random_data(bytes, size); + free(bytes); + return random_data; +} + +Data Data::read_from_file(std::string path) { + FILE* f = fopen(path.c_str(), "r"); + if (f) { + fseek(f, 0, SEEK_END); + int size = (int)ftell(f); + fseek(f, 0, SEEK_SET); + + char* buffer = (char*)malloc(size); + fread(buffer, 1, size, f); + Data data(buffer, size); + free(buffer); + fclose(f); + return data; + } + return Data(); +} + +Data Data::hex_to_bytes() const { + Data data(m_size / 2); + char byte_chars[3] = {'\0','\0','\0'}; + unsigned long whole_byte; + + int i = 0, counter = 0; + while (i < m_size) { + byte_chars[0] = m_bytes[i++]; + byte_chars[1] = m_bytes[i++]; + whole_byte = strtoul(byte_chars, NULL, 16); + data.m_bytes[counter++] = whole_byte; + } + return data; +} + +Data Data::bytes_to_hex() const { + int counter = 0; + Data hex(m_size * 2); + char fmt[3] = {'\0','\0','\0'}; + for (int i = 0; i < m_size; i++) { + sprintf(fmt, "%02X", m_bytes[i]); + hex.m_bytes[counter++] = fmt[0]; + hex.m_bytes[counter++] = fmt[1]; + } + return hex; +} + +void Data::write_to_file(std::string path) { + FILE *f = fopen(path.c_str(), "w"); + fwrite(m_bytes, m_size, 1, f); + fclose(f); +} diff --git a/src/crypto/Data.hpp b/src/crypto/Data.hpp new file mode 100644 index 0000000..943fad8 --- /dev/null +++ b/src/crypto/Data.hpp @@ -0,0 +1,42 @@ +#include +#include +#pragma once + +class Data { +public: + Data(): Data(0) {}; + Data(char* bytes, size_t size); + Data(unsigned char* bytes, size_t size): Data((char *)bytes, size) {}; + Data(size_t capacity); + + ~Data(); + + char* bytes() const { + return m_bytes; + } + + size_t size() const { + return m_size; + } + + Data subdata(size_t size); + + Data(const Data& that); + Data& operator=(const Data& that); + + static Data random_bytes(size_t size); + static Data read_from_file(std::string path); + + Data hex_to_bytes() const; + Data bytes_to_hex() const; + + bool is_empty() const { + return m_size == 0; + } + + void write_to_file(std::string path); + +private: + char* m_bytes = nullptr; + size_t m_size = 0; +}; diff --git a/src/moonlight_glfw.cpp b/src/moonlight_glfw.cpp index 5cab64a..7b58eb7 100644 --- a/src/moonlight_glfw.cpp +++ b/src/moonlight_glfw.cpp @@ -1,12 +1,25 @@ #include #include -#include -#include "glsym/glsym.h" #include "Application.hpp" #include "Settings.hpp" #include "Limelight.h" #include "libretro.h" #include "InputController.hpp" +#include +#include + +#ifdef __SWITCH__ +#include +#include +#else +#include "glsym/glsym.h" +#endif + +#include + +#include "Data.hpp" +#include "NetworkClient.hpp" +#include "ServerInfoRequest.hpp" extern retro_input_state_t input_state_cb; @@ -49,6 +62,9 @@ static int16_t glfw_input_state_cb(unsigned port, unsigned device, unsigned inde int main(int argc, const char * argv[]) { input_state_cb = glfw_input_state_cb; + OpenSSL_add_all_algorithms(); + curl_global_init(CURL_GLOBAL_ALL); + glfwInit(); glfwSetErrorCallback([](int i, const char *error) { @@ -62,9 +78,14 @@ int main(int argc, const char * argv[]) { GLFWwindow* window = glfwCreateWindow(1280, 720, "Test", NULL, NULL); glfwMakeContextCurrent(window); - rglgen_resolve_symbols(glfwGetProcAddress); glfwSwapInterval(1); + #ifdef __SWITCH__ + gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); + #else + rglgen_resolve_symbols(glfwGetProcAddress); + #endif + glfwSetCursorPosCallback(window, [](GLFWwindow *w, double x, double y) { mouse_x = x; mouse_y = y; @@ -90,7 +111,15 @@ int main(int argc, const char * argv[]) { glfwGetWindowSize(window, &width, &height); glfwGetFramebufferSize(window, &fb_width, &fb_height); + #ifdef __SWITCH__ + Settings::settings()->set_working_dir("sdmc:/switch/moonlight"); + #else Settings::settings()->set_working_dir("/Users/rock88/Documents/RetroArch/system/moonlight"); + #endif + +// SERVER_DATA d5; +// std::string a = "www.google.ru"; +// ServerInfoRequest::request(a, &d5); nanogui::init(); nanogui::ref app = new Application(Size(width, height), Size(fb_width, fb_height)); @@ -98,6 +127,15 @@ int main(int argc, const char * argv[]) { nanogui::setup(1.0 / 15.0); while (!glfwWindowShouldClose(window)) { + #ifdef __SWITCH__ + hidScanInput(); + u32 kDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (kDown & KEY_PLUS) { + printf("Exit...\n"); + break; + } + #endif + glfwPollEvents(); InputController::controller()->handle_input(width, height); @@ -110,5 +148,6 @@ int main(int argc, const char * argv[]) { glfwSwapBuffers(window); } + glfwTerminate(); return 0; } diff --git a/src/moonlight_libnx.c b/src/moonlight_libnx.c new file mode 100644 index 0000000..c1d7ae3 --- /dev/null +++ b/src/moonlight_libnx.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include "libretro.h" + +retro_audio_sample_batch_t audio_batch_cb = NULL; +retro_input_state_t input_state_cb = NULL; + +uint32_t htonl(uint32_t hostlong) { + return __builtin_bswap32(hostlong); +} + +uint16_t htons(uint16_t hostshort) { + return __builtin_bswap16(hostshort); +} + +uint32_t ntohl(uint32_t netlong) { + return __builtin_bswap32(netlong); +} + +uint16_t ntohs(uint16_t netshort) { + return __builtin_bswap16(netshort); +} + +int sigaction(int a, const struct sigaction* b, struct sigaction* c) { + return 0; +} + +static int nxlink_sock = -1; + +void userAppInit() { + socketInitializeDefault(); + nxlink_sock = nxlinkStdio(); +} + +void userAppExit() { + if (nxlink_sock != -1) + close(nxlink_sock); + socketExit(); +} diff --git a/src/network/NetworkClient.cpp b/src/network/NetworkClient.cpp new file mode 100644 index 0000000..8135606 --- /dev/null +++ b/src/network/NetworkClient.cpp @@ -0,0 +1,96 @@ +#include "NetworkClient.hpp" +#include "Settings.hpp" +#include "CryptoManager.hpp" +#include "errors.h" +#include "Log.h" +#include + +static CURL *curl; +static bool init_curl_completed = false; + +struct HTTP_DATA { + char *memory; + size_t size; +}; + +static size_t _write_curl(void *contents, size_t size, size_t nmemb, void *userp) { + size_t realsize = size * nmemb; + HTTP_DATA* mem = (HTTP_DATA *)userp; + + mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1); + if (mem->memory == NULL) + return 0; + + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +static void init_curl() { + if (init_curl_completed) { + return; + } + + std::string key_directory = Settings::settings()->working_dir() + "/key"; + + curl = curl_easy_init(); + + char certificateFilePath[4096]; + sprintf(certificateFilePath, "%s/%s", key_directory.c_str(), CERTIFICATE_FILE_NAME); + + char keyFilePath[4096]; + sprintf(&keyFilePath[0], "%s/%s", key_directory.c_str(), KEY_FILE_NAME); + + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSLENGINE_DEFAULT, 1L); + curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE,"PEM"); + curl_easy_setopt(curl, CURLOPT_SSLCERT, certificateFilePath); + curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM"); + curl_easy_setopt(curl, CURLOPT_SSLKEY, keyFilePath); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_curl); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_SESSIONID_CACHE, 0L); + + init_curl_completed = true; +} + +int NetworkClient::perform(std::string url, bool use_https, Data* out) { + init_curl(); + + std::string _url; + + if (use_https) { + _url = "https://" + url; + } else { + _url = "http://" + url; + } + + LOG_FMT("Perform %s\n", _url.c_str()); + + HTTP_DATA* http_data = (HTTP_DATA*)malloc(sizeof(HTTP_DATA)); + http_data->memory = (char*)malloc(1); + http_data->size = 0; + + curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_data); + curl_easy_setopt(curl, CURLOPT_URL, _url.c_str()); + + CURLcode res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + gs_error = curl_easy_strerror(res); + return NetworkClientError; + } else if (http_data->memory == NULL) { + gs_error = "Out of memory"; + return NetworkClientError; + } + + *out = Data(http_data->memory, (int)http_data->size); + + free(http_data->memory); + free(http_data); + + return NetworkClientOK; +} diff --git a/src/network/NetworkClient.hpp b/src/network/NetworkClient.hpp new file mode 100644 index 0000000..3ab8433 --- /dev/null +++ b/src/network/NetworkClient.hpp @@ -0,0 +1,13 @@ +#include "Data.hpp" +#include +#pragma once + +#define NetworkClientOK 0 +#define NetworkClientError 1 + +static const std::string unique_id = "0123456789ABCDEF"; + +class NetworkClient { +public: + static int perform(std::string url, bool use_https, Data* out); +}; diff --git a/src/network/ServerInfoRequest.cpp b/src/network/ServerInfoRequest.cpp new file mode 100644 index 0000000..1b47814 --- /dev/null +++ b/src/network/ServerInfoRequest.cpp @@ -0,0 +1,109 @@ +#include "ServerInfoRequest.hpp" +#include "CryptoManager.hpp" +#include "Log.h" + +extern "C" { + #include "errors.h" +} + +int ServerInfoRequest::request(std::string address, PSERVER_DATA server) { + LiInitializeServerInformation(&server->serverInfo); + char* addr = (char *)malloc(sizeof(char) * address.length() + 1); + addr[address.length()] = '\0'; + memcpy(addr, address.c_str(), sizeof(char) * address.length()); + server->serverInfo.address = addr; + + int result = NetworkClientError; + char url[1024]; + + bool certs_exists = CryptoManager::certs_exists(); + + if (certs_exists) { + snprintf(url, sizeof(url), "%s:47984/serverinfo?uniqueid=%s", server->serverInfo.address, unique_id.c_str()); + } else { + snprintf(url, sizeof(url), "%s:47989/serverinfo", server->serverInfo.address); + } + + Data data; + if (NetworkClient::perform(url, certs_exists, &data) != NetworkClientOK) { + return NetworkClientError; + } + + char *pairedText = NULL; + char *currentGameText = NULL; + char *stateText = NULL; + char *serverCodecModeSupportText = NULL; + + if (xml_status(data.bytes(), data.size()) == GS_ERROR) + goto cleanup; + + if (xml_search(data.bytes(), data.size(), "currentgame", ¤tGameText) != GS_OK) { + goto cleanup; + } + + if (xml_search(data.bytes(), data.size(), "PairStatus", &pairedText) != GS_OK) + goto cleanup; + + if (xml_search(data.bytes(), data.size(), "appversion", (char**) &server->serverInfo.serverInfoAppVersion) != GS_OK) + goto cleanup; + + if (xml_search(data.bytes(), data.size(), "state", &stateText) != GS_OK) + goto cleanup; + + if (xml_search(data.bytes(), data.size(), "ServerCodecModeSupport", &serverCodecModeSupportText) != GS_OK) + goto cleanup; + + if (xml_search(data.bytes(), data.size(), "gputype", &server->gpuType) != GS_OK) + goto cleanup; + + if (xml_search(data.bytes(), data.size(), "GsVersion", &server->gsVersion) != GS_OK) + goto cleanup; + + if (xml_search(data.bytes(), data.size(), "hostname", &server->hostname) != GS_OK) + goto cleanup; + + if (xml_search(data.bytes(), data.size(), "GfeVersion", (char**) &server->serverInfo.serverInfoGfeVersion) != GS_OK) + goto cleanup; + + if (xml_modelist(data.bytes(), 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; + } + result = NetworkClientOK; + +cleanup: + if (pairedText != NULL) + free(pairedText); + + if (currentGameText != NULL) + free(currentGameText); + + if (serverCodecModeSupportText != NULL) + free(serverCodecModeSupportText); + + if (result == GS_OK && !server->unsupported) { + if (server->serverMajorVersion > MAX_SUPPORTED_GFE_VERSION) { + gs_error = "Ensure you're running the latest version of Moonlight Embedded or downgrade GeForce Experience and try again"; + result = NetworkClientError; + } else if (server->serverMajorVersion < MIN_SUPPORTED_GFE_VERSION) { + gs_error = "Moonlight Embedded requires a newer version of GeForce Experience. Please upgrade GFE on your PC and try again."; + result = NetworkClientError; + } + } + + return result; +} diff --git a/src/network/ServerInfoRequest.hpp b/src/network/ServerInfoRequest.hpp new file mode 100644 index 0000000..0d37b30 --- /dev/null +++ b/src/network/ServerInfoRequest.hpp @@ -0,0 +1,12 @@ +#include "NetworkClient.hpp" + +extern "C" { + #include "client.h" +} + +#pragma once + +class ServerInfoRequest { +public: + static int request(std::string address, PSERVER_DATA server); +}; diff --git a/src/streaming/video/GLVideoRenderer.hpp b/src/streaming/video/GLVideoRenderer.hpp index d7d716a..9fd6777 100644 --- a/src/streaming/video/GLVideoRenderer.hpp +++ b/src/streaming/video/GLVideoRenderer.hpp @@ -1,5 +1,9 @@ #include "IVideoRenderer.hpp" +#ifdef __SWITCH__ +#include +#else #include "glsym.h" +#endif #pragma once class GLVideoRenderer: public IVideoRenderer { diff --git a/src/ui/buttons/HostButton.cpp b/src/ui/buttons/HostButton.cpp index 8e2222b..259f53c 100644 --- a/src/ui/buttons/HostButton.cpp +++ b/src/ui/buttons/HostButton.cpp @@ -1,6 +1,7 @@ #include "HostButton.hpp" #include "GameStreamClient.hpp" #include "nanovg.h" +#include "Log.h" #include using namespace nanogui; @@ -22,6 +23,7 @@ HostButton::HostButton(Widget* parent, const std::string &address): Button(paren m_is_paired = result.value().paired; m_host_status_icon = m_is_paired ? FA_CHECK : FA_QUESTION; } else { + LOG_FMT("Error: %s\n", result.error().c_str()); m_is_paired = false; m_is_active = false; m_host_status_icon = FA_POWER_OFF; diff --git a/third_party/nanogui b/third_party/nanogui index 56eb83f..48528d3 160000 --- a/third_party/nanogui +++ b/third_party/nanogui @@ -1 +1 @@ -Subproject commit 56eb83fd44745accc765171afb46a7c60abc3067 +Subproject commit 48528d30a2c2036b7e006915984cae6f44f60f34