From e8bca1d76504b7583b01659c6be4359ed43655e4 Mon Sep 17 00:00:00 2001 From: Sectorbob Date: Wed, 30 Mar 2022 11:47:03 -0400 Subject: [PATCH 1/4] add NVIDIA Shield Default box-art.png to Resources --- UWPHook/Properties/Resources.Designer.cs | 10 ++++++++++ UWPHook/Properties/Resources.resx | 3 +++ UWPHook/Resources/box-art.png | Bin 0 -> 6129 bytes 3 files changed, 13 insertions(+) create mode 100644 UWPHook/Resources/box-art.png diff --git a/UWPHook/Properties/Resources.Designer.cs b/UWPHook/Properties/Resources.Designer.cs index 236c6b6..aa2687d 100644 --- a/UWPHook/Properties/Resources.Designer.cs +++ b/UWPHook/Properties/Resources.Designer.cs @@ -60,6 +60,16 @@ internal Resources() { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap box_art { + get { + object obj = ResourceManager.GetObject("box_art", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/UWPHook/Properties/Resources.resx b/UWPHook/Properties/Resources.resx index 4285897..8898583 100644 --- a/UWPHook/Properties/Resources.resx +++ b/UWPHook/Properties/Resources.resx @@ -118,6 +118,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\box-art.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\GetAUMIDScript.ps1;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/UWPHook/Resources/box-art.png b/UWPHook/Resources/box-art.png new file mode 100644 index 0000000000000000000000000000000000000000..80b8fb75770db59ec4ad29f51ab51de95ba3d9b7 GIT binary patch literal 6129 zcmeHL>01+d@{f)R3WAbRt{^HBR(b^)&HxLd4v0&@yNL!lVn76uj&g|s<48{QA zkXaQR1rtdaXa@u#kw`d3MJ@q>gy1M=P>v-eZZppO1;6L_j#pjZPgi}ay1KfmpQ0b~ z_E^8pU>ypDTJL#a{}B{wz?aYNB@>b9Dfg$800$R2eknW zqG)sgB@}K#1xePStf19bbK#YHyof8dVwZ0(r)9YumOq2#?(qN673bz=XJ=I^)p&0e zJ*wx)N+dqyVHO!P*+<)3@E z5Fp!aZ^G#lrqs{Nr6L~0#LaU$xsk!Se=0TvXf`+EgL`1Mm%?pT=aQ}deY4MY#bha# zUy~K7ArYHC`#Lu?J@7Tm>+(oJ<@9ZlVm{VKl<(xS>Yo?vh(Ey;(|ZpYc@|Qus@b#S z73rKIOCd``kD1HZSIbYu-K=__TmWx|$kFKcB*9Mip#E~faT@U1fVqi(JRxv%vndU* zz8oK~a2cSMwvaGePB>dxLu5zNXaM7*{y zUa+E%Euafo_t=#bJs{s99j4f{pU{$ufsl=$){IJ3s~I1u-h3UG3RC`(o`UNZiA0pC z`fEbgKAc%geS!YS_YyJW?2vTypCV(-0V1yL*l3C1Uv5(68U=!jTZEMi!a*4~{<=GTtdN*#l6k8smZ$5Jiztnwf6EUGEakjz1H zdDp#6!%4J}tINJ1u%_z?QhEjCU-FY-)r;A(1UqbIb6xx`IVBr^?H&zKV~=Zrkdi6? zp~!LV;wGuFbIhcStFH1U;$9vrWv_vh9z!WJF=pO@F;u@PCrGxH&+;TD#yo%Yj_*Pu zvP-!ZX;IVKuG{Y)}bV{e@9s(gFJDj;^hYf|`@< z!axLT?YXbZ7(MpkK+gGhMIN4!dJs8AWD}Z^??bSn>mPk4yHOWBqQ_`Gb-H7fR_|fJ z<#4^DL_Cpw?R*H@g-vZsZ`D3J&Jjc;SC-HPG(h1P-uHbMH~)!MbN2X!25HJE9dI_P z`y3Wh{AqVz0DhXXkmAeBoWtYRi_^&)dSf3l;E zcJ1o5XPV5hZ1I>qh+^QMFYGt6BA7BKM+2N3r`cnYg{bD)*5Q{_8ImzXvAI5U z<2NsHaflaS%pJi3?LlXR@!9la2W|AkiijmF>l-1MKocf=cs{X*$d(?QU<2XID3w%`R z&&l%px~cE%edPuc~>pwt#o9EHnt|U>3m>VnJGI#OJzSxcQ%I{@~$7d7q@K z=S(YE1nH-D;M2`J z(eOndb=QLV`lMboc)9tNtv#e18)ONXM29CObR`Wa|99C^5_E6OL_IPQ=3%3^bA%S< zd7&G+28>>H^=N4$q0nKS>VwyWGsfcv`T88bs^Obj+hy!?zHr9p#27nqW_H%@!~0(8 zi-xTnVYXY?WyylB6n(@YwQ%uZJ&*O14tNkGwA#={>kPOtq#zOF_e8894?DY+a~oT9 z?+TXVZ}VwhE|(v>))-PIHvymS+0gcS$P6mJ^q<8HywUntaaiNbgN@DQYVK)`9KFX% zcCRRr@fDZksA@Q#x4=X)<3`-+7ZH#uzZQVI@*fkZVGV^&iW2;stIRkyE+Yy%t9i$;yt8y*eJLBZ`6g+*48JK0y=%%@RTeB+lYLKr zo#f#2bJ&RIdDy*wN&7aer2{JvlNDik>VHX)=?+df*%n<@j%k0whIuS5B(rl~m6e8w z9bSFcZSlv1tj7rD-ylpe?6f(&5v)9U-4u4wcDuiw6CU(%w>=4uUJH@Y+Ae)(y;ziH z*H-f_NeA{4dxS@Z5^c}TAyZ$z^GwFO2Q6uhmPU1#!=HC+epG^+HIYGLSDyFL(!B|b zc__kqj`{nx?_aXptu$i}ydAnND53!fGn2eW!jxZ=>U51ax-W!(VPqSvGNb{c1oOq} ztWu3#_D1=SxOLU9oL*K4Su?c={MApMAyKDme5iX8kUNfm#45$uo32k^W~?=?_jnTt zzta}mi<=h)`xig1nZzQ~tMlU1i6J{ud94c#=%oXryY{^ftM1uT#ud!Jg~zmf|4k$` z9T^(R9ynCOO@FL#*C z72MRV9ZLdvHL<=w5((1PlY+P&o=Z)DfB(&;0b#(kds`bzFg`twkFnP}MFYZW-bOi) zl^=^2K2bw7Ao9&Ff_Zp~^2O+vj(&&mZJaXscF49ybx#*}A+Ub;p9T$72l0X~_Z0|{ zmkh}BPc6wxuxhe&vN2Etl7&{)X^U>l(jyY10^$m%kntEgr*gp{E#jHt(+Thd8oU!N zh0RCT*-w<7@+07haqs*tG#={gn0SXU+Aa|go9>nw_Vc(Cl;EZFoT2+;4Se6^^CxEu zI)tnT{o$HMMFKJaeO>;@-GVO}0QT|pjviY5Q6yBNuC8J6RGUO(32 z0Ko_9uT29=>41-+N!t&z#5CTWKkokR;RWL1Bd5EoytTPguoRJB#O-69p2Ufl8^49{ zcpD}BF|SQ}QjHO!!C!KsL6sG-MJX0>eUOzz`QJ&_ohH_S5}x~ z{eiGSO~;T90C>2p>1ym@{!B*&YpJG74A6etsw?K^=Hl)w%6x;IkS|5Q^Gc>$kCJ9T zg*3-~{(gM3ZJcn*Ler2HCLYy(KBH>7j}fM2W-bw*FuQ}XYl4f>icjCGZ-PuC#p1x` zoQSd8KH(O>>L8uRW47bRPB3yx$E>IW={IcB!a{#lU(GF&_r`qwEi`yAzNpn&z4m8$ z$)say)S+@U$h5M(#~IxTR<@f5g(deekdZa@ySM!oB1Z1xY28u=K#=K4#BPn4OPc2spJJR;XBSr=J$^?52YDku zkc|~2J!${c{G?1fAX!vC3 Date: Wed, 30 Mar 2022 11:47:46 -0400 Subject: [PATCH 2/4] fix SteamGrid search call for game names that include `/` character --- UWPHook/SteamGridDb/SteamGridDbApi.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/UWPHook/SteamGridDb/SteamGridDbApi.cs b/UWPHook/SteamGridDb/SteamGridDbApi.cs index b340a9f..e57a3eb 100644 --- a/UWPHook/SteamGridDb/SteamGridDbApi.cs +++ b/UWPHook/SteamGridDb/SteamGridDbApi.cs @@ -33,7 +33,8 @@ public SteamGridDbApi(string apiKey) /// Array of games corresponding to the provided name public async Task SearchGame(string gameName) { - string path = $"search/autocomplete/{gameName}"; + // remove any forward slashes in the gameName to allow Games like FINAL FANTASY X/X-2 HD Remaster to be found + string path = $"search/autocomplete/{gameName.Replace('/', ' ')}"; GameResponse[] games = null; HttpResponseMessage response = await httpClient.GetAsync(path); From 57f5af399b443b3ca3fb4b745c3a7b33944877fc Mon Sep 17 00:00:00 2001 From: Sectorbob Date: Wed, 30 Mar 2022 12:01:32 -0400 Subject: [PATCH 3/4] initial NVIDIA Shield streaming support implementation --- UWPHook/GamesWindow.xaml.cs | 334 ++++++++++++++++++++++++++---------- 1 file changed, 243 insertions(+), 91 deletions(-) diff --git a/UWPHook/GamesWindow.xaml.cs b/UWPHook/GamesWindow.xaml.cs index dd054a6..3eba4ba 100644 --- a/UWPHook/GamesWindow.xaml.cs +++ b/UWPHook/GamesWindow.xaml.cs @@ -40,7 +40,7 @@ public GamesWindow() if (args?.Length > 1) { // When length is 1, the only argument is the path where the app is installed - _ = LauncherAsync(args); // Launches the requested game + _ = LauncherAsync(args); // Launches the requested game } else @@ -210,7 +210,7 @@ await Task.Run(() => stream.Close(); client.Dispose(); } - }); + }); } /// @@ -218,16 +218,16 @@ await Task.Run(() => /// /// The user path to copy images to private void CopyTempGridImagesToSteamUser(string user) - { + { string tmpGridDirectory = Path.GetTempPath() + "UWPHook\\tmp_grid\\"; string userGridDirectory = user + "\\config\\grid\\"; - + // No images were downloaded, maybe the key is invalid or no app had an image if (!Directory.Exists(tmpGridDirectory)) { return; } - + string[] images = Directory.GetFiles(tmpGridDirectory); if (!Directory.Exists(userGridDirectory)) @@ -273,7 +273,7 @@ private async Task DownloadTempGridImages(string appName, string appTarget) { throw; } - + if (games != null) { var game = games[0]; @@ -348,7 +348,7 @@ private async Task ExportGames(bool restartSteam) { var users = SteamManager.GetUsers(steam_folder); var selected_apps = Apps.Entries.Where(app => app.Selected); - var exePath = @"""" + System.Reflection.Assembly.GetExecutingAssembly().Location + @""""; + var exePath = GenerateExePath(); var exeDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); List gridImagesDownloadTasks = new List(); @@ -370,89 +370,8 @@ private async Task ExportGames(bool restartSteam) // Export the selected apps and the downloaded images to each user // in the steam folder by modifying it's VDF file - foreach (var user in users) - { - try - { - VDFEntry[] shortcuts = new VDFEntry[0]; - try - { - shortcuts = SteamManager.ReadShortcuts(user); - } - catch (Exception ex) - { - //If it's a short VDF, let's just overwrite it - if (ex.GetType() != typeof(VDFTooShortException)) - { - throw new Exception("Error: Program failed to load existing Steam shortcuts." + Environment.NewLine + ex.Message); - } - } - - if (shortcuts != null) - { - foreach (var app in selected_apps) - { - VDFEntry newApp = new VDFEntry() - { - AppName = app.Name, - Exe = exePath, - StartDir = exeDir, - LaunchOptions = app.Aumid, - AllowDesktopConfig = 1, - AllowOverlay = 1, - Icon = app.Icon, - Index = shortcuts.Length, - IsHidden = 0, - OpenVR = 0, - ShortcutPath = "", - Tags = tags, - Devkit = 0, - DevkitGameID = "", - LastPlayTime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - }; - Boolean isFound = false; - for (int i = 0; i < shortcuts.Length; i++) - { - Debug.WriteLine(shortcuts[i].ToString()); - - - if (shortcuts[i].AppName == app.Name) - { - isFound = true; - Debug.WriteLine(app.Name + " already added to Steam. Updating existing shortcut."); - shortcuts[i] = newApp; - } - } - - if (!isFound) - { - //Resize this array so it fits the new entries - Array.Resize(ref shortcuts, shortcuts.Length + 1); - shortcuts[shortcuts.Length - 1] = newApp; - } - - } - - try - { - if (!Directory.Exists(user + @"\\config\\")) - { - Directory.CreateDirectory(user + @"\\config\\"); - } - //Write the file with all the shortcuts - File.WriteAllBytes(user + @"\\config\\shortcuts.vdf", VDFSerializer.Serialize(shortcuts)); - } - catch (Exception ex) - { - throw new Exception("Error: Program failed while trying to write your Steam shortcuts" + Environment.NewLine + ex.Message); - } - } - } - catch (Exception ex) - { - MessageBox.Show("Error: Program failed exporting your games:" + Environment.NewLine + ex.Message + ex.StackTrace); - } - } + ExportAppsToSteamShortcuts(tags, users, selected_apps, exePath, exeDir); + ExportAppsToShieldShortcuts(selected_apps, exePath, exeDir); if (gridImagesDownloadTasks.Count > 0) { @@ -463,6 +382,7 @@ await Task.Run(() => foreach (var user in users) { CopyTempGridImagesToSteamUser(user); + CopyTempGridImagesToShieldStreamingAssets(selected_apps); } RemoveTempGridImages(); @@ -470,7 +390,7 @@ await Task.Run(() => } } - if(restartSteam) + if (restartSteam) { Func getSteam = () => Process.GetProcessesByName("steam").SingleOrDefault(); @@ -517,6 +437,103 @@ await Task.Run(() => return true; } + /// + /// Extracted method to simplify the complexity of the ExportGames method. + /// + /// This method updates and existing steam vdf file to add or update shortcuts for the selected windows apps. + /// + /// + /// + /// + /// + /// + private static void ExportAppsToSteamShortcuts(string[] tags, string[] users, IEnumerable selected_apps, string exePath, string exeDir) + { + foreach (var user in users) + { + try + { + VDFEntry[] shortcuts = new VDFEntry[0]; + try + { + shortcuts = SteamManager.ReadShortcuts(user); + } + catch (Exception ex) + { + //If it's a short VDF, let's just overwrite it + if (ex.GetType() != typeof(VDFTooShortException)) + { + throw new Exception("Error: Program failed to load existing Steam shortcuts." + Environment.NewLine + ex.Message); + } + } + + if (shortcuts != null) + { + foreach (var app in selected_apps) + { + VDFEntry newApp = new VDFEntry() + { + AppName = app.Name, + Exe = exePath, + StartDir = exeDir, + LaunchOptions = app.Aumid, + AllowDesktopConfig = 1, + AllowOverlay = 1, + Icon = app.Icon, + Index = shortcuts.Length, + IsHidden = 0, + OpenVR = 0, + ShortcutPath = "", + Tags = tags, + Devkit = 0, + DevkitGameID = "", + LastPlayTime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + }; + Boolean isFound = false; + for (int i = 0; i < shortcuts.Length; i++) + { + Debug.WriteLine(shortcuts[i].ToString()); + + + if (shortcuts[i].AppName == app.Name) + { + isFound = true; + Debug.WriteLine(app.Name + " already added to Steam. Updating existing shortcut."); + shortcuts[i] = newApp; + } + } + + if (!isFound) + { + //Resize this array so it fits the new entries + Array.Resize(ref shortcuts, shortcuts.Length + 1); + shortcuts[shortcuts.Length - 1] = newApp; + } + + } + + try + { + if (!Directory.Exists(user + @"\\config\\")) + { + Directory.CreateDirectory(user + @"\\config\\"); + } + //Write the file with all the shortcuts + File.WriteAllBytes(user + @"\\config\\shortcuts.vdf", VDFSerializer.Serialize(shortcuts)); + } + catch (Exception ex) + { + throw new Exception("Error: Program failed while trying to write your Steam shortcuts" + Environment.NewLine + ex.Message); + } + } + } + catch (Exception ex) + { + MessageBox.Show("Error: Program failed exporting your games:" + Environment.NewLine + ex.Message + ex.StackTrace); + } + } + } + public static void ClearAllShortcuts() { Debug.WriteLine("DBG: Clearing all elements in shortcuts.vdf"); @@ -695,5 +712,140 @@ private void Window_Loaded(object sender, RoutedEventArgs e) } } } + + /// + /// Generates the exe path used by Steam and NVIDIA Shield to call UWPHook + /// + /// + private static string GenerateExePath() + { + return @"""" + System.Reflection.Assembly.GetExecutingAssembly().Location + @""""; + } + + /// + /// Iterates through the selected apps and creates windwos shortcut files for each inside the NVIDIA Shield Apps directory. + /// Will place an NVIDIA required placeholder box-art.png file inside the StreamingAssets/{app-name} dir. + /// + /// + /// + /// + private static void ExportAppsToShieldShortcuts(IEnumerable selected_apps, string exePath, string exeDir) + { + string shieldAppsDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "NVIDIA Corporation", "Shield Apps"); + string streamingAssetsDir = Path.Combine(shieldAppsDir, "StreamingAssets"); + if (!Directory.Exists(streamingAssetsDir)) + { + DirectoryInfo di = Directory.CreateDirectory(streamingAssetsDir); + di.Attributes = FileAttributes.Directory | FileAttributes.Hidden; + } + + foreach (AppEntry app in selected_apps) + { + string scrubbedAppName = app.Name; + foreach (char c in Path.GetInvalidFileNameChars()) + { + scrubbedAppName = scrubbedAppName.Replace(c, '_'); + } + + string shortcutFile = Path.Combine(shieldAppsDir, scrubbedAppName + ".lnk"); + Debug.WriteLine("Creating NVIDIA Shield shortcut: " + shortcutFile); + + IWshRuntimeLibrary.IWshShell3 wsh = new IWshRuntimeLibrary.IWshShell_Class(); + IWshRuntimeLibrary.IWshShortcut shortcut = wsh.CreateShortcut(shortcutFile); + shortcut.Arguments = app.Aumid; + shortcut.TargetPath = exePath; + // not sure about what this is for + shortcut.WindowStyle = 1; + shortcut.Description = "test link for " + app.Name; + shortcut.WorkingDirectory = exeDir; + //shortcut.IconLocation = "specify icon location"; + shortcut.Save(); + + // Copy over placeholder box-art.png (required by NVIDIA) + string boxArtDir = Path.Combine(streamingAssetsDir, scrubbedAppName); + string boxArtFile = Path.Combine(streamingAssetsDir, scrubbedAppName, "box-art.png"); + if (!Directory.Exists(boxArtDir)) + { + Debug.WriteLine("creating directory for game streaming assets: " + boxArtDir); + try + { + Directory.CreateDirectory(boxArtDir); + } + catch (Exception e) + { + Console.Error.WriteLine("unable to create game streaming assets dir: {0}", e); + } + + } + try + { + Debug.WriteLine("copying placeholder box-art to " + boxArtFile); + Properties.Resources.box_art.Save(boxArtFile); + } + catch (Exception e) + { + Console.Error.WriteLine("unable to copy placeholder box-art: {0}", e); + } + + } + + } + + /// + /// Copies all temporary images to the {drive}:/Users/{user}/Appdata/Local/NVIDIA Corporation/Shield Apps/StreamingAssets/{scrubbed game name} dirs + /// + /// Selected app entrys to copy images for + private void CopyTempGridImagesToShieldStreamingAssets(IEnumerable selectedApps) + { + string tmpGridDirectory = Path.Combine(Path.GetTempPath(), "UWPHook", "tmp_grid"); + string streamingAssetsDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "NVIDIA Corporation", "Shield Apps", "StreamingAssets"); + + // No images were downloaded, maybe the key is invalid or no app had an image + if (!Directory.Exists(tmpGridDirectory)) + { + return; + } + if (!Directory.Exists(streamingAssetsDirectory)) + { + DirectoryInfo di = Directory.CreateDirectory(streamingAssetsDirectory); + di.Attributes = FileAttributes.Directory | FileAttributes.Hidden; + } + + foreach (AppEntry app in selectedApps) + { + string scrubbedAppName = app.Name; + foreach (char c in Path.GetInvalidFileNameChars()) + { + scrubbedAppName = scrubbedAppName.Replace(c, '_'); + } + + ulong gameId = GenerateSteamGridAppId(app.Name, GenerateExePath()); + string srcFile = Path.Combine(tmpGridDirectory, string.Format("{0}p.png", gameId)); + string destDir = Path.Combine(streamingAssetsDirectory, scrubbedAppName); + string destFile = Path.Combine(destDir, "box-art.png"); + if (!Directory.Exists(destDir)) + { + try + { + Debug.WriteLine("Creating directory: ", destDir); + _ = Directory.CreateDirectory(destDir); + } + catch (Exception e) + { + Console.Error.WriteLine("unable to create directory: ", e); + } + } + try + { + Debug.WriteLine("Copying box-art image from {0} to {1}", new object[] { srcFile, destFile }); + File.Copy(srcFile, destFile, true); + } + catch (Exception e) + { + Console.Error.WriteLine("unable to copy box-art: ", e); + } + + } + } } } From 7ab6eb33f82e5bd8a670368a522a50e93859a92d Mon Sep 17 00:00:00 2001 From: Sectorbob Date: Wed, 30 Mar 2022 12:11:50 -0400 Subject: [PATCH 4/4] fix broken "test" button code + resolve MaterialDesignThemes version --- UWPHook/SettingsWindow.xaml | 1 - UWPHook/SettingsWindow.xaml.cs | 8 -------- UWPHook/UWPHook.csproj | 10 +++++----- UWPHook/packages.config | 2 +- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/UWPHook/SettingsWindow.xaml b/UWPHook/SettingsWindow.xaml index d1fe0dc..b624cd0 100644 --- a/UWPHook/SettingsWindow.xaml +++ b/UWPHook/SettingsWindow.xaml @@ -92,7 +92,6 @@