├── .gitattributes ├── .idea └── .idea.Spotify │ └── .idea │ ├── .name │ ├── encodings.xml │ ├── vcs.xml │ ├── indexLayout.xml │ └── .gitignore ├── README.md ├── Content ├── ExampleMap.umap ├── BP_ExampleActor.uasset ├── UI │ └── BPW_PlayerUI.uasset └── ExampleMap_BuiltData.uasset ├── Config ├── DefaultEditorPerProjectUserSettings.ini ├── DefaultEditor.ini ├── DefaultGame.ini ├── HoloLens │ └── HoloLensEngine.ini ├── DefaultEngine.ini └── DefaultInput.ini ├── .gitignore ├── Source ├── Spotify │ ├── Spotify.h │ ├── Spotify.cpp │ ├── SpotifyCredentials.cpp │ ├── SpotifyCredentials.h │ ├── Spotify.Build.cs │ ├── SpotifyDevSettings.cpp │ ├── SpotifyDevSettings.h │ ├── SHA256.h │ ├── SHA256.cpp │ ├── SpotifyService.h │ └── SpotifyService.cpp ├── Spotify.Target.cs └── SpotifyEditor.Target.cs └── Spotify.uproject /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/.idea.Spotify/.idea/.name: -------------------------------------------------------------------------------- 1 | Spotify -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spotify 2 | 3 | Developed with Unreal Engine 4 4 | -------------------------------------------------------------------------------- /Content/ExampleMap.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkusTheOrt/Spotify-Unreal-Controls/HEAD/Content/ExampleMap.umap -------------------------------------------------------------------------------- /Config/DefaultEditorPerProjectUserSettings.ini: -------------------------------------------------------------------------------- 1 | [ContentBrowser] 2 | ContentBrowserTab1.SelectedPaths=/Game/ThirdPersonBP -------------------------------------------------------------------------------- /Content/BP_ExampleActor.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkusTheOrt/Spotify-Unreal-Controls/HEAD/Content/BP_ExampleActor.uasset -------------------------------------------------------------------------------- /Content/UI/BPW_PlayerUI.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkusTheOrt/Spotify-Unreal-Controls/HEAD/Content/UI/BPW_PlayerUI.uasset -------------------------------------------------------------------------------- /Content/ExampleMap_BuiltData.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3857720a46b1956d57406bb42f67e023bdd19afc0240b65b78dd42dc65e4c7ea 3 | size 1290115 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries 2 | DerivedDataCache 3 | Intermediate 4 | Saved 5 | .vscode 6 | .vs 7 | *.VC.db 8 | *.opensdf 9 | *.opendb 10 | *.sdf 11 | *.sln 12 | *.suo 13 | *.xcodeproj 14 | *.xcworkspace -------------------------------------------------------------------------------- /.idea/.idea.Spotify/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Source/Spotify/Spotify.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | DECLARE_LOG_CATEGORY_EXTERN(LogSpotify, Log, Log); 8 | -------------------------------------------------------------------------------- /.idea/.idea.Spotify/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.idea.Spotify/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Source/Spotify/Spotify.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "Spotify.h" 4 | #include "Modules/ModuleManager.h" 5 | 6 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, Spotify, "Spotify" ); 7 | 8 | DEFINE_LOG_CATEGORY(LogSpotify); -------------------------------------------------------------------------------- /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- 1 | [UnrealEd.SimpleMap] 2 | SimpleMapName=/Game/TP_ThirdPerson/Maps/ThirdPersonExampleMap 3 | 4 | [EditoronlyBP] 5 | bAllowClassAndBlueprintPinMatching=true 6 | bReplaceBlueprintWithClass= true 7 | bDontLoadBlueprintOutsideEditor= true 8 | bBlueprintIsNotBlueprintType= true 9 | 10 | -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | [/Script/EngineSettings.GeneralProjectSettings] 2 | ProjectID=D769471F490E518A62F458970FAEF326 3 | ProjectName=Third Person BP Game Template 4 | 5 | [/Script/Spotify.SpotifyDevSettings] 6 | ClientId=43bf2bdff42d4bc8877fdd9d2ff77717 7 | Callback="http://localhost:3036" 8 | SaveSlotName=SpotifyCredentials 9 | 10 | -------------------------------------------------------------------------------- /.idea/.idea.Spotify/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /modules.xml 6 | /projectSettingsUpdater.xml 7 | /.idea.Spotify.iml 8 | /contentModel.xml 9 | # Datasource local storage ignored files 10 | /dataSources/ 11 | /dataSources.local.xml 12 | # Editor-based HTTP Client requests 13 | /httpRequests/ 14 | -------------------------------------------------------------------------------- /Source/Spotify/SpotifyCredentials.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "SpotifyCredentials.h" 5 | 6 | void USpotifyCredentials::SetValues(FString InVerify, FString InChallenge, FString InRefreshKey) 7 | { 8 | Verify = InVerify; 9 | Challenge = InChallenge; 10 | RefreshKey = InRefreshKey; 11 | } 12 | -------------------------------------------------------------------------------- /Spotify.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "5.0", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "Spotify", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default", 11 | "AdditionalDependencies": [ 12 | "CoreUObject", 13 | "Engine", 14 | "DeveloperSettings" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /Source/Spotify.Target.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class SpotifyTarget : TargetRules 7 | { 8 | public SpotifyTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Game; 11 | DefaultBuildSettings = BuildSettingsVersion.V2; 12 | 13 | ExtraModuleNames.AddRange( new string[] { "Spotify" } ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/SpotifyEditor.Target.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class SpotifyEditorTarget : TargetRules 7 | { 8 | public SpotifyEditorTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Editor; 11 | DefaultBuildSettings = BuildSettingsVersion.V2; 12 | 13 | ExtraModuleNames.AddRange( new string[] { "Spotify" } ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/Spotify/SpotifyCredentials.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/SaveGame.h" 7 | #include "SpotifyCredentials.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class SPOTIFY_API USpotifyCredentials : public USaveGame 14 | { 15 | GENERATED_BODY() 16 | public: 17 | UPROPERTY(SaveGame) 18 | FString Verify; 19 | 20 | UPROPERTY(SaveGame) 21 | FString Challenge; 22 | 23 | UPROPERTY(SaveGame) 24 | FString RefreshKey; 25 | 26 | void SetValues(FString InVerify, FString InChallenge, FString InRefreshKey); 27 | 28 | }; 29 | -------------------------------------------------------------------------------- /Source/Spotify/Spotify.Build.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class Spotify : ModuleRules 6 | { 7 | public Spotify(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange(new string[] 12 | { 13 | "Core", 14 | "CoreUObject", 15 | "Engine", 16 | "InputCore", 17 | "Http", 18 | "Networking", 19 | "Sockets", 20 | "Json", 21 | "DeveloperSettings" 22 | }); 23 | 24 | PrivateDependencyModuleNames.AddRange(new string[] 25 | { 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Source/Spotify/SpotifyDevSettings.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "SpotifyDevSettings.h" 5 | 6 | FName USpotifyDevSettings::GetContainerName() const 7 | { 8 | return TEXT("Project"); 9 | } 10 | 11 | FName USpotifyDevSettings::GetCategoryName() const 12 | { 13 | return NAME_Game; 14 | } 15 | 16 | FName USpotifyDevSettings::GetSectionName() const 17 | { 18 | return TEXT("Spotify"); 19 | } 20 | 21 | FText USpotifyDevSettings::GetSectionText() const 22 | { 23 | return NSLOCTEXT("Spotify", "Section", "Spotify"); 24 | } 25 | 26 | FText USpotifyDevSettings::GetSectionDescription() const 27 | { 28 | return NSLOCTEXT("Spotify", "Description", "Change your Spotify Game Setup."); 29 | } 30 | -------------------------------------------------------------------------------- /Config/HoloLens/HoloLensEngine.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | [/Script/HoloLensPlatformEditor.HoloLensTargetSettings] 4 | bBuildForEmulation=False 5 | bBuildForDevice=True 6 | bUseNameForLogo=True 7 | bBuildForRetailWindowsStore=False 8 | bAutoIncrementVersion=False 9 | bShouldCreateAppInstaller=False 10 | AppInstallerInstallationURL= 11 | HoursBetweenUpdateChecks=0 12 | bEnablePIXProfiling=False 13 | TileBackgroundColor=(B=64,G=0,R=0,A=255) 14 | SplashScreenBackgroundColor=(B=64,G=0,R=0,A=255) 15 | +PerCultureResources=(CultureId="",Strings=(PackageDisplayName="",PublisherDisplayName="",PackageDescription="",ApplicationDisplayName="",ApplicationDescription=""),Images=()) 16 | TargetDeviceFamily=Windows.Holographic 17 | MinimumPlatformVersion= 18 | MaximumPlatformVersionTested=10.0.18362.0 19 | MaxTrianglesPerCubicMeter=500.000000 20 | SpatialMeshingVolumeSize=20.000000 21 | CompilerVersion=Default 22 | Windows10SDKVersion=10.0.18362.0 23 | +CapabilityList=internetClientServer 24 | +CapabilityList=privateNetworkClientServer 25 | +Uap2CapabilityList=spatialPerception 26 | bSetDefaultCapabilities=False 27 | SpatializationPlugin= 28 | ReverbPlugin= 29 | OcclusionPlugin= 30 | SoundCueCookQualityIndex=-1 31 | 32 | -------------------------------------------------------------------------------- /Source/Spotify/SpotifyDevSettings.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Engine/DeveloperSettings.h" 7 | #include "SpotifyDevSettings.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS(Config=Game) 13 | class SPOTIFY_API USpotifyDevSettings : public UDeveloperSettings 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | 19 | // The Spotify Developer App Client ID (You'll Retrieve this from the Spotify Dev Console). 20 | UPROPERTY(Config, EditDefaultsOnly) 21 | FString ClientId; 22 | 23 | // The Callback on where to redirect (Needs to be the same as you entered in your spotify app settings.) 24 | UPROPERTY(Config, EditDefaultsOnly) 25 | FString Callback = TEXT("http://localhost:3036"); 26 | 27 | // The Save game where it saves the Refresh Key. 28 | UPROPERTY(Config, EditDefaultsOnly) 29 | FString SaveSlotName = TEXT("SpotifyCredentials"); 30 | 31 | public: 32 | 33 | virtual FName GetContainerName() const override; 34 | virtual FName GetCategoryName() const override; 35 | virtual FName GetSectionName() const override; 36 | virtual FText GetSectionText() const override; 37 | virtual FText GetSectionDescription() const override; 38 | }; 39 | -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | [URL] 2 | GameName=Spotify 3 | 4 | [/Script/EngineSettings.GameMapsSettings] 5 | EditorStartupMap=/Game/ExampleMap.ExampleMap 6 | GameDefaultMap=/Game/ExampleMap.ExampleMap 7 | TransitionMap= 8 | bUseSplitscreen=True 9 | TwoPlayerSplitscreenLayout=Horizontal 10 | ThreePlayerSplitscreenLayout=FavorTop 11 | GlobalDefaultGameMode=/Game/ThirdPersonBP/Blueprints/ThirdPersonGameMode.ThirdPersonGameMode_C 12 | GlobalDefaultServerGameMode=None 13 | 14 | [/Script/IOSRuntimeSettings.IOSRuntimeSettings] 15 | MinimumiOSVersion=IOS_13 16 | 17 | [/Script/HardwareTargeting.HardwareTargetingSettings] 18 | TargetedHardwareClass=Desktop 19 | AppliedTargetedHardwareClass=Desktop 20 | DefaultGraphicsPerformance=Maximum 21 | AppliedDefaultGraphicsPerformance=Maximum 22 | 23 | [/Script/Engine.RendererSettings] 24 | r.GenerateMeshDistanceFields=True 25 | r.DynamicGlobalIlluminationMethod=0 26 | r.ReflectionMethod=1 27 | r.Shadow.Virtual.Enable=1 28 | 29 | [/Script/Engine.Engine] 30 | +ActiveGameNameRedirects=(OldGameName="TP_ThirdPersonBP",NewGameName="/Script/Spotify") 31 | +ActiveGameNameRedirects=(OldGameName="/Script/TP_ThirdPersonBP",NewGameName="/Script/Spotify") 32 | 33 | [/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] 34 | bEnablePlugin=True 35 | bAllowNetworkConnection=True 36 | SecurityToken=7521842F493529E3BBA3DC9713FD850E 37 | bIncludeInShipping=False 38 | bAllowExternalStartInShipping=False 39 | bCompileAFSProject=False 40 | bUseCompression=False 41 | bLogFiles=False 42 | bReportStats=False 43 | ConnectionType=USBOnly 44 | bUseManualIPAddress=False 45 | ManualIPAddress= 46 | 47 | -------------------------------------------------------------------------------- /Source/Spotify/SHA256.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Updated to C++, zedwood.com 2012 3 | * Based on Olivier Gay's version 4 | * See Modified BSD License below: 5 | * 6 | * FIPS 180-2 SHA-224/256/384/512 implementation 7 | * Issue date: 04/30/2005 8 | * http://www.ouah.org/ogay/sha2/ 9 | * 10 | * Copyright (C) 2005, 2007 Olivier Gay 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without 14 | * modification, are permitted provided that the following conditions 15 | * are met: 16 | * 1. Redistributions of source code must retain the above copyright 17 | * notice, this list of conditions and the following disclaimer. 18 | * 2. Redistributions in binary form must reproduce the above copyright 19 | * notice, this list of conditions and the following disclaimer in the 20 | * documentation and/or other materials provided with the distribution. 21 | * 3. Neither the name of the project nor the names of its contributors 22 | * may be used to endorse or promote products derived from this software 23 | * without specific prior written permission. 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 26 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 | * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 29 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 | * SUCH DAMAGE. 36 | */ 37 | 38 | #pragma once 39 | 40 | #include 41 | 42 | class SHA256 43 | { 44 | protected: 45 | typedef unsigned char uint8; 46 | typedef unsigned int uint32; 47 | typedef unsigned long long uint64; 48 | 49 | const static uint32 sha256_k[]; 50 | static const unsigned int SHA224_256_BLOCK_SIZE = (512/8); 51 | public: 52 | void init(); 53 | void update(const unsigned char *message, unsigned int len); 54 | void final(unsigned char *digest); 55 | static const unsigned int DIGEST_SIZE = ( 256 / 8); 56 | 57 | protected: 58 | void transform(const unsigned char *message, unsigned int block_nb); 59 | unsigned int m_tot_len; 60 | unsigned int m_len; 61 | unsigned char m_block[2*SHA224_256_BLOCK_SIZE]; 62 | uint32 m_h[8]; 63 | }; 64 | 65 | TArray sha256(FString input); 66 | 67 | #define SHA2_SHFR(x, n) (x >> n) 68 | #define SHA2_ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) 69 | #define SHA2_ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) 70 | #define SHA2_CH(x, y, z) ((x & y) ^ (~x & z)) 71 | #define SHA2_MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) 72 | #define SHA256_F1(x) (SHA2_ROTR(x, 2) ^ SHA2_ROTR(x, 13) ^ SHA2_ROTR(x, 22)) 73 | #define SHA256_F2(x) (SHA2_ROTR(x, 6) ^ SHA2_ROTR(x, 11) ^ SHA2_ROTR(x, 25)) 74 | #define SHA256_F3(x) (SHA2_ROTR(x, 7) ^ SHA2_ROTR(x, 18) ^ SHA2_SHFR(x, 3)) 75 | #define SHA256_F4(x) (SHA2_ROTR(x, 17) ^ SHA2_ROTR(x, 19) ^ SHA2_SHFR(x, 10)) 76 | #define SHA2_UNPACK32(x, str) \ 77 | { \ 78 | *((str) + 3) = (uint8) ((x) ); \ 79 | *((str) + 2) = (uint8) ((x) >> 8); \ 80 | *((str) + 1) = (uint8) ((x) >> 16); \ 81 | *((str) + 0) = (uint8) ((x) >> 24); \ 82 | } 83 | #define SHA2_PACK32(str, x) \ 84 | { \ 85 | *(x) = ((uint32) *((str) + 3) ) \ 86 | | ((uint32) *((str) + 2) << 8) \ 87 | | ((uint32) *((str) + 1) << 16) \ 88 | | ((uint32) *((str) + 0) << 24); \ 89 | } 90 | -------------------------------------------------------------------------------- /Source/Spotify/SHA256.cpp: -------------------------------------------------------------------------------- 1 | #include "sha256.h" 2 | #include 3 | #include 4 | 5 | 6 | const unsigned int SHA256::sha256_k[64] = //UL = uint32 7 | {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 8 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 9 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 10 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 11 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 12 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 13 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 14 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 15 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 16 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 17 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 18 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 19 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 20 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 21 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 22 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; 23 | 24 | void SHA256::transform(const unsigned char *message, unsigned int block_nb) 25 | { 26 | uint32 w[64]; 27 | uint32 wv[8]; 28 | uint32 t1, t2; 29 | const unsigned char *sub_block; 30 | int i; 31 | int j; 32 | for (i = 0; i < (int) block_nb; i++) { 33 | sub_block = message + (i << 6); 34 | for (j = 0; j < 16; j++) { 35 | SHA2_PACK32(&sub_block[j << 2], &w[j]); 36 | } 37 | for (j = 16; j < 64; j++) { 38 | w[j] = SHA256_F4(w[j - 2]) + w[j - 7] + SHA256_F3(w[j - 15]) + w[j - 16]; 39 | } 40 | for (j = 0; j < 8; j++) { 41 | wv[j] = m_h[j]; 42 | } 43 | for (j = 0; j < 64; j++) { 44 | t1 = wv[7] + SHA256_F2(wv[4]) + SHA2_CH(wv[4], wv[5], wv[6]) 45 | + sha256_k[j] + w[j]; 46 | t2 = SHA256_F1(wv[0]) + SHA2_MAJ(wv[0], wv[1], wv[2]); 47 | wv[7] = wv[6]; 48 | wv[6] = wv[5]; 49 | wv[5] = wv[4]; 50 | wv[4] = wv[3] + t1; 51 | wv[3] = wv[2]; 52 | wv[2] = wv[1]; 53 | wv[1] = wv[0]; 54 | wv[0] = t1 + t2; 55 | } 56 | for (j = 0; j < 8; j++) { 57 | m_h[j] += wv[j]; 58 | } 59 | } 60 | } 61 | 62 | void SHA256::init() 63 | { 64 | m_h[0] = 0x6a09e667; 65 | m_h[1] = 0xbb67ae85; 66 | m_h[2] = 0x3c6ef372; 67 | m_h[3] = 0xa54ff53a; 68 | m_h[4] = 0x510e527f; 69 | m_h[5] = 0x9b05688c; 70 | m_h[6] = 0x1f83d9ab; 71 | m_h[7] = 0x5be0cd19; 72 | m_len = 0; 73 | m_tot_len = 0; 74 | } 75 | 76 | void SHA256::update(const unsigned char *message, unsigned int len) 77 | { 78 | unsigned int block_nb; 79 | unsigned int new_len, rem_len, tmp_len; 80 | const unsigned char *shifted_message; 81 | tmp_len = SHA224_256_BLOCK_SIZE - m_len; 82 | rem_len = len < tmp_len ? len : tmp_len; 83 | memcpy(&m_block[m_len], message, rem_len); 84 | if (m_len + len < SHA224_256_BLOCK_SIZE) { 85 | m_len += len; 86 | return; 87 | } 88 | new_len = len - rem_len; 89 | block_nb = new_len / SHA224_256_BLOCK_SIZE; 90 | shifted_message = message + rem_len; 91 | transform(m_block, 1); 92 | transform(shifted_message, block_nb); 93 | rem_len = new_len % SHA224_256_BLOCK_SIZE; 94 | memcpy(m_block, &shifted_message[block_nb << 6], rem_len); 95 | m_len = rem_len; 96 | m_tot_len += (block_nb + 1) << 6; 97 | } 98 | 99 | void SHA256::final(unsigned char *digest) 100 | { 101 | unsigned int block_nb; 102 | unsigned int pm_len; 103 | unsigned int len_b; 104 | int i; 105 | block_nb = (1 + ((SHA224_256_BLOCK_SIZE - 9) 106 | < (m_len % SHA224_256_BLOCK_SIZE))); 107 | len_b = (m_tot_len + m_len) << 3; 108 | pm_len = block_nb << 6; 109 | memset(m_block + m_len, 0, pm_len - m_len); 110 | m_block[m_len] = 0x80; 111 | SHA2_UNPACK32(len_b, m_block + pm_len - 4); 112 | transform(m_block, block_nb); 113 | for (i = 0 ; i < 8; i++) { 114 | SHA2_UNPACK32(m_h[i], &digest[i << 2]); 115 | } 116 | } 117 | 118 | TArray sha256(FString base) 119 | { 120 | const std::string input = TCHAR_TO_UTF8(*base); 121 | unsigned char digest[SHA256::DIGEST_SIZE]; 122 | memset(digest,0,SHA256::DIGEST_SIZE); 123 | 124 | SHA256 ctx = SHA256(); 125 | ctx.init(); 126 | ctx.update( (unsigned char*)input.c_str(), input.length()); 127 | ctx.final(digest); 128 | 129 | TArray Arr; 130 | for (int i = 0; i < SHA256::DIGEST_SIZE; i++) 131 | { 132 | Arr.Add(digest[i]); 133 | } 134 | return Arr; 135 | } -------------------------------------------------------------------------------- /Source/Spotify/SpotifyService.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "HttpModule.h" 7 | #include "Subsystems/GameInstanceSubsystem.h" 8 | #include "SpotifyService.generated.h" 9 | 10 | // Params: Song Name, Artists, Album Name, Volume, Progress, Duration, isPlaying 11 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_SevenParams(FOnReceivePlaybackDataDelegate, FString, SongName, const TArray&, 12 | Artists, FString, AlbumName, int, Volume, int, Progress, int, Duration, bool, isPlaying); 13 | 14 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnPlaybackAdvancedDelegate, int, Duration, int, Progress); 15 | 16 | /** 17 | * This Class handles the Spotify API 18 | * It has the same lifetime as a Game Instance (meaning it will persist between worlds) 19 | */ 20 | UCLASS() 21 | class SPOTIFY_API USpotifyService : public UGameInstanceSubsystem, public FTickableGameObject 22 | { 23 | GENERATED_BODY() 24 | 25 | protected: 26 | 27 | void SaveToSlot(); 28 | 29 | bool LoadCredentials(); 30 | 31 | UPROPERTY(Transient) 32 | FString SaveSlotName; 33 | 34 | // Your applications public key. 35 | UPROPERTY(Transient) 36 | FString ClientKey; 37 | 38 | // Where a user should be redirected to after approving. 39 | UPROPERTY(Transient) 40 | FString RedirectURL; 41 | 42 | // The Retrieved Auth key. 43 | UPROPERTY(Transient) 44 | FString AuthKey; 45 | 46 | // Access Key (the Key used for executing API calls). 47 | UPROPERTY(Transient) 48 | FString AccessKey; 49 | 50 | // This key wont run out, it is used to refresh the Access key once it runs out. 51 | UPROPERTY(Transient) 52 | FString RefreshKey; 53 | 54 | // For the Proof-Key-Challenge-Exchange (PKCE). 55 | UPROPERTY(Transient) 56 | FString Verify; 57 | 58 | // The Auto-Generated Challenge for PKCE. 59 | UPROPERTY(Transient) 60 | FString Challenge; 61 | 62 | UPROPERTY(Transient) 63 | FDateTime AccessKeyExpiration; 64 | 65 | FHttpModule* Http; 66 | 67 | // This Socket listens for incoming connection on localhost:Port. 68 | FSocket* ServerSocket; 69 | 70 | // This Socket exchanges Data. 71 | FSocket* ConnectionSocket; 72 | 73 | // Whether or not we should listen on a pending connection. 74 | bool bListening; 75 | 76 | FTimerHandle AccessKeyExpireTimerHandle; 77 | 78 | FTimerHandle PlaybackInfoTimerHandle; 79 | 80 | // Only call big update whenever song ID changes. 81 | UPROPERTY(Transient) 82 | FString SongId; 83 | 84 | public: 85 | 86 | UPROPERTY(BlueprintAssignable) 87 | FOnReceivePlaybackDataDelegate OnReceivePlaybackDataDelegate; 88 | 89 | UPROPERTY(BlueprintAssignable) 90 | FOnPlaybackAdvancedDelegate OnPlaybackAdvancedDelegate; 91 | 92 | protected: 93 | 94 | #pragma region Authentication 95 | 96 | // Start Auth Procedure. 97 | UFUNCTION() 98 | void BeginAuthorization(); 99 | 100 | // Creates the Server Socket. 101 | FSocket* CreateServerSocket(); 102 | 103 | // Listening for incoming connections. 104 | void TCPListener(); 105 | 106 | // Exchange Data between connections. 107 | void ConnectionListener(); 108 | 109 | // Filter Auth key from HTTP Request. 110 | void RetrieveAuthKey(FString HttpResponse); 111 | 112 | void RefreshAccessKey(); 113 | 114 | #pragma endregion 115 | 116 | #pragma region API Requests 117 | ///////////////////////////////////////// 118 | // API Requests 119 | 120 | // Requests a new Refresh Key. 121 | void RequestRefreshKey(); 122 | 123 | // Requests Information about Playback. (Title, Artist, Duration, Progression, Volume etc...) 124 | void RequestPlaybackInformation(); 125 | 126 | // Request the player to start or resume playback. 127 | UFUNCTION(BlueprintCallable) 128 | void RequestPlay(); 129 | 130 | void PlaybackRequest(const FString& Url, const FString& Verb); 131 | 132 | void PlaybackRequest(const FString& Url, const FString& Verb, const FString& Body); 133 | 134 | // Request the player to pause playback. 135 | UFUNCTION(BlueprintCallable) 136 | void RequestPause(); 137 | 138 | UFUNCTION(BlueprintCallable) 139 | void RequestNext(); 140 | 141 | UFUNCTION(BlueprintCallable) 142 | void RequestPrev(); 143 | 144 | UFUNCTION(BlueprintCallable) 145 | void Seek(int TimeInSeconds); 146 | 147 | UFUNCTION(BlueprintCallable) 148 | void SetVolume(float Val); 149 | 150 | ///////////////////////////////////////// 151 | // API Responses 152 | 153 | // Received the Refresh Key. 154 | void ReceiveRefreshKey(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); 155 | 156 | // When Playback info is received. 157 | void ReceivePlaybackInformation(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); 158 | 159 | // Whether the playback started or not. 160 | void ReceivePlay(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); 161 | 162 | // Handle common error messages. 163 | void OnError(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); 164 | 165 | #pragma endregion 166 | 167 | public: 168 | 169 | // FTickableGameObject Interface 170 | virtual void Tick(float DeltaTime) override; 171 | virtual TStatId GetStatId() const override 172 | { 173 | return GetStatID(); 174 | } 175 | // End of FTickableGameObject Interface 176 | 177 | // UGameInstanceSubsystem Interface 178 | virtual bool ShouldCreateSubsystem(UObject* Outer) const override; 179 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 180 | virtual void Deinitialize() override; 181 | // End of UGameInstanceSubsystem Interface 182 | 183 | }; 184 | -------------------------------------------------------------------------------- /Source/Spotify/SpotifyService.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "SpotifyService.h" 5 | #include "Spotify.h" 6 | #include "SHA256.h" 7 | #include "SpotifyCredentials.h" 8 | #include "SpotifyDevSettings.h" 9 | #include "Common/TcpSocketBuilder.h" 10 | #include "GenericPlatform/GenericPlatformHttp.h" 11 | #include "Interfaces/IHttpResponse.h" 12 | #include "Kismet/GameplayStatics.h" 13 | #include "Kismet/KismetSystemLibrary.h" 14 | 15 | void USpotifyService::SaveToSlot() 16 | { 17 | auto SaveGame = (USpotifyCredentials*)UGameplayStatics::CreateSaveGameObject(USpotifyCredentials::StaticClass()); 18 | SaveGame->SetValues(Verify, Challenge, RefreshKey); 19 | UGameplayStatics::SaveGameToSlot(SaveGame, SaveSlotName, 0); 20 | } 21 | 22 | bool USpotifyService::LoadCredentials() 23 | { 24 | if(UGameplayStatics::DoesSaveGameExist(SaveSlotName, 0)) 25 | { 26 | auto SaveGame = (USpotifyCredentials*)UGameplayStatics::LoadGameFromSlot(SaveSlotName, 0); 27 | if(SaveGame && !SaveGame->Verify.IsEmpty() && !SaveGame->Challenge.IsEmpty() && !SaveGame->RefreshKey.IsEmpty()) 28 | { 29 | Verify = SaveGame->Verify; 30 | Challenge = SaveGame->Challenge; 31 | RefreshKey = SaveGame->RefreshKey; 32 | return true; 33 | } 34 | } 35 | return false; 36 | } 37 | 38 | void USpotifyService::BeginAuthorization() 39 | { 40 | 41 | // Just a bunch of characters. 42 | const FString RandomChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 43 | 44 | // Create a random string for the Secret-keyless authentication. 45 | for (int i = 0; i < 64; i++) 46 | { 47 | Verify += RandomChars.GetCharArray()[FMath::RandRange(0, RandomChars.Len() - 1)]; 48 | } 49 | // Create and Base64 encode the challenge 50 | Challenge = FBase64::Encode(sha256(Verify)); 51 | Challenge = Challenge.Replace(TEXT("+"), TEXT("-")) 52 | .Replace(TEXT("/"), TEXT("_")) 53 | .Replace(TEXT(" "), TEXT("")); 54 | Challenge.RemoveFromEnd("="); 55 | 56 | UKismetSystemLibrary::LaunchURL(FString::Printf(TEXT("https://accounts.spotify.com/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=user-modify-playback-state,user-read-playback-state,user-read-currently-playing&code_challenge=%s&code_challenge_method=S256"), 57 | *ClientKey, *RedirectURL, *Challenge)); 58 | 59 | ServerSocket = CreateServerSocket(); 60 | 61 | TSharedRef RemoteAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); 62 | } 63 | 64 | FSocket* USpotifyService::CreateServerSocket() 65 | { 66 | const uint16 Port = FGenericPlatformHttp::GetUrlPort(RedirectURL).GetValue(); 67 | const FIPv4Endpoint Endpoint(FIPv4Address::InternalLoopback, Port); 68 | FSocket* NewSock = FTcpSocketBuilder(TEXT("HttpServer")) 69 | .AsReusable() 70 | .Listening(8) 71 | .BoundToEndpoint(Endpoint) 72 | .WithReceiveBufferSize(2 * 1024 * 1024) 73 | .Build(); 74 | return NewSock; 75 | } 76 | 77 | void USpotifyService::TCPListener() 78 | { 79 | if(!ServerSocket) return; 80 | auto Sockets = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); 81 | const TSharedRef RemoteAddress = Sockets->CreateInternetAddr(); 82 | 83 | bool bPending; 84 | ServerSocket->HasPendingConnection(bPending); 85 | if(bPending) 86 | { 87 | //if(!bPending) return; 88 | if(ConnectionSocket) 89 | { 90 | ConnectionSocket->Close(); 91 | Sockets->DestroySocket(ConnectionSocket); 92 | } 93 | 94 | ConnectionSocket = ServerSocket->Accept(*RemoteAddress, TEXT("ServerConnection")); 95 | if(ConnectionSocket != nullptr) 96 | { 97 | bListening = true; 98 | } 99 | } 100 | 101 | } 102 | 103 | void USpotifyService::ConnectionListener() 104 | { 105 | if(!bListening || !ConnectionSocket) return; 106 | 107 | TArray ReceivedData; 108 | uint32 Size; 109 | 110 | while(ConnectionSocket->HasPendingData(Size)) 111 | { 112 | ReceivedData.Init(0, FMath::Min(Size, 65507u)); 113 | int32 ReadData = 0; 114 | ConnectionSocket->Recv(ReceivedData.GetData(), ReceivedData.Num(), ReadData); 115 | } 116 | 117 | ReceivedData.Add(0); 118 | const FString Request = UTF8_TO_TCHAR(ReceivedData.GetData()); 119 | if(!Request.IsEmpty()) 120 | RetrieveAuthKey(Request); 121 | 122 | const FString Response = TEXT("HTTP/1.1 200 OK\r\n\ 123 | Cache-Control: no-cache, private\n\r\ 124 | Server: Unreal-Socket-Server\n\r\n\r\ 125 | \r\n\r\nSuccess!\ 126 | \r\n\r

