New message handling, and new NvAPI

This commit is contained in:
Gustavo 2016-02-25 07:58:36 -03:00
parent f423fc6314
commit c420db5373
17 changed files with 6878 additions and 370 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
user-data-dir/
pnacl/
# Object files
*.o
*.ko

View File

@ -6,8 +6,9 @@ TARGET = moonlight-chrome
include common-c.mk
include opus.mk
include h264bitstream.mk
include libgamestream.mk
EXTRA_INC_PATHS := $(EXTRA_INC_PATHS) $(COMMON_C_INCLUDE) $(OPUS_INCLUDE) $(H264BS_INCLUDE)
EXTRA_INC_PATHS := $(EXTRA_INC_PATHS) $(COMMON_C_INCLUDE) $(OPUS_INCLUDE) $(H264BS_INCLUDE) $(LIBGS_C_INCLUDE)
include $(NACL_SDK_ROOT)/tools/common.mk
@ -16,7 +17,7 @@ HTTPD_PY := $(HTTPD_PY) --no-dir-check
CHROME_ARGS += --allow-nacl-socket-api=localhost
LIBS = ppapi_gles2 ppapi ppapi_cpp pthread nacl_io
LIBS = ppapi_gles2 ppapi ppapi_cpp pthread curl z ssl crypto nacl_io
CFLAGS = -Wall $(COMMON_C_C_FLAGS) $(OPUS_C_FLAGS)
@ -24,6 +25,7 @@ SOURCES = \
$(OPUS_SOURCE) \
$(H264BS_SOURCE) \
$(COMMON_C_SOURCE) \
$(LIBGS_C_SOURCE) \
libchelper.c \
main.cpp \
input.cpp \
@ -31,6 +33,7 @@ SOURCES = \
connectionlistener.cpp \
viddec.cpp \
auddec.cpp \
http.cpp \
# Build rules generated by macros from common.mk:

125
http.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "moonlight.hpp"
#include "ppapi/cpp/var_array_buffer.h"
#include <http.h>
#include "libgamestream/errors.h" //fix-me
#include <string.h>
#include <mkcert.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
extern const char* gs_error;
X509 *g_cert;
EVP_PKEY *g_privateKey;
void MoonlightInstance::MakeCert(int32_t callbackId, pp::VarArray args)
{
pp::VarDictionary ret;
ret.Set("callbackId", pp::Var(callbackId));
ret.Set("type", pp::Var("resolve"));
pp::VarDictionary retData;
CERT_KEY_PAIR certKeyPair = mkcert_generate();
BIO* bio = BIO_new(BIO_s_mem());
PEM_write_bio_X509(bio, certKeyPair.x509);
BUF_MEM *mem = NULL;
BIO_get_mem_ptr(bio, &mem);
std::string cert(mem->data, mem->length);
BIO_free(bio);
BIO* biokey = BIO_new(BIO_s_mem());
PEM_write_bio_PrivateKey(biokey, certKeyPair.pkey, NULL, NULL, 0, NULL, NULL);
BIO_get_mem_ptr(biokey, &mem);
std::string pkey(mem->data, mem->length);
BIO_free(biokey);
retData.Set("privateKey", pkey.c_str());
retData.Set("cert", cert.c_str());
ret.Set("ret", retData);
PostMessage(ret);
}
void MoonlightInstance::LoadCert(const char* certStr, const char* keyStr)
{
char* _certStr = strdup(certStr);
char* _keyStr = strdup(keyStr);
BIO *bio = BIO_new_mem_buf(_certStr, -1);
if(!(g_cert = PEM_read_bio_X509(bio, NULL, NULL, NULL))) {
PostMessage(pp::Var("Error loading cert into memory"));
}
BIO_reset(bio);
BIO_free_all(bio);
bio = BIO_new_mem_buf(_keyStr, -1);
if(PEM_read_bio_PrivateKey(bio, &g_privateKey, NULL, NULL) == NULL) {
PostMessage(pp::Var("Error loading private key into memory"));
}
BIO_free_all(bio);
free(_certStr);
free(_keyStr);
}
void MoonlightInstance::NvHTTPInit(int32_t callbackId, pp::VarArray args)
{
std::string _cert = args.Get(0).AsString();
std::string _key = args.Get(1).AsString();
LoadCert(_cert.c_str(), _key.c_str());
http_init();
pp::VarDictionary ret;
ret.Set("callbackId", pp::Var(callbackId));
ret.Set("type", pp::Var("resolve"));
ret.Set("ret", pp::Var(""));
PostMessage(ret);
}
void MoonlightInstance::NvHTTPRequest(int32_t /*result*/, int32_t callbackId, std::string url)
{
char* _url = strdup(url.c_str());
PHTTP_DATA data = http_create_data();
if (data == NULL) {
pp::VarDictionary ret;
ret.Set("callbackId", pp::Var(callbackId));
ret.Set("type", pp::Var("reject"));
ret.Set("ret", pp::Var("Error when creating data buffer."));
PostMessage(ret);
goto clean_data;
}
if(http_request(_url , data) != GS_OK) {
pp::VarDictionary ret;
ret.Set("callbackId", pp::Var(callbackId));
ret.Set("type", pp::Var("reject"));
ret.Set("ret", pp::Var(gs_error));
PostMessage(ret);
goto clean_data;
}
{
pp::VarDictionary ret;
ret.Set("callbackId", pp::Var(callbackId));
ret.Set("type", pp::Var("resolve"));
ret.Set("ret", pp::Var(data->memory));
PostMessage(ret);
}
clean_data:
http_free_data(data);
free(_url);
}

View File

