/// <summary> /// When the user presses "Start Streaming Steam", first check that they are paired /// </summary> public static async Task <StreamContext> StartStreaming(CoreDispatcher uiDispatcher, Computer computer, MoonlightStreamConfiguration streamConfig) { PairingManager p = new PairingManager(computer); // If we can't get the pair state, return bool?pairState = await p.QueryPairState(); if (!pairState.HasValue) { DialogUtils.DisplayDialog(uiDispatcher, "Pair state query failed", "Failed to start streaming"); return(null); } // If we're not paired, return if (pairState == false) { DialogUtils.DisplayDialog(uiDispatcher, "Device not paired", "Failed to start streaming"); return(null); } // Lookup the desired app in the app list // NOTE: This will go away when we have a proper app list int appId = await LookupAppIdForApp(uiDispatcher, new NvHttp(computer.IpAddress), "Steam"); if (appId == 0) { // LookupAppIdForApp() handles displaying a failure dialog return(null); } return(new StreamContext(computer, appId, streamConfig)); }
/// <summary> /// Query the app list on the server to get the Steam App ID /// </summary> /// <returns>True if the operation succeeded, false otherwise</returns> private static async Task <int> LookupAppIdForApp(CoreDispatcher dispatcher, NvHttp nv, String app) { XmlQuery appList; string appIdStr; appList = new XmlQuery(nv.BaseUrl + "/applist?uniqueid=" + nv.GetUniqueId()); // App list query went well - try to get the app ID try { appIdStr = await appList.SearchElement("App", "AppTitle", app, "ID"); Debug.WriteLine(appIdStr); if (appIdStr == null) { // Not found DialogUtils.DisplayDialog(dispatcher, "App Not Found", "App ID Lookup Failed"); return(0); } } // Exception connecting to the resource catch (Exception e) { // Steam ID lookup failed DialogUtils.DisplayDialog(dispatcher, "Failed to get app ID: " + e.Message, "App ID Lookup Failed"); return(0); } // We're in the clear - save the app ID return(Convert.ToInt32(appIdStr)); }
/// <summary> /// Pair with the hostname in the textbox /// </summary> public async Task Pair(CoreDispatcher uiDispatcher, Computer c) { Debug.WriteLine("Pairing..."); // Get the pair state. bool?pairState = await QueryPairState(); if (pairState == true) { DialogUtils.DisplayDialog(uiDispatcher, "This device is already paired to the host PC", "Already Paired"); return; } // pairstate = null. We've encountered an error else if (!pairState.HasValue) { DialogUtils.DisplayDialog(uiDispatcher, "Failed to query pair state", "Pairing failed"); return; } bool challenge = await PairingCryptoHelpers.PerformPairingHandshake(uiDispatcher, new WindowsCryptoProvider(), nv, nv.GetUniqueId()); if (!challenge) { Debug.WriteLine("Challenges failed"); return; } // Otherwise, everything was successful MainPage.SaveComputer(c); // FIXME: We can't have two dialogs open at once. // DialogUtils.DisplayDialog(uiDispatcher, "Pairing successful", "Success"); }
public static void CreatePackFromDirectory() { string sourceFolder = DialogUtils.OpenDirectory( "Select folder that contains the assets you wish to pack", "", "", DialogUtils.DirectoryIsEmpty); if (sourceFolder == null) { return; } string[] files = Directory.GetFiles(sourceFolder); var destinationFile = DialogUtils.SaveFile( "Select destination to save created pack file", sourceFolder, "Assets_256", "pack"); if (destinationFile != null) { CreatePackFromFiles(files, destinationFile); DialogUtils.DisplayDialog("Export Successful", "Successfully packed and saved " + files.Length + " assets to " + destinationFile); } }
/// <summary> /// Event handler for clicking the add PC manually button /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Add_Button_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrWhiteSpace(ip_textbox.Text) || String.IsNullOrWhiteSpace(nickname_textbox.Text)) { DialogUtils.DisplayDialog(this.Dispatcher, "Please fill out both text boxes", "Add PC Failed"); return; } else { Computer toAdd = new Computer(nickname_textbox.Text, ip_textbox.Text); this.Frame.Navigate(typeof(MainPage), toAdd); } }
public static void LoadZoneFileDbg() { string path = DialogUtils.OpenFile("Load Zone File", "", "zone"); if (path != null) { ForgelightGame game = new ForgelightGame("hehe", "", ""); if (!game.LoadZoneFromFile(path)) { DialogUtils.DisplayDialog("Zone Import Failed", "An error occurred while loading the zone file. Please check the console window for more info."); } } }
/// <summary> /// Runs if starting the connection failed /// </summary> private void ConnectionFailed() { // Stop showing the wait UI this.Waitgrid.Visibility = Visibility.Collapsed; this.currentStateText.Visibility = Visibility.Collapsed; // Inform the user of the failure via a message dialog DialogUtils.DisplayDialog(this.Dispatcher, stageFailureText, "Starting Connection Failed", x => { Cleanup(); this.Frame.Navigate(typeof(MainPage)); }); }
public static void LoadZoneFile() { if (ForgelightExtension.Instance.ForgelightGameFactory.ActiveForgelightGame == null) { DialogUtils.DisplayDialog("No Active Game", "There is currently no active forgelight game. Please load the correct forgelight game and try again."); return; } string path = EditorUtility.OpenFilePanel("Load Zone File", "", "zone"); if (path != null) { if (!ForgelightExtension.Instance.ForgelightGameFactory.ActiveForgelightGame.LoadZoneFromFile(path)) { DialogUtils.DisplayDialog("Zone Import Failed", "An error occurred while loading the zone file. Please check the console window for more info."); } } }
/// <summary> /// Executed when the user presses "Pair" /// </summary> private async Task PairButton_Click_Common() { Computer selected = (Computer)computerPicker.SelectedItem; // User hasn't selected anything or selected the placeholder if (selected == null || selected.IpAddress == null) { DialogUtils.DisplayDialog(this.Dispatcher, "No machine selected", "Pairing Failed"); return; } PairingManager p = new PairingManager(selected); // Stop polling timer while we're pairing mDnsTimer.Stop(); await p.Pair(this.Dispatcher, selected); mDnsTimer.Start(); }
/// <summary> /// Connection terminated callback /// </summary> /// <param name="errorCode">Error code for connection terminated</param> public void ClConnectionTerminated(int errorCode) { Debug.WriteLine("Connection terminated: " + errorCode); if (controllers != null) { // Stop controller code controllers.Stop(); } var unused = Task.Run(() => { // This needs to be done on a separate thread MoonlightCommonRuntimeComponent.StopConnection(); }); DialogUtils.DisplayDialog(this.Dispatcher, "Connection terminated unexpectedly", "Connection Terminated", (command) => { this.Frame.Navigate(typeof(MainPage), null); }); }
public void ExportZoneFile() { if (ForgelightExtension.Instance.ZoneManager.LoadedZone != null) { string path = DialogUtils.SaveFile( "Save zone file", ForgelightExtension.Instance.ForgelightGameFactory.ActiveForgelightGame.PackDirectory, Path.GetFileNameWithoutExtension(ForgelightExtension.Instance.ZoneManager.LoadedZone.Name), "zone"); if (path == null) { return; } SaveZone(path); } else { DialogUtils.DisplayDialog("Cannot save zone", "An existing zone file needs to be loaded first. Please import a zone file, then try again"); } }
/// <summary> /// Executed when the user presses "Start Streaming Steam!" /// </summary> private async Task StreamButton_Click_Common() { Debug.WriteLine("Start Streaming button pressed"); selected = (Computer)computerPicker.SelectedItem; // User hasn't selected a machine or selected a placeholder if (selected == null || String.IsNullOrWhiteSpace(selected.IpAddress)) { DialogUtils.DisplayDialog(this.Dispatcher, "No machine selected", "Streaming Failed"); } else { // Stop enumerating machines while we're trying to check pair state mDnsTimer.Stop(); byte[] aesKey = PairingCryptoHelpers.GenerateRandomBytes(16); // GameStream only uses 4 bytes of a 16 byte IV. Go figure. byte[] aesRiIndex = PairingCryptoHelpers.GenerateRandomBytes(4); byte[] aesIv = new byte[16]; Array.ConstrainedCopy(aesRiIndex, 0, aesIv, 0, aesRiIndex.Length); SettingsPage s = new SettingsPage(); MoonlightStreamConfiguration config = new MoonlightStreamConfiguration( s.GetStreamWidth(), s.GetStreamHeight(), s.GetStreamFps(), 10000, // FIXME: Scale by resolution 1024, aesKey, aesIv); StreamContext context = await ConnectionManager.StartStreaming(this.Dispatcher, selected, config); if (context != null) { this.Frame.Navigate(typeof(StreamFrame), context); } } }
/// <summary> /// Quit Game Event Handler /// </summary> private async Task QuitGame_Common() { Computer selected = (Computer)computerPicker.SelectedItem; // User hasn't selected anything or selected the placeholder if (selected == null || selected.IpAddress == null) { DialogUtils.DisplayDialog(this.Dispatcher, "No machine selected", "Quit Failed"); return; } PairingManager p = new PairingManager(selected); if (await p.QueryPairState() != true) { DialogUtils.DisplayDialog(this.Dispatcher, "Device not paired", "Quit Failed"); return; } try { NvHttp nv = new NvHttp(selected.IpAddress); XmlQuery quit = new XmlQuery(nv.BaseUrl + "/cancel?uniqueid=" + nv.GetUniqueId()); string cancelled = await quit.ReadXmlElement("cancel"); if (cancelled == "1") { DialogUtils.DisplayDialog(this.Dispatcher, "Successfully Quit Game", "Quit Game"); return; } } catch (Exception ex) { Debug.WriteLine(ex.Message); } DialogUtils.DisplayDialog(this.Dispatcher, "Unable to quit", "Quit Game"); }
public static void CreatePackFromDirectory() { string sourceFolder = EditorUtility.OpenFolderPanel("Select folder that contains the assets you wish to pack", "", ""); if (sourceFolder == null) { return; } if (DialogUtils.DirectoryIsEmpty(sourceFolder)) { bool dialog = DialogUtils.DisplayCancelableDialog("Invalid Directory", "Please select a directory that contains files."); if (dialog) { CreatePackFromDirectory(); } return; } string[] files = Directory.GetFiles(sourceFolder); var destinationFile = EditorUtility.SaveFilePanel( "Select destination to save created pack file", sourceFolder, "Assets_256", "pack"); if (destinationFile == null) { return; } CreatePackFromFiles(files, destinationFile); DialogUtils.DisplayDialog("Export Successful", "Successfully packed and saved " + files.Length + " assets to " + destinationFile); }
public void ShowMessageDialog(string title, string message, string okBtn, PlatformDialogServiceArguments platformArguments = null) { Activity activity = platformArguments?.Context != null && platformArguments.Context is Activity act ? act : Current.Activity; DialogUtils.DisplayDialog(activity, title, message, okBtn); }
public static async Task<bool> PerformPairingHandshake(CoreDispatcher uiDispatcher, WindowsCryptoProvider provider, NvHttp nv, string uniqueId) { string result; // Generate a salt for hashing the PIN byte[] salt = GenerateRandomBytes(16); string pin = new Random().Next(9999).ToString("D4"); // Combine the salt and pin, then create an AES key from them byte[] saltAndPin = SaltPin(salt, pin); CryptographicKey aesKey = GenerateAesKey(saltAndPin); // Send the salt and get the server cert DialogUtils.DisplayDialog(uiDispatcher, "Enter the following PIN on the host PC: " + pin, "Enter PIN"); // User will need to close dialog themselves XmlQuery getServerCert = new XmlQuery(nv.BaseUrl + "/pair?uniqueid=" + uniqueId + "&devicename=roth&updateState=1&phrase=getservercert&salt=" + BytesToHex(salt) + "&clientcert=" + BytesToHex(await provider.GetPemCertBytes())); result = await getServerCert.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } X509Certificate serverCert = await ExtractPlainCert(getServerCert, "plaincert"); // Generate a random challenge and encrypt it with our AES key byte[] randomChallenge = GenerateRandomBytes(16); Debug.WriteLine("Client challenge: " + BytesToHex(randomChallenge)); byte[] encryptedChallenge = EncryptAes(randomChallenge, aesKey); // Send the encrypted challenge to the server XmlQuery challengeResp = new XmlQuery(nv.BaseUrl + "/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientchallenge="+BytesToHex(encryptedChallenge)); // If we're not paired, there's a problem. result = await challengeResp.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } // Decode the server's response and subsequent challenge byte[] encServerChallengeResponse = HexToBytes(await challengeResp.ReadXmlElement("challengeresponse")); byte[] decServerChallengeResponse = DecryptAes(encServerChallengeResponse, aesKey); byte[] serverResponse = new byte[20], serverChallenge = new byte[16]; Array.Copy(decServerChallengeResponse, serverResponse, serverResponse.Length); Array.Copy(decServerChallengeResponse, 20, serverChallenge, 0, serverChallenge.Length); Debug.WriteLine("serverResponse: " + BytesToHex(serverResponse)); Debug.WriteLine("server challenge: " + BytesToHex(serverChallenge)); // Using another 16 bytes secret, compute a challenge response hash using the secret, our cert sig, and the challenge byte[] clientSecret = GenerateRandomBytes(16); Debug.WriteLine("Client secret: " + BytesToHex(clientSecret)); Debug.WriteLine("Client sig: " + BytesToHex((await provider.GetClientCertificate()).GetSignature())); byte[] challengeRespHash = ToSHA1Bytes(concatBytes(concatBytes(serverChallenge, (await provider.GetClientCertificate()).GetSignature()), clientSecret)); Debug.WriteLine("Challenge SHA 1: " + BytesToHex(challengeRespHash)); byte[] challengeRespEncrypted = EncryptAes(challengeRespHash, aesKey); XmlQuery secretResp = new XmlQuery(nv.BaseUrl + "/pair?uniqueid=" + uniqueId + "&devicename=roth&updateState=1&serverchallengeresp=" + BytesToHex(challengeRespEncrypted)); result = await secretResp.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } // Get the server's signed secret byte[] serverSecretResp = HexToBytes(await secretResp.ReadXmlElement("pairingsecret")); byte[] serverSecret = new byte[16]; byte[] serverSignature = new byte[256]; Array.Copy(serverSecretResp, 0, serverSecret, 0, 16); Array.Copy(serverSecretResp, 16, serverSignature, 0, 256); // Ensure the authenticity of the data if (!VerifySignature(serverSecret, serverSignature, serverCert)) { // Cancel the pairing process await Unpair(nv); // Looks like a MITM return false; } // Ensure the server challenge matched what we expected (aka the PIN was correct) byte[] serverChallengeRespHash = ToSHA1Bytes(concatBytes(concatBytes(randomChallenge, serverCert.GetSignature()), serverSecret)); if (!serverChallengeRespHash.SequenceEqual(serverResponse)) { // Cancel the pairing process await Unpair(nv); // Probably got the wrong PIN return false; } // Send the server our signed secret byte[] clientPairingSecret = concatBytes(clientSecret, SignData(await provider.GetKeyPair(), clientSecret)); XmlQuery clientSecretResp = new XmlQuery(nv.BaseUrl + "/pair?uniqueid=" + uniqueId + "&devicename=roth&updateState=1&clientpairingsecret=" + BytesToHex(clientPairingSecret)); result = await clientSecretResp.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } // Do the initial challenge (seems neccessary for us to show as paired) XmlQuery pairChallenge = new XmlQuery(nv.BaseUrl + "/pair?uniqueid=" + uniqueId + "&devicename=roth&updateState=1&phrase=pairchallenge"); result = await pairChallenge.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } return true; }