Success!

\n

You can close this window now!

"); 127 | 128 | const TCHAR* ResponseData = *Response; 129 | const int32 ResponseSize = FCString::Strlen(ResponseData); 130 | int32 ResponseSent = 0; 131 | ConnectionSocket->Send((uint8*)TCHAR_TO_UTF8(ResponseData), ResponseSize, ResponseSent); 132 | ConnectionSocket->Shutdown(ESocketShutdownMode::ReadWrite); 133 | bListening = false; 134 | } 135 | 136 | void USpotifyService::RetrieveAuthKey(FString HttpResponse) 137 | { 138 | const FRegexPattern AuthCodeRegex(TEXT("/?code=([\\d\\w-_]+)")); 139 | const FRegexPattern ErrorCodeRegex(TEXT("/?error=([\\d\\w-_]+)")); 140 | FRegexMatcher AuthMatcher(AuthCodeRegex, HttpResponse); 141 | FRegexMatcher ErrorMatcher(ErrorCodeRegex, HttpResponse); 142 | if(AuthMatcher.FindNext()) 143 | { 144 | AuthKey = AuthMatcher.GetCaptureGroup(1); 145 | bListening = false; 146 | ServerSocket->Close(); 147 | RequestRefreshKey(); 148 | } 149 | if(ErrorMatcher.FindNext()) 150 | { 151 | UE_LOG(LogSpotify, Error, TEXT("Error Authenticating with Spotify: %s"), *ErrorMatcher.GetCaptureGroup(1)); 152 | } 153 | 154 | } 155 | 156 | void USpotifyService::RefreshAccessKey() 157 | { 158 | if(RefreshKey.IsEmpty() || ClientKey.IsEmpty()) return; 159 | 160 | UE_LOG(LogSpotify, Verbose, TEXT("Requesting new Access Key.")); 161 | 162 | auto Request = Http->CreateRequest(); 163 | Request->SetURL("https://accounts.spotify.com/api/token"); 164 | Request->SetVerb("POST"); 165 | Request->SetHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); 166 | Request->SetContentAsString(FString::Printf(TEXT("grant_type=refresh_token&refresh_token=%s&client_id=%s"), *RefreshKey, *ClientKey)); 167 | Request->OnProcessRequestComplete().BindUObject(this, &USpotifyService::ReceiveRefreshKey); 168 | Request->ProcessRequest(); 169 | } 170 | 171 | void USpotifyService::RequestRefreshKey() 172 | { 173 | if(!Http) return; 174 | 175 | auto Request = Http->CreateRequest(); 176 | Request->SetURL("https://accounts.spotify.com/api/token"); 177 | Request->SetVerb("POST"); 178 | const FString Body = FString::Printf(TEXT("grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s&code_verifier=%s"), 179 | *AuthKey, *RedirectURL, *ClientKey, *Verify); 180 | Request->SetContentAsString(Body); 181 | Request->SetHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); 182 | Request->OnProcessRequestComplete().BindUObject(this, &USpotifyService::ReceiveRefreshKey); 183 | Request->ProcessRequest(); 184 | } 185 | 186 | void USpotifyService::RequestPlaybackInformation() 187 | { 188 | if(!Http || AccessKey.IsEmpty()) return; 189 | 190 | auto Request = Http->CreateRequest(); 191 | Request->SetURL("https://api.spotify.com/v1/me/player?market=from_token"); 192 | Request->SetVerb("GET"); 193 | Request->SetHeader("Authorization", FString::Printf(TEXT("Bearer %s"), *AccessKey)); 194 | Request->OnProcessRequestComplete().BindUObject(this, &USpotifyService::ReceivePlaybackInformation); 195 | Request->ProcessRequest(); 196 | UE_LOG(LogSpotify, Verbose, TEXT("Requesting Playback Info.")); 197 | } 198 | 199 | void USpotifyService::PlaybackRequest(const FString& Url, const FString& Verb) 200 | { 201 | if(!Http || AccessKey.IsEmpty()) return; 202 | 203 | auto Request = Http->CreateRequest(); 204 | Request->SetURL(Url); 205 | Request->SetVerb(Verb); 206 | Request->SetHeader("Authorization", FString::Printf(TEXT("Bearer %s"), *AccessKey)); 207 | Request->OnProcessRequestComplete().BindUObject(this, &USpotifyService::ReceivePlay); 208 | Request->ProcessRequest(); 209 | } 210 | 211 | void USpotifyService::PlaybackRequest(const FString& Url, const FString& Verb, const FString& Body) 212 | { 213 | if(!Http || AccessKey.IsEmpty()) return; 214 | 215 | auto Request = Http->CreateRequest(); 216 | Request->SetURL(Url); 217 | Request->SetVerb(Verb); 218 | Request->SetHeader("Authorization", FString::Printf(TEXT("Bearer %s"), *AccessKey)); 219 | Request->SetContentAsString(Body); 220 | Request->OnProcessRequestComplete().BindUObject(this, &USpotifyService::ReceivePlay); 221 | Request->ProcessRequest(); 222 | } 223 | 224 | void USpotifyService::RequestPlay() 225 | { 226 | PlaybackRequest("https://api.spotify.com/v1/me/player/play", "PUT"); 227 | UE_LOG(LogSpotify, Verbose, TEXT("Requesting Resume Playback.")); 228 | } 229 | 230 | void USpotifyService::RequestPause() 231 | { 232 | PlaybackRequest("https://api.spotify.com/v1/me/player/pause", "PUT"); 233 | UE_LOG(LogSpotify, Verbose, TEXT("Requesting Pause Playback.")); 234 | } 235 | 236 | void USpotifyService::RequestNext() 237 | { 238 | PlaybackRequest("https://api.spotify.com/v1/me/player/next", "POST"); 239 | UE_LOG(LogSpotify, Verbose, TEXT("Requesting Next Song.")); 240 | } 241 | 242 | void USpotifyService::RequestPrev() 243 | { 244 | PlaybackRequest("https://api.spotify.com/v1/me/player/previous", "POST"); 245 | UE_LOG(LogSpotify, Verbose, TEXT("Requesting Previous Song.")); 246 | } 247 | 248 | void USpotifyService::Seek(int TimeInSeconds) 249 | { 250 | const int TimeInMS = TimeInSeconds * 1000; 251 | PlaybackRequest("https://api.spotify.com/v1/me/player/previous", "PUT", FString::Printf(TEXT("position_ms=%d"), TimeInMS)); 252 | UE_LOG(LogSpotify, Verbose, TEXT("Requesting Seek.")); 253 | } 254 | 255 | void USpotifyService::SetVolume(float Val) 256 | { 257 | const int VolPercent = FMath::Clamp(Val * 100, 0, 100); 258 | PlaybackRequest(FString::Printf(TEXT("https://api.spotify.com/v1/me/player/volume?volume_percent=%d"), VolPercent), "PUT"); 259 | UE_LOG(LogSpotify, Verbose, TEXT("Requesting Volume.")); 260 | } 261 | 262 | void USpotifyService::ReceiveRefreshKey(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) 263 | { 264 | if(!bWasSuccessful) return; 265 | 266 | if( Response->GetResponseCode() >= 200 && Response->GetResponseCode() < 300) 267 | { 268 | const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); 269 | 270 | TSharedPtr ParsedResponse; 271 | if(FJsonSerializer::Deserialize(JsonReader, ParsedResponse)) 272 | { 273 | const int Expires = ParsedResponse->GetIntegerField("expires_in"); 274 | AccessKeyExpiration = FDateTime::Now() + FTimespan(0, 0, Expires); 275 | AccessKey = ParsedResponse->GetStringField("access_token"); 276 | RefreshKey = ParsedResponse->GetStringField("refresh_token"); 277 | // Resfresh Access Key 50 Seconds before it expires. 278 | GetWorld()->GetTimerManager().ClearTimer(AccessKeyExpireTimerHandle); 279 | GetWorld()->GetTimerManager().ClearTimer(PlaybackInfoTimerHandle); 280 | GetWorld()->GetTimerManager().SetTimer(AccessKeyExpireTimerHandle, this, &USpotifyService::RefreshAccessKey, Expires - 50, false); 281 | GetWorld()->GetTimerManager().SetTimer(PlaybackInfoTimerHandle, this, &USpotifyService::RequestPlaybackInformation, 1, true); 282 | } 283 | 284 | return; 285 | } 286 | UE_LOG(LogSpotify, Error, TEXT("RES: %s"), *Response->GetContentAsString()); 287 | } 288 | 289 | 290 | void USpotifyService::ReceivePlaybackInformation(FHttpRequestPtr Request, FHttpResponsePtr Response, 291 | bool bWasSuccessful) 292 | { 293 | if(!bWasSuccessful) return; 294 | if(Response->GetResponseCode() == 200) 295 | { 296 | const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); 297 | TSharedPtr ParsedResponse; 298 | if(FJsonSerializer::Deserialize(JsonReader, ParsedResponse)) 299 | { 300 | 301 | const int Progress = ParsedResponse->GetIntegerField("progress_ms"); 302 | const bool Playing = ParsedResponse->GetBoolField("is_playing"); 303 | 304 | 305 | const TSharedPtr Device = ParsedResponse->GetObjectField("device"); 306 | const TSharedPtr Item = ParsedResponse->GetObjectField("item"); 307 | const TArray> Artists = Item->GetArrayField("artists"); 308 | const TSharedPtr Album = Item->GetObjectField("album"); 309 | 310 | const int Volume = Device->GetIntegerField("volume_percent"); 311 | const int Duration = Item->GetIntegerField("duration_ms"); 312 | const FString SongName = Item->GetStringField("name"); 313 | const FString AlbumName = Album->GetStringField("name"); 314 | TArray ArtistNames; 315 | for(const auto& Artist : Artists) 316 | { 317 | ArtistNames.Add( Artist->AsObject()->GetStringField("name")); 318 | } 319 | 320 | if(SongId == Item->GetStringField("id")) 321 | { 322 | OnPlaybackAdvancedDelegate.Broadcast(Duration, Progress); 323 | return; 324 | } 325 | SongId = Item->GetStringField("id"); 326 | OnReceivePlaybackDataDelegate.Broadcast(SongName, ArtistNames, AlbumName, Volume, Duration, Progress, Playing); 327 | 328 | } 329 | } 330 | if(Response->GetResponseCode() == 204) 331 | { 332 | UE_LOG(LogSpotify, Verbose, TEXT("Received Playback, no device playing or in private session.")); 333 | } 334 | } 335 | 336 | void USpotifyService::ReceivePlay(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) 337 | { 338 | if(!bWasSuccessful) return; 339 | if(Response->GetResponseCode() == 204) 340 | { 341 | UE_LOG(LogSpotify, Verbose, TEXT("Request Successful.")); 342 | } 343 | else if(Response->GetResponseCode() == 404) 344 | { 345 | UE_LOG(LogSpotify, Error, TEXT("Device not Found")); 346 | } 347 | else if(Response->GetResponseCode() == 403) 348 | { 349 | UE_LOG(LogSpotify, Error, TEXT("User is Non-Premium")); 350 | } 351 | else 352 | { 353 | UE_LOG(LogSpotify, Error, TEXT("%s"), *Response->GetContentAsString()); 354 | } 355 | } 356 | 357 | void USpotifyService::OnError(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) 358 | { 359 | 360 | } 361 | 362 | 363 | void USpotifyService::Tick(float DeltaTime) 364 | { 365 | TCPListener(); 366 | ConnectionListener(); 367 | } 368 | 369 | bool USpotifyService::ShouldCreateSubsystem(UObject* Outer) const 370 | { 371 | return Super::ShouldCreateSubsystem(Outer); 372 | } 373 | 374 | void USpotifyService::Initialize(FSubsystemCollectionBase& Collection) 375 | { 376 | Super::Initialize(Collection); 377 | Http = &FModuleManager::LoadModuleChecked("Http").Get(); 378 | 379 | const auto Settings = GetDefault(); 380 | ClientKey = Settings->ClientId; 381 | RedirectURL = Settings->Callback; 382 | SaveSlotName = Settings->SaveSlotName; 383 | 384 | if(LoadCredentials()) 385 | { 386 | RefreshAccessKey(); 387 | return; 388 | } 389 | 390 | 391 | BeginAuthorization(); 392 | } 393 | 394 | void USpotifyService::Deinitialize() 395 | { 396 | if(!RefreshKey.IsEmpty() && !Verify.IsEmpty() && !Challenge.IsEmpty()) 397 | { 398 | SaveToSlot(); 399 | } 400 | if(ConnectionSocket) 401 | { 402 | ConnectionSocket->Close(); 403 | ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ConnectionSocket); 404 | } 405 | if(ServerSocket) 406 | { 407 | ServerSocket->Close(); 408 | ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ServerSocket); 409 | } 410 | Super::Deinitialize(); 411 | } 412 | -------------------------------------------------------------------------------- /Config/DefaultInput.ini: -------------------------------------------------------------------------------- 1 | 2 | [/Script/Engine.InputSettings] 3 | +AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 4 | +AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 5 | +AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 6 | +AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 7 | +AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) 8 | +AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) 9 | +AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 10 | +AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 11 | +AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 12 | +AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 13 | +AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 14 | +AxisConfig=(AxisKeyName="MotionController_Left_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 15 | +AxisConfig=(AxisKeyName="MotionController_Left_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 16 | +AxisConfig=(AxisKeyName="MotionController_Left_Grip2Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 17 | +AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 18 | +AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 19 | +AxisConfig=(AxisKeyName="MotionController_Right_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 20 | +AxisConfig=(AxisKeyName="MotionController_Right_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 21 | +AxisConfig=(AxisKeyName="MotionController_Right_Grip2Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 22 | +AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 23 | +AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 24 | +AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_Z",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 25 | +AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_Z",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 26 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 27 | +AxisConfig=(AxisKeyName="OculusTouch_Left_FaceButton1",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 28 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 29 | +AxisConfig=(AxisKeyName="OculusTouch_Left_FaceButton2",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 30 | +AxisConfig=(AxisKeyName="OculusTouch_Left_IndexPointing",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 31 | +AxisConfig=(AxisKeyName="OculusTouch_Left_ThumbUp",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 32 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 33 | +AxisConfig=(AxisKeyName="OculusTouch_Right_FaceButton1",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 34 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 35 | +AxisConfig=(AxisKeyName="OculusTouch_Right_FaceButton2",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 36 | +AxisConfig=(AxisKeyName="OculusTouch_Right_IndexPointing",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 37 | +AxisConfig=(AxisKeyName="OculusTouch_Right_ThumbUp",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 38 | +AxisConfig=(AxisKeyName="OculusTouchpad_Touchpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 39 | +AxisConfig=(AxisKeyName="OculusTouchpad_Touchpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 40 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Left_HandGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 41 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Left_IndexGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 42 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Left_MiddleGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 43 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Left_RingGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 44 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Left_PinkyGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 45 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Right_HandGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 46 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Right_IndexGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 47 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Right_MiddleGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 48 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Right_RingGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 49 | +AxisConfig=(AxisKeyName="SteamVR_Knuckles_Right_PinkyGrip",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 50 | +AxisConfig=(AxisKeyName="Daydream_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 51 | +AxisConfig=(AxisKeyName="Daydream_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 52 | +AxisConfig=(AxisKeyName="Daydream_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 53 | +AxisConfig=(AxisKeyName="Daydream_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 54 | +AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 55 | +AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 56 | +AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 57 | +AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 58 | +AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 59 | +AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 60 | +AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 61 | +AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 62 | +AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 63 | +AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 64 | +AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 65 | +AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 66 | +AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 67 | +AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 68 | +AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 69 | +AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 70 | +AxisConfig=(AxisKeyName="OculusGo_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 71 | +AxisConfig=(AxisKeyName="OculusGo_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 72 | +AxisConfig=(AxisKeyName="OculusGo_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 73 | +AxisConfig=(AxisKeyName="OculusGo_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 74 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 75 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 76 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 77 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 78 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 79 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 80 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 81 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 82 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 83 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 84 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 85 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 86 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 87 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 88 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 89 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 90 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Touch",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 91 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 92 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 93 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 94 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 95 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 96 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 97 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 98 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 99 | bAltEnterTogglesFullscreen=True 100 | bF11TogglesFullscreen=True 101 | bUseMouseForTouch=False 102 | bEnableMouseSmoothing=True 103 | bEnableFOVScaling=True 104 | bCaptureMouseOnLaunch=True 105 | bAlwaysShowTouchInterface=False 106 | bShowConsoleOnFourFingerTap=True 107 | bEnableGestureRecognizer=False 108 | bUseAutocorrect=False 109 | DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown 110 | DefaultViewportMouseLockMode=LockOnCapture 111 | FOVScale=0.011110 112 | DoubleClickTime=0.200000 113 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=SpaceBar) 114 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Gamepad_FaceButton_Bottom) 115 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Daydream_Left_Select_Click) 116 | +ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=R) 117 | +ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Daydream_Left_Trackpad_Click) 118 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Left_Trigger_Click) 119 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Right_Trigger_Click) 120 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Left_Trigger_Click) 121 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Right_Trigger_Click) 122 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusGo_Left_Trigger_Click) 123 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Left_Trigger_Click) 124 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_Trigger_Click) 125 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Left_Trigger_Click) 126 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Right_Trigger_Click) 127 | +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MagicLeap_Left_Trigger) 128 | +ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Left_Grip_Click) 129 | +ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Left_Thumbstick_Click) 130 | +ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusGo_Left_Trackpad_Click) 131 | +ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Left_Thumbstick_Click) 132 | +ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Left_Thumbstick_Click) 133 | +ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MagicLeap_Left_Bumper) 134 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=W) 135 | +AxisMappings=(AxisName="MoveForward",Scale=-1.000000,Key=S) 136 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=Up) 137 | +AxisMappings=(AxisName="MoveForward",Scale=-1.000000,Key=Down) 138 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=Gamepad_LeftY) 139 | +AxisMappings=(AxisName="MoveRight",Scale=-1.000000,Key=A) 140 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=D) 141 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=Gamepad_LeftX) 142 | +AxisMappings=(AxisName="TurnRate",Scale=1.000000,Key=Gamepad_RightX) 143 | +AxisMappings=(AxisName="TurnRate",Scale=-1.000000,Key=Left) 144 | +AxisMappings=(AxisName="TurnRate",Scale=1.000000,Key=Right) 145 | +AxisMappings=(AxisName="Turn",Scale=1.000000,Key=MouseX) 146 | +AxisMappings=(AxisName="LookUpRate",Scale=1.000000,Key=Gamepad_RightY) 147 | +AxisMappings=(AxisName="LookUp",Scale=-1.000000,Key=MouseY) 148 | +AxisMappings=(AxisName="TurnRate",Scale=-1.000000,Key=Vive_Right_Trackpad_X) 149 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=Daydream_Left_Trackpad_Y) 150 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=Vive_Left_Trackpad_Y) 151 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=Daydream_Left_Trackpad_X) 152 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=Vive_Left_Trackpad_X) 153 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=MixedReality_Left_Thumbstick_X) 154 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=OculusGo_Left_Trackpad_X) 155 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=MixedReality_Left_Thumbstick_Y) 156 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=OculusGo_Left_Trackpad_Y) 157 | +AxisMappings=(AxisName="TurnRate",Scale=-1.000000,Key=MixedReality_Right_Thumbstick_X) 158 | +AxisMappings=(AxisName="TurnRate",Scale=-1.000000,Key=OculusTouch_Right_Thumbstick_X) 159 | +AxisMappings=(AxisName="TurnRate",Scale=-1.000000,Key=ValveIndex_Right_Thumbstick_X) 160 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=OculusTouch_Left_Thumbstick_Y) 161 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=ValveIndex_Left_Thumbstick_Y) 162 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=MagicLeap_Left_Trackpad_Y) 163 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=OculusTouch_Left_Thumbstick_X) 164 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=ValveIndex_Left_Thumbstick_X) 165 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=MagicLeap_Left_Trackpad_X) 166 | DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks 167 | +ConsoleKeys=Tilde 168 | 169 | 170 | --------------------------------------------------------------------------------