@ -27,7 +27,7 @@
<div id="streamSettings">
<div class="mdl-select">
<select id="selectResolution">
<option selected="NONE">Stream Resolution</option>
<option value="NONE">Stream Resolution</option>
<option value="1280:720">1280x720</option>
<option value="1920:1080">1920x1080</option>
</select>
@ -82,7 +82,10 @@
</div>
<script defer src="static/js/jquery-2.2.0.min.js"></script>
<script defer src="static/js/material.min.js"></script>
<script defer src="static/js/crypto-js.js"></script>
<script type="text/javascript" src="static/js/messages.js"></script>
<script type="text/javascript" src="static/js/common.js"></script>
<script type="text/javascript" src="static/js/index.js"></script>
<script type="text/javascript" src="static/js/utils.js"></script>
</body>
</html>

8
libgamestream.mk Normal file
View File

@ -0,0 +1,8 @@
LIBGS_C_DIR := libgamestream
LIBGS_C_SOURCE := \
$(LIBGS_C_DIR)/http.c \
$(LIBGS_C_DIR)/mkcert.c \
LIBGS_C_INCLUDE := \
$(LIBGS_C_DIR) \

29
libgamestream/errors.h Normal file
View File

@ -0,0 +1,29 @@
/*
* This file is part of Moonlight Embedded.
*
* Copyright (C) 2015 Iwan Timmer
*
* Moonlight is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Moonlight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#define GS_OK 0
#define GS_FAILED -1
#define GS_OUT_OF_MEMORY -2
#define GS_INVALID -3
#define GS_WRONG_STATE -4
#define GS_IO_ERROR -5
#define GS_NOT_SUPPORTED_4K -6

138
libgamestream/http.c Normal file
View File

@ -0,0 +1,138 @@
/*
* This file is part of Moonlight Embedded.
*
* Copyright (C) 2015 Iwan Timmer
*
* Moonlight is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Moonlight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
*/
#include "http.h"
#include "errors.h"
#include <string.h>
#include <curl/curl.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
static CURL *curl;
const char* gs_error;
extern X509 *g_cert;
extern EVP_PKEY *g_privateKey;
static size_t _write_curl(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
PHTTP_DATA mem = (PHTTP_DATA)userp;
mem->memory = 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 CURLcode sslctx_function(CURL * curl, void * sslctx, void * parm)
{
SSL_CTX* ctx = (SSL_CTX*)sslctx;
//X509_STORE* store = SSL_CTX_get_cert_store(ctx);
//X509_STORE_add_cert(store, cert);
if(!SSL_CTX_use_certificate(ctx, g_cert))
printf("SSL_CTX_use_certificate problem\n");
if(!SSL_CTX_use_PrivateKey(ctx, g_privateKey))
printf("Use Key failed\n");
return CURLE_OK;
}
int http_init() {
curl = curl_easy_init();
if (!curl)
return GS_FAILED;
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(curl, CURLOPT_SSLENGINE_DEFAULT, 1L);
curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE,"PEM");
curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
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_CTX_FUNCTION, *sslctx_function);
return GS_OK;
}
int http_request(char* url, PHTTP_DATA data) {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, data);
curl_easy_setopt(curl, CURLOPT_URL, url);
if (data->size > 0) {
free(data->memory);
data->memory = malloc(1);
if(data->memory == NULL)
return GS_OUT_OF_MEMORY;
data->size = 0;
}
//return GS_FAILED;
CURLcode res = curl_easy_perform(curl);
if(res != CURLE_OK) {
gs_error = curl_easy_strerror(res);
return GS_FAILED;
} else if (data->memory == NULL) {
return GS_OUT_OF_MEMORY;
}
return GS_OK;
}
void http_cleanup() {
curl_easy_cleanup(curl);
}
PHTTP_DATA http_create_data() {
PHTTP_DATA data = malloc(sizeof(HTTP_DATA));
if (data == NULL)
return NULL;
data->memory = malloc(1);
if(data->memory == NULL) {
free(data);
return NULL;
}
data->size = 0;
return data;
}
void http_free_data(PHTTP_DATA data) {
if (data != NULL) {
if (data->memory != NULL)
free(data->memory);
free(data);
}
}

40
libgamestream/http.h Normal file
View File

@ -0,0 +1,40 @@
/*
* This file is part of Moonlight Embedded.
*
* Copyright (C) 2015 Iwan Timmer
*
* Moonlight is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Moonlight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct _HTTP_DATA {
char *memory;
size_t size;
} HTTP_DATA, *PHTTP_DATA;
int http_init();
PHTTP_DATA http_create_data();
int http_request(char* url, PHTTP_DATA data);
void http_free_data(PHTTP_DATA data);
#ifdef __cplusplus
}
#endif

173
libgamestream/mkcert.c Normal file
View File

@ -0,0 +1,173 @@
/*
* Moonlight is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Moonlight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
*/
#include "mkcert.h"
#include <stdio.h>
#include <stdlib.h>
#include <openssl/pem.h>
#include <openssl/conf.h>
#include <openssl/pkcs12.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif
static const int NUM_BITS = 2048;
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);
SSLeay_add_all_algorithms();
ERR_load_crypto_strings();
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();
CRYPTO_mem_leaks(bio_err);
BIO_free(bio_err);
return (CERT_KEY_PAIR) {x509, pkey, p12};
}
void mkcert_free(CERT_KEY_PAIR certKeyPair) {
X509_free(certKeyPair.x509);
EVP_PKEY_free(certKeyPair.pkey);
PKCS12_free(certKeyPair.p12);
}
void mkcert_save(const char* certFile, const char* p12File, const char* keyPairFile, CERT_KEY_PAIR certKeyPair) {
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;
}
rsa = RSA_generate_key(bits, RSA_F4, NULL, NULL);
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_basic_constraints, "critical,CA:TRUE");
add_ext(x, NID_key_usage, "critical,keyCertSign,cRLSign");
add_ext(x, NID_subject_key_identifier, "hash");
if (!X509_sign(x, pk, EVP_sha1())) {
goto err;
}
*x509p = x;
*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;
}

40
libgamestream/mkcert.h Normal file
View File

@ -0,0 +1,40 @@
/*
* Created by Diego Waxemberg on 10/16/14.
* Copyright (c) 2014 Limelight Stream. All rights reserved.
*
* Moonlight is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Moonlight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <openssl/x509v3.h>
#include <openssl/pkcs12.h>
typedef struct _CERT_KEY_PAIR {
X509 *x509;
EVP_PKEY *pkey;
PKCS12 *p12;
} CERT_KEY_PAIR, *PCERT_KEY_PAIR;
CERT_KEY_PAIR mkcert_generate();
void mkcert_free(CERT_KEY_PAIR);
void mkcert_save(const char* certFile, const char* p12File, const char* keyPairFile, CERT_KEY_PAIR certKeyPair);
#ifdef __cplusplus
}
#endif

100
main.cpp
View File

@ -3,17 +3,19 @@
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "ppapi/cpp/input_event.h"
// Requests the NaCl module to connection to the server specified after the :
#define MSG_START_REQUEST "startRequest:"
#define MSG_START_REQUEST "startRequest"
// Requests the NaCl module stop streaming
#define MSG_STOP_REQUEST "stopRequest"
// Sent by the NaCl module when the stream has stopped whether user-requested or not
#define MSG_STREAM_TERMINATED "streamTerminated"
#define MSG_OPENURL "openUrl"
MoonlightInstance* g_Instance;
MoonlightInstance::~MoonlightInstance() {}
@ -120,55 +122,53 @@ void* MoonlightInstance::ConnectionThreadFunc(void* context) {
// hook from javascript into the CPP code.
void MoonlightInstance::HandleMessage(const pp::Var& var_message) {
// Ignore the message if it is not a string.
if (!var_message.is_string())
if (!var_message.is_dictionary())
return;
std::string message = var_message.AsString();
if (message.substr(0, strlen(MSG_START_REQUEST)) == MSG_START_REQUEST) {
handleStartStream(message);
} else if (message.substr(0, strlen(MSG_STOP_REQUEST)) == MSG_STOP_REQUEST) {
handleStopStream(message);
pp::VarDictionary msg(var_message);
int32_t callbackId = msg.Get("callbackId").AsInt();
std::string method = msg.Get("method").AsString();
pp::VarArray params(msg.Get("params"));
if (strcmp(method.c_str(), MSG_START_REQUEST) == 0) {
handleStartStream(callbackId, params);
} else if (strcmp(method.c_str(), MSG_STOP_REQUEST) == 0) {
handleStopStream(callbackId, params);
} else if (strcmp(method.c_str(), MSG_OPENURL) == 0) {
handleOpenURL(callbackId, params);
} else if (strcmp(method.c_str(), "httpInit") == 0) {
NvHTTPInit(callbackId, params);
} else if (strcmp(method.c_str(), "makeCert") == 0) {
MakeCert(callbackId, params);
} else {
pp::Var response("Unhandled message received: " + message);
pp::Var response("Unhandled message received: " + method);
PostMessage(response);
}
}
void split(const std::string& s, char c, std::vector<std::string>& v) {
std::string::size_type i = 0;
std::string::size_type j = s.find(c);
while (j != std::string::npos) {
v.push_back(s.substr(i, j-i));
i = ++j;
j = s.find(c, j);
if (j == std::string::npos) v.push_back(s.substr(i, s.length()));
}
}
void MoonlightInstance::handleStartStream(std::string startStreamMessage) {
// request:host:width:height:fps:bitrate
std::vector<std::string> splitString;
split(startStreamMessage, ':', splitString);
pp::Var response("Setting stream width to: " + splitString.at(2));
void MoonlightInstance::handleStartStream(int32_t callbackId, pp::VarArray args) {
std::string host = args.Get(0).AsString();
std::string width = args.Get(1).AsString();
std::string height = args.Get(2).AsString();
std::string fps = args.Get(3).AsString();
std::string bitrate = args.Get(4).AsString();
pp::Var response("Setting stream width to: " + width);
PostMessage(response);
response = ("Setting stream height to: " + splitString.at(3));
response = ("Setting stream height to: " + height);
PostMessage(response);
response = ("Setting stream fps to: " + splitString.at(4));
response = ("Setting stream fps to: " + fps);
PostMessage(response);
response = ("Setting stream host to: " + splitString.at(1));
response = ("Setting stream host to: " + host);
PostMessage(response);
response = ("Setting stream bitrate to: " + splitString.at(5));
response = ("Setting stream bitrate to: " + bitrate);
PostMessage(response);
// Populate the stream configuration
m_StreamConfig.width = stoi(splitString.at(2));
m_StreamConfig.height = stoi(splitString.at(3));
m_StreamConfig.fps = stoi(splitString.at(4));
m_StreamConfig.bitrate = stoi(splitString.at(5)); // kilobits per second
m_StreamConfig.width = stoi(width);
m_StreamConfig.height = stoi(height);
m_StreamConfig.fps = stoi(fps);
m_StreamConfig.bitrate = stoi(bitrate); // kilobits per second
m_StreamConfig.packetSize = 1024;
m_StreamConfig.streamingRemotely = 0;
m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
@ -176,15 +176,35 @@ void MoonlightInstance::handleStartStream(std::string startStreamMessage) {
m_ServerMajorVersion = 5;
// Store the host from the start message
m_Host = splitString.at(1);
m_Host = host;
// Start the worker thread to establish the connection
pthread_create(&m_ConnectionThread, NULL, MoonlightInstance::ConnectionThreadFunc, this);
pp::VarDictionary ret;
ret.Set("callbackId", pp::Var(callbackId));
ret.Set("type", pp::Var("resolve"));
ret.Set("ret", pp::VarDictionary());
PostMessage(ret);
}
void MoonlightInstance::handleStopStream(std::string stopStreamMessage) {
void MoonlightInstance::handleStopStream(int32_t callbackId, pp::VarArray args) {
// Begin connection teardown
StopConnection();
pp::VarDictionary ret;
ret.Set("callbackId", pp::Var(callbackId));
ret.Set("type", pp::Var("resolve"));
ret.Set("ret", pp::VarDictionary());
PostMessage(ret);
}
void MoonlightInstance::handleOpenURL(int32_t callbackId, pp::VarArray args) {
std::string url = args.Get(0).AsString();
openHttpThread.message_loop().PostWork(m_CallbackFactory.NewCallback(&MoonlightInstance::NvHTTPRequest, callbackId, url));
PostMessage(pp::Var (url.c_str()));
}
bool MoonlightInstance::Init(uint32_t argc,

View File

@ -1 +1 @@
@%NACL_SDK_ROOT%\tools\make.exe %*
set EXTRA_INC_PATHS=%NACL_SDK_ROOT%/ports/include %NACL_SDK_ROOT%/include/pnacl && set EXTRA_LIB_PATHS=%NACL_SDK_ROOT%/ports/lib && @%NACL_SDK_ROOT%\tools\make.exe %*

View File

@ -1,6 +1,7 @@
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"
#include "ppapi/cpp/var_dictionary.h"
#include "ppapi/cpp/mouse_lock.h"
#include "ppapi/cpp/graphics_3d.h"
#include "ppapi/cpp/video_decoder.h"
@ -13,6 +14,7 @@
#include "ppapi/cpp/graphics_3d_client.h"
#include "ppapi/utility/completion_callback_factory.h"
#include "ppapi/utility/threading/simple_thread.h"
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
@ -44,13 +46,16 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock {
m_CallbackFactory(this),
m_MouseLocked(false),
m_KeyModifiers(0),
m_WaitingForAllModifiersUp(false) {
m_WaitingForAllModifiersUp(false),
openHttpThread(this) {
// This function MUST be used otherwise sockets don't work (nacl_io_init() doesn't work!)
nacl_io_init_ppapi(pp_instance(), pp::Module::Get()->get_browser_interface());
LiInitializeStreamConfiguration(&m_StreamConfig);
m_GamepadApi = static_cast<const PPB_Gamepad*>(pp::Module::Get()->GetBrowserInterface(PPB_GAMEPAD_INTERFACE));
openHttpThread.Start();
}
virtual ~MoonlightInstance();
@ -58,27 +63,27 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock {
bool Init(uint32_t argc, const char* argn[], const char* argv[]);
void HandleMessage(const pp::Var& var_message);
void handlePair(std::string pairMessage);
void handleShowGames(std::string showGamesMessage);
void handleStartStream(std::string startStreamMessage);
void handleStopStream(std::string stopStreamMessage);
void handlePair(int32_t callbackId, pp::VarArray args);
void handleShowGames(int32_t callbackId, pp::VarArray args);
void handleStartStream(int32_t callbackId, pp::VarArray args);
void handleStopStream(int32_t callbackId, pp::VarArray args);
void handleOpenURL(int32_t callbackId, pp::VarArray args);
void UpdateModifiers(PP_InputEvent_Type eventType, short keyCode);
bool HandleInputEvent(const pp::InputEvent& event);
void PollGamepads();
void DidLockMouse(int32_t result);
void MouseLockLost();
void DidLockMouse(int32_t result);
void DidChangeFocus(bool got_focus);
void DidChangeView(const pp::Rect& position,
const pp::Rect& clip_ignored);
void OnConnectionStopped(uint32_t unused);
void OnConnectionStarted(uint32_t error);
void StopConnection();
void DidChangeView(const pp::Rect& position,
const pp::Rect& clip_ignored);
static void* ConnectionThreadFunc(void* context);
static void* GamepadThreadFunc(void* context);
static void* StopThreadFunc(void* context);
@ -106,6 +111,12 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock {
static void AudDecCleanup(void);
static void AudDecDecodeAndPlaySample(char* sampleData, int sampleLength);
void MakeCert(int32_t callbackId, pp::VarArray args);
void LoadCert(const char* certStr, const char* keyStr);
void NvHTTPInit(int32_t callbackId, pp::VarArray args);
void NvHTTPRequest(int32_t, int32_t callbackId, std::string url);
private:
static CONNECTION_LISTENER_CALLBACKS s_ClCallbacks;
static DECODER_RENDERER_CALLBACKS s_DrCallbacks;
@ -138,6 +149,8 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock {
bool m_MouseLocked;
char m_KeyModifiers;
bool m_WaitingForAllModifiersUp;
pp::SimpleThread openHttpThread;
};
extern MoonlightInstance* g_Instance;

5949
static/js/crypto-js.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,19 +3,19 @@ var hosts = [];
// Called by the common.js module.
function attachListeners() {
$('#startButton')[0].addEventListener('click', startPushed);
$('#stopButton')[0].addEventListener('click', stopPushed);
$('#pairButton')[0].addEventListener('click', pairPushed);
$('#showAppsButton')[0].addEventListener('click', showAppsPushed);
$('#selectResolution')[0].addEventListener('change', saveResolution);
$('#selectFramerate')[0].addEventListener('change', saveFramerate);
$('#bitrateSlider')[0].addEventListener('input', updateBitrateField); // input occurs every notch you slide
$('#bitrateSlider')[0].addEventListener('change', saveBitrate); // change occurs once the mouse lets go.
window.addEventListener("resize", fullscreenNaclModule);
$('#startButton').on('click', startPushed);
$('#stopButton').on('click', stopPushed);
$('#pairButton').on('click', pairPushed);
$('#showAppsButton').on('click', showAppsPushed);
$('#selectResolution').on('change', saveResolution);
$('#selectFramerate').on('change', saveFramerate);
$('#bitrateSlider').on('input', updateBitrateField); // input occurs every notch you slide
$('#bitrateSlider').on('change', saveBitrate); // change occurs once the mouse lets go.
$(window).resize(fullscreenNaclModule);
}
function updateBitrateField() {
$('#bitrateField')[0].innerHTML = $('#bitrateSlider')[0].value + " Mbps"
$('#bitrateField').html($('#bitrateSlider')[0].value + " Mbps");
}
function moduleDidLoad() {
@ -26,9 +26,9 @@ function moduleDidLoad() {
// but to save from the PITA of inter-chrome-app-page JS message passing,
// I'm opting to do it in a single page, and keep the data around.
function hideAllWorkflowDivs() {
$('#streamSettings')[0].style.display = 'inline-block';
$('#hostSettings')[0].style.display = 'inline-block';
$('#gameSelection')[0].style.display = 'none';
$('#streamSettings').css('display', 'inline-block');
$('#hostSettings').css('display', 'inline-block');
$('#gameSelection').css('display', 'none');
// common.hideModule(); // do NOT hide the nacl module. you can't interact with it then
}
@ -52,11 +52,11 @@ function showAppsPushed() {
function showAppsMode() {
console.log("entering show apps mode.")
$('#streamSettings')[0].style.display = 'none';
$('#hostSettings')[0].style.display = 'none';
$('#gameSelection')[0].style.display = 'inline-block';
$("#main-content").children().not("#listener").display = "inline-block";
document.body.style.backgroundColor = "white";
$('#streamSettings').css('display', 'none');
$('#hostSettings').css('display', 'none');
$('#gameSelection').css('display', 'inline-block');
$("#main-content").children().not("#listener").css('display', 'inline-block');
$("body").css('backgroundColor', 'white');
}
// user wants to start a stream. We need the host, game ID, and video settings(?)
@ -66,12 +66,17 @@ function startPushed() {
var e = document.getElementById("selectHost");
target = e.options[e.selectedIndex].value;
}
var frameRate = $("#selectFramerate")[0].value;
var resolution = $("#selectResolution")[0].value;
var frameRate = $("#selectFramerate").val();
var streamWidth = $('#selectResolution option:selected').val().split(':')[0];
var streamHeight = $('#selectResolution option:selected').val().split(':')[1];
// we told the user it was in Mbps. We're dirty liars and use Kbps behind their back.
var bitrate = parseInt($("#bitrateSlider")[0].value) * 1024;
var bitrate = parseInt($("#bitrateSlider").val()) * 1024;
console.log('startRequest:' + target + ":" + resolution + ":" + frameRate);
common.naclModule.postMessage('startRequest:' + target + ":" + resolution + ":" + frameRate + ":" + bitrate + ":");
sendMessage('startRequest', [target, streamWidth, streamHeight, frameRate, bitrate]);
// we just finished the gameSelection section. only expose the NaCl section
playGameMode();
}
@ -86,8 +91,8 @@ function playGameMode() {
}
function fullscreenNaclModule() {
var streamWidth = $('#selectResolution')[0].options[$('#selectResolution')[0].selectedIndex].value.split(':')[0];
var streamHeight = $('#selectResolution')[0].options[$('#selectResolution')[0].selectedIndex].value.split(':')[1];
var streamWidth = $('#selectResolution option:selected').val().split(':')[0];
var streamHeight = $('#selectResolution option:selected').val().split(':')[1];
var screenWidth = window.innerWidth;
var screenHeight = window.innerHeight;
@ -104,46 +109,25 @@ function fullscreenNaclModule() {
// user pushed the stop button. we should stop.
function stopPushed() {
common.naclModule.postMessage('stopRequested');
}
// hook from main.cpp into the javascript
function handleMessage(msg) {
var quitStreamString = "streamTerminated";
var connectionEstablishedString = "Connection Established";
console.log("message received: " + msg.data);
if (msg.data.lastIndexOf(quitStreamString, 0) === 0) {
console.log("Stream termination message received. returning to 'show apps' screen.")
showAppsMode();
} else if (msg.data.lastIndexOf(connectionEstablishedString, 0) === 0) {
var hostSelect = $('#selectHost')[0];
for(var i = 0; i < hostSelect.length; i++) {
if (hostSelect.options[i].value == target) return;
}
var opt = document.createElement('option');
opt.appendChild(document.createTextNode(target));
opt.value = target;
$('#selectHost')[0].appendChild(opt);
hosts.push(target);
saveHosts();
}
//common.naclModule.postMessage('stopRequested');
sendMessage('stopRequested');
}
function storeData(key, data, callbackFunction) {
var obj = {};
obj[key] = data;
chrome.storage.sync.set(obj, callbackFunction);
if(chrome.storage)
chrome.storage.sync.set(obj, callbackFunction);
}
function saveResolution() {
updateDefaultBitrate();
storeData('resolution', $('#selectResolution')[0].value, null);
storeData('resolution', $('#selectResolution').val(), null);
}
function saveFramerate() {
updateDefaultBitrate();
storeData('frameRate', $('#selectFramerate')[0].value, null);
storeData('frameRate', $('#selectFramerate').val(), null);
}
function saveHosts() {
@ -151,12 +135,12 @@ function saveHosts() {
}
function saveBitrate() {
storeData('bitrate', $('#bitrateSlider')[0].value, null);
storeData('bitrate', $('#bitrateSlider').val(), null);
}
function updateDefaultBitrate() {
var res = $('#selectResolution')[0].value;
var frameRate = $('#selectFramerate')[0].value;
var res = $('#selectResolution').val();
var frameRate = $('#selectFramerate').val();
if (res.lastIndexOf("1920:1080", 0) === 0) {
if (frameRate.lastIndexOf("30", 0) === 0) { // 1080p, 30fps
@ -177,36 +161,39 @@ function updateDefaultBitrate() {
function onWindowLoad(){
// don't show the game selection div
document.getElementById('gameSelection').style.display = 'none';
$('#gameSelection').css('display', 'none');
$("#bitrateField").addClass("bitrateField");
// load stored resolution prefs
chrome.storage.sync.get('resolution', function(previousValue) {
$('#selectResolution')[0].remove(0);
$('#selectResolution')[0].value = previousValue.resolution != null ? previousValue.resolution : '1280:720';
});
// load stored framerate prefs
chrome.storage.sync.get('frameRate', function(previousValue) {
$('#selectFramerate')[0].remove(0);
$('#selectFramerate')[0].value = previousValue.frameRate != null ? previousValue.frameRate : '30';
});
// load previously connected hosts
chrome.storage.sync.get('hosts', function(previousValue) {
hosts = previousValue.hosts != null ? previousValue.hosts : [];
if ($('#selectHost')[0].length > 0) {
$('#selectHost')[0].remove($('#selectHost')[0].selectedIndex);
}
for(var i = 0; i < hosts.length; i++) { // programmatically add each new host.
var opt = document.createElement('option');
opt.appendChild(document.createTextNode(hosts[i]));
opt.value = hosts[i];
$('#selectHost')[0].appendChild(opt);
}
});
// load stored bitrate prefs
chrome.storage.sync.get('bitrate', function(previousValue) {
$('#bitrateSlider')[0].MaterialSlider.change(previousValue.bitrate != null ? previousValue.bitrate : '15');
updateBitrateField();
});
if(chrome.storage) {
// load stored resolution prefs
chrome.storage.sync.get('resolution', function(previousValue) {
$('#selectResolution')[0].remove(0);
$('#selectResolution')[0].value = previousValue.resolution != null ? previousValue.resolution : '1280:720';
});
// load stored framerate prefs
chrome.storage.sync.get('frameRate', function(previousValue) {
$('#selectFramerate')[0].remove(0);
$('#selectFramerate')[0].value = previousValue.frameRate != null ? previousValue.frameRate : '30';
});
// load previously connected hosts
chrome.storage.sync.get('hosts', function(previousValue) {
hosts = previousValue.hosts != null ? previousValue.hosts : [];
if ($('#selectHost')[0].length > 0) {
$('#selectHost')[0].remove($('#selectHost')[0].selectedIndex);
}
for(var i = 0; i < hosts.length; i++) { // programmatically add each new host.
var opt = document.createElement('option');
opt.appendChild(document.createTextNode(hosts[i]));
opt.value = hosts[i];
$('#selectHost')[0].appendChild(opt);
}
});
// load stored bitrate prefs
chrome.storage.sync.get('bitrate', function(previousValue) {
$('#bitrateSlider')[0].MaterialSlider.change(previousValue.bitrate != null ? previousValue.bitrate : '15');
updateBitrateField();
});
}
}

24
static/js/messages.js Normal file
View File

@ -0,0 +1,24 @@
var callbacks = {}
var callbacks_ids = 1;
var sendMessage = function(method, params) {
return new Promise(function(resolve, reject) {
var id = callbacks_ids++;
callbacks[id] = {'resolve': resolve, 'reject': reject};
common.naclModule.postMessage({
'callbackId': id,
'method': method,
'params': params
});
});
}
function handleMessage(msg) {
if (msg.data.callbackId && callbacks[msg.data.callbackId]) {
callbacks[msg.data.callbackId][msg.data.type](msg.data.ret);
delete callbacks[msg.data.callbackId]
} else {
console.log(msg.data);
}
}

View File

@ -1,243 +1,196 @@
function getXMLString(xml, tagName) {
var xmlDoc = xml.responseXML;
return xmlDoc.getElementsByTagName(tagName)[0].childNodes[0].nodeValue;
function guuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
function verifyResponseStatus(xml) {
var responseCode = parseInt(getXMLString(xml, "status_code"));
if (responseCode !== 200) {
throw "Error, expected status code 200, received status code: ".concat(responseCode.toString());
}
String.prototype.toHex = function() {
var hex = '';
for(var i = 0; i < this.length; i++) {
hex += '' + this.charCodeAt(i).toString(16);
}
return hex;
}
function getServerInfo() {
var connectionResp = openHttpConnectionToString(baseUrlHttps + "/serverinfo?"+buildUniqueIdUuidString(), true);
var serverResp = getServerVersion(connectionResp);
function NvAPI(address, clientUid) {
this.address = address;
this.paired = false;
this.supports4K = false;
this.currentGame = 0;
this.serverMajorVersion = 0;
this.clientUid = clientUid;
this._baseUrlHttps = 'https://' + address + ':47984';
this._baseUrlHttp = 'http://' + address + ':47989';
_self = this;
};
if(serverResp == 200) {
return connectionResp;
} else {
if(serverResp == 401) {
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
}
throw serverResp
}
//FOR TEST ONLY
var api;
function init() {
api = new NvAPI('localhost', guuid());
return sendMessage('makeCert', []).then(function (cert) {
return sendMessage('httpInit', [cert.cert, cert.privateKey]).then(function (ret) {
return api.pair(cert, "1234");
});
});
}
function getComputerDetails() {
var serverInfo = getServerInfo();
var details = {
name: getXMLString(serverinfo, "hostname"),
uuid: UUID.fromString(getXmlString(serverInfo, "uniqueid").trim()),
macAddress: getXmlString(serverInfo, "mac").trim(),
localIPStr: getXmlString(serverInfo, "LocalIP"),
externalIpStr: getXmlString(serverInfo, "ExternalIP"),
pairState: parseInt(getXmlString(serverInfo, "PairStatus").trim()) == 1 ? PairState.PAIRED : PairState.NOT_PAIRED, // needs support for PiarState.FAILED
runningGameId: getCurrentGame(serverInfo), // force to 0 if an error happens
state: ONLINE
};
return details;
}
function openHTTPRequest(destinationURL, enableReadTimeout, callbackFunction) {
var xmlHttp = new XMLHttpRequest();
if(enableReadTimeout) {
xmlHttp.timeout = 5000;
}
xmlHttp.onreadystatechange = function() {
callbackFunction(xmlHttp);
// if (xmlHttp.readyState == 4 && xmlHttp.status == 200) callbackFunction(xmlHttp.responseText);
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
function getServerVersion(serverInfo) {
return getXmlString(serverInfo, "appversion");
}
function getPairState() {
return pm.getPairState(getServerInfo());
}
function getPairState(serverInfo) {
return pm.getPairState(serverInfo);
}
function getMaxLumaPixelsH264(serverInfo) {
var str = getXmlString(serverInfo, "MaxLumaPixelsH264");
if (str !== null) {
return parseInt(str);
} else {
return 0;
}
}
function getMaxLumaPixelsHEVC(serverInfo) {
var str = getXmlString(serverInfo, "MaxLumaPixelsHEVC");
if (str !== null) {
return parseInt(str);
} else {
return 0;
}
}
function getGpuType(String serverInfo) {
return getXmlString(serverInfo, "gputype");
}
public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException {
// serverinfo returns supported resolutions in descending order, so getting the first
// height will give us whether we support 4K. If this is not present, we don't support
// 4K.
var heightStr = getXmlString(serverInfo, "Height");
if (heightStr == null) {
return false;
}
NvAPI.prototype = {
init: function () {
return sendMessage('openUrl', [_self._baseUrlHttps+'/serverinfo?'+_self._buildUidStr()]).then(function(ret) {
$xml = _self._parseXML(ret);
$root = $xml.find('root')
if($root.attr("status_code") == 200) {
_self.pair = getXMLString(xml, "PairStatus").trim() == 1;
_self.currentGame = getXMLString(xml, "currentgame").trim();
}
});
},
// GFE 2.8 released without 4K support, even though it claims to have such
// support using the SupportedDisplayMode element. We'll use the new ServerCodecModeSupport
// element to tell whether 4K is supported or not. For now, we just check the existence
// of this element. I'm hopeful that the production version that ships with 4K will
// also be the first production version that has this element.
if (getXmlString(serverInfo, "ServerCodecModeSupport") == null) {
return false;
}
if (parseInt(heightStr) >= 2160) {
// Found a 4K resolution in the list
return true;
}
return false;
}
function getCurrentGame(String serverInfo) throws IOException, XmlPullParserException {
// GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
// has the semantics that its name would indicate. To contain the effects of this change as much
// as possible, we'll force the current game to zero if the server isn't in a streaming session.
var serverState = getXmlString(serverInfo, "state").trim();
if (serverState != null && !serverState.match("_SERVER_AVAILABLE" + "$")) { // an endsWith implementation.
var game = getXmlString(serverInfo, "currentgame").trim();
return parseInt(game);
}
else {
return 0;
}
}
function getAppById(appId) {
var appList = getAppList();
for (var i = 0, len = appList.length; i < len; i++) {
if (appList[i].getAppId() == appId) {
return appFromList;
}
}
return null;
}
/* NOTE: Only use this function if you know what you're doing.
* It's totally valid to have two apps named the same thing,
* or even nothing at all! Look apps up by ID if at all possible
* using the above function */
function getAppByName(appName) {
var appList = getAppList();
for (var i = 0, len = appList.length; i < len; i++) {
if (appList[i].getAppName().equalsIgnoreCase(appName)) {
return appList[i];
}
}
return null;
}
function pair(pin) {
return pm.pair(pin);
}
function getAppListByReader(xml) {
var rootElement = xml.getElementsByTagName("root")[0];
var appElements = rootElement.getElementsByTagName("App");
var returnVar;
for(var i = 0, len = appElements.length; i < len; i++) {
returnVar.push(
var app {
appTitle: appElements[i].getElementsByTagName("AppTitle")[0].nodeValue;
ID: appElements[i].getElementsByTagName("ID")[0].nodeValue;
isRunning: appElements[i].getElementsByTagName("IsRunning")[0].nodeValue;
getAppById: function (appId) {
return getAppList().then(function (list) {
var retApp = null;
list.some(function (app) {
if (app.id == appId) {
retApp = app;
return true;
}
)
}
}
function getAppListRaw() {
return openHttpConnectionToString(baseUrlHttps + "/applist?" + buildUniqueIdUuidString(), true);
}
function getAppList() {
if (verbose) {
// Use the raw function so the app list is printed
return getAppListByReader(new StringReader(getAppListRaw()));
}
else {
var resp = openHttpConnection(baseUrlHttps + "/applist?" + buildUniqueIdUuidString(), true);
var appList = getAppListByReader(new InputStreamReader(resp.byteStream()));
resp.close();
return appList;
}
}
function unpair() {
openHttpConnectionToString(baseUrlHttps + "/unpair?"+buildUniqueIdUuidString(), true);
}
function getBoxArt(app) {
var resp = openHttpConnection(baseUrlHttps + "/appasset?"+ buildUniqueIdUuidString() + "&appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
return resp.byteStream();
}
var hexChar = ["0", "1", "2", "3", "4", "5", "6", "7","8", "9", "A", "B", "C", "D", "E", "F"];
function byteToHex(b) { // thanks, https://gist.github.com/amorri40/3430429
if (b.constructor === Array) { // if we were given an array, then return an array.
var returnVar;
for(var i = 0, len = b.length; i < len; i++) {
returnVar.push(hexChar[(b[i] >> 4) & 0x0f] + hexChar[b[i] & 0x0f]);
}
return returnVar;
}
return hexChar[(b >> 4) & 0x0f] + hexChar[b & 0x0f];
}
function launchApp(context, appId){
var xmlStr = openHttpConnectionToString(baseUrlHttps +
"/launch?" + buildUniqueIdUuidString() +
"&appid=" + appId +
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + context.negotiatedFps +
"&additionalStates=1&sops=" + (context.streamConfig.getSops() ? 1 : 0) +
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
"&rikeyid="+context.riKeyId +
"&localAudioPlayMode=" + (context.streamConfig.getPlayLocalAudio() ? 1 : 0) +
"&surroundAudioInfo=" + ((context.streamConfig.getAudioChannelMask() << 16) + context.streamConfig.getAudioChannelCount()),
false);
var gameSession = getXmlString(xmlStr, "gamesession");
return gameSession != null && !gameSession.equals("0");
}
function resumeApp(context) {
var xmlStr = openHttpConnectionToString(baseUrlHttps + "/resume?" + buildUniqueIdUuidString() +
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
"&rikeyid="+context.riKeyId, false);
var resume = getXmlString(xmlStr, "resume");
return parseInt(resume) != 0;
}
function quitApp() {
var xmlStr = openHttpConnectionToString(baseUrlHttps + "/cancel?" + buildUniqueIdUuidString(), false);
var cancel = getXmlString(xmlStr, "cancel");
return parseInt(cancel) != 0;
}
return false;
});
return retApp;
});
},
getAppList: function () {
return sendMessage('openUrl', [_self._baseUrlHttps+'/applist?'+_self._buildUidStr()]).then(function (ret) {
$xml = _self._parseXML(ret);
var rootElement = xml.getElementsByTagName("root")[0];
var appElements = rootElement.getElementsByTagName("App");
var appList;
for(var i = 0, len = appElements.length; i < len; i++) {
appList.push({
title: appElements[i].getElementsByTagName("AppTitle")[0].nodeValue.trim(),
id: appElements[i].getElementsByTagName("ID")[0].nodeValue.trim(),
running: appElements[i].getElementsByTagName("IsRunning")[0].nodeValue.trim()
});
}
return appList;
});
},
getArtBox: function (appId) {
return sendMessage('openUrl', [
_self._baseUrlHttps+
'/appasset?'+_self._buildUidStr()+
'&appid=' + appId +
'&AssetType=2&AssetIdx=0'
]).then(function(ret) {
return ret;
});
},
launchApp: function (context, appId) {
return sendMessage('openUrl', [
_self.baseUrlHttps +
'/launch?' + _self._buildUidStr() +
'&appid=' + appId +
'&mode=' +
'&additionalStates=1&sops=' +
'&rikey' +
'&rikeyid' +
'&localAudioPlayMode' +
'&surroundAudioInfo'
]).then(function (ret) {
return true;
});
},
resumeApp: function (context) {
return sendMessage('openUrl', [
_self._baseUrlHttps +
'/resume?' + _self._buildUidStr() +
'&rikey=' +
'&rikeyid='
]).then(function (ret) {
return true;
});
},
quitApp: function () {
return sendMessage('openUrl', [_self._baseUrlHttps+'/unpair?'+_self._buildUidStr()]);
},
pair: function (cert, pin) {
if (_self.paired)
return $.when(false);
if (_self.currentGame)
return $.when(false);
var salt_data = CryptoJS.lib.WordArray.random(16);
var cert_hex = cert.cert.toHex();
return sendMessage('openUrl',[
_self._baseUrlHttp+
'/pair?'+_self._buildUidStr()+
'&devicename=roth&updateState=1&phrase=getservercert&salt='+salt_data.toString()+
'&clientcert='+cert_hex
]).then(function (ret) {
var salt_pin_hex = salt_data.toString();
var aes_key_hash = CryptoJS.SHA1(CryptoJS.enc.Hex.parse(salt_pin_hex + salt_pin_hex.substr(0, 8)));
console.log(aes_key_hash);
var challenge_data = CryptoJS.lib.WordArray.random(16);
var challenge_enc = CryptoJS.AES.encrypt(challenge_data, aes_key_hash, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.NoPadding});
var challange_enc_hex = challenge_enc.ciphertext.toString()
return sendMessage('openUrl', [
_self._baseUrlHttp+
'/pair?'+_self._buildUidStr()+
'&devicename=roth&updateState=1&clientchallenge=' + challange_enc_hex
]).then(function (ret) {
console.log(ret);
$xml = _self._parseXML(ret);
var challengeresponse = $xml.find('challengeresponse').text();
for (var i = 0; i < 96; i += 32) {
var data = CryptoJS.enc.Hex.parse(challengeresponse.substr(i, 32));
var challenge_dec = CryptoJS.AES.decrypt(data, aes_key_hash, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.NoPadding});
console.log(challenge_dec);
}
return sendMessage('openUrl', [
_self._baseUrlHttp+
'/pair?'+
'&devicename=roth&updateState=1&serverchallengeresp='
]).then(function (ret) {
console.log(ret);
return true;
});
});
});
},
unpair: function () {
return sendMessage('openUrl', [_self._baseUrlHttps+'/unpair?'+_self._buildUidStr()]);
},
_buildUidStr: function () {
return 'uniqueid=' + _self.clientUid + '&uuid=' + guuid();
},
_parseXML: function (xmlData) {
return $($.parseXML(xmlData.toString()));
},
};