2017-02-26 19:23:31 +00:00
using System ;
using System.Collections.Generic ;
2020-04-19 05:37:29 +00:00
using System.ComponentModel.DataAnnotations ;
using McMaster.Extensions.CommandLineUtils ;
2017-02-26 19:23:31 +00:00
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
2018-10-20 00:20:53 +00:00
using System.Net ;
2020-04-19 05:37:29 +00:00
using System.Reflection ;
2020-04-13 10:10:35 +00:00
using System.Runtime.CompilerServices ;
2017-02-26 19:23:31 +00:00
using System.Threading ;
using System.Threading.Tasks ;
using System.Windows.Forms ;
2020-04-23 08:16:16 +00:00
using HeliosPlus.InterProcess ;
using HeliosPlus.Resources ;
2020-04-27 10:55:44 +00:00
using HeliosPlus.GameLibraries ;
2020-04-23 08:16:16 +00:00
using HeliosPlus.Shared ;
using HeliosPlus.UIForms ;
2020-04-14 10:18:52 +00:00
using System.Net.NetworkInformation ;
2020-05-17 09:19:55 +00:00
using System.Text.RegularExpressions ;
2020-06-14 04:20:52 +00:00
using System.Drawing ;
2020-10-04 10:01:03 +00:00
using System.Diagnostics.Contracts ;
2017-02-26 19:23:31 +00:00
2020-04-23 08:16:16 +00:00
namespace HeliosPlus {
2020-10-11 08:12:52 +00:00
2020-04-22 11:46:31 +00:00
public enum SupportedGameLibrary
{
Unknown ,
Steam ,
Uplay
}
2017-02-26 19:23:31 +00:00
internal static class Program
{
2020-04-13 03:47:38 +00:00
2020-05-09 13:02:07 +00:00
internal static string AppDataPath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) , "HeliosPlus" ) ;
2020-06-14 04:20:52 +00:00
public static string AppIconPath = Path . Combine ( Program . AppDataPath , $"Icons" ) ;
2020-08-18 22:16:04 +00:00
public static string AppProfilePath = Path . Combine ( Program . AppDataPath , $"Profiles" ) ;
public static string AppShortcutPath = Path . Combine ( Program . AppDataPath , $"Shortcuts" ) ;
2020-06-14 04:20:52 +00:00
public static string AppHeliosPlusIconFilename = Path . Combine ( AppIconPath , @"HeliosPlus.ico" ) ;
public static string AppOriginIconFilename = Path . Combine ( AppIconPath , @"Origin.ico" ) ;
public static string AppSteamIconFilename = Path . Combine ( AppIconPath , @"Steam.ico" ) ;
public static string AppUplayIconFilename = Path . Combine ( AppIconPath , @"Steam.ico" ) ;
public static string AppEpicIconFilename = Path . Combine ( AppIconPath , @"Epic.ico" ) ;
2020-05-19 09:41:26 +00:00
//internal static string ShortcutIconCachePath;
2020-04-22 11:46:31 +00:00
2020-05-09 13:02:07 +00:00
2020-07-21 11:40:33 +00:00
2017-02-26 19:23:31 +00:00
/// <summary>
/// The main entry point for the application.
/// </summary>
2020-04-13 10:10:35 +00:00
[STAThread]
private static int Main ( string [ ] args )
2017-02-26 19:23:31 +00:00
{
2020-04-13 10:10:35 +00:00
2020-04-28 10:38:43 +00:00
// Write the Application Name
Console . WriteLine ( $"{Application.ProductName} v{Application.ProductVersion}" ) ;
for ( int i = 0 ; i < = Application . ProductName . Length + Application . ProductVersion . Length ; i + + )
{
Console . Write ( "=" ) ;
}
Console . WriteLine ( "=" ) ;
2020-07-21 11:40:33 +00:00
Console . WriteLine ( $"Copyright <20> Terry MacDonald 2020-{DateTime.Today.Year}" ) ;
2020-04-28 10:38:43 +00:00
Console . WriteLine ( @"Based on Helios Display Management - Copyright <20> Soroush Falahati 2017-2020" ) ;
2020-04-19 05:37:29 +00:00
var app = new CommandLineApplication ( ) ;
2020-04-13 10:10:35 +00:00
2020-04-19 05:37:29 +00:00
//app.Name = "HeliosDM+";
//app.Name = Assembly.GetEntryAssembly().GetName().Name;
app . Description = "This application helps configure your NVIDIA Videocard for multiple displays." ;
app . ExtendedHelpText = "This application helps configure your NVIDIA Videocard for multiple displays. It has some nifty features such as the "
+ Environment . NewLine + " ability to temporarily change your screen settings while you are playing a game, and then change them back once finished." ;
2017-02-26 19:23:31 +00:00
2020-04-19 05:37:29 +00:00
app . GetFullNameAndVersion ( ) ;
app . MakeSuggestionsInErrorMessage = true ;
2020-04-28 10:38:43 +00:00
app . HelpOption ( "-?|-h|--help" , inherited : true ) ;
2020-04-16 10:49:11 +00:00
2020-04-19 05:37:29 +00:00
app . VersionOption ( "-v|--version" , ( ) = > {
return string . Format ( "Version {0}" , Assembly . GetExecutingAssembly ( ) . GetName ( ) . Version ) ;
} ) ;
2020-04-13 10:10:35 +00:00
2020-07-21 11:40:33 +00:00
// This is the RunShortcut command
2020-10-11 08:12:52 +00:00
app . Command ( HeliosStartupAction . RunShortcut . ToString ( ) , ( runShortcutCmd ) = >
2020-04-14 10:18:52 +00:00
{
2020-10-11 08:12:52 +00:00
var argumentShortcut = runShortcutCmd . Argument ( "\"SHORTCUT_UUID\"" , "(required) The UUID of the shortcut to run from those stored in the shortcut library." ) . IsRequired ( ) ;
2020-05-17 09:19:55 +00:00
argumentShortcut . Validators . Add ( new ShortcutMustExistValidator ( ) ) ;
2020-04-19 05:37:29 +00:00
//description and help text of the command.
2020-10-11 08:12:52 +00:00
runShortcutCmd . Description = "Use this command to run favourite game or application with a display profile of your choosing." ;
2018-10-20 00:27:25 +00:00
2020-10-11 08:12:52 +00:00
runShortcutCmd . OnExecute ( ( ) = >
2020-04-19 05:37:29 +00:00
{
2020-07-21 11:40:33 +00:00
//
RunShortcut ( argumentShortcut . Value ) ;
2020-05-17 09:19:55 +00:00
return 0 ;
2020-04-19 05:37:29 +00:00
} ) ;
} ) ;
2020-04-22 11:46:31 +00:00
app . OnExecute ( ( ) = >
{
Console . WriteLine ( "Starting Normally..." ) ;
StartUpNormally ( ) ;
return 0 ;
} ) ;
2020-10-18 08:17:21 +00:00
// Try to load all the games in parallel to this process
Task . Run ( ( ) = > LoadGamesInBackground ( ) ) ;
2020-04-19 05:37:29 +00:00
try
{
// This begins the actual execution of the application
app . Execute ( args ) ;
}
catch ( CommandParsingException ex )
{
2020-10-11 08:12:52 +00:00
//Console.WriteLine($"Program/Main commandParsingException: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
2020-04-19 05:37:29 +00:00
// You'll always want to catch this exception, otherwise it will generate a messy and confusing error for the end user.
// the message will usually be something like:
// "Unrecognized command or argument '<invalid-command>'"
2020-10-11 08:12:52 +00:00
//Console.WriteLine(ex.Message);
Console . WriteLine ( "Didn't recognise the supplied commandline options: {0}" , ex . Message ) ;
2020-04-19 05:37:29 +00:00
}
catch ( Exception ex )
{
2020-07-24 04:51:48 +00:00
Console . WriteLine ( $"Program/Main exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}" ) ;
2020-04-19 05:37:29 +00:00
Console . WriteLine ( "Unable to execute application: {0}" , ex . Message ) ;
}
return 0 ;
//return app.Execute(args);
}
private static void StartUpNormally ( )
{
Application . EnableVisualStyles ( ) ;
Application . SetCompatibleTextRenderingDefault ( false ) ;
ServicePointManager . SecurityProtocol = SecurityProtocolType . Tls12 ;
try
{
// Start the IPC Service to
if ( ! IPCService . StartService ( ) )
{
throw new Exception ( Language . Can_not_open_a_named_pipe_for_Inter_process_communication ) ;
2017-02-26 19:23:31 +00:00
}
2020-04-13 03:47:38 +00:00
2020-08-18 22:16:04 +00:00
// Create the Shortcut Icon Cache if it doesn't exist so that it's avilable for all the program
if ( ! Directory . Exists ( AppIconPath ) )
{
try
{
Directory . CreateDirectory ( AppIconPath ) ;
}
catch ( Exception ex )
{
Console . WriteLine ( $"Program/StartUpNormally exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}" ) ;
// TODO
}
}
2020-04-13 03:47:38 +00:00
2020-06-14 04:20:52 +00:00
// Create the Shortcut Icon Cache if it doesn't exist so that it's avilable for all the program
if ( ! Directory . Exists ( AppIconPath ) )
{
try
{
Directory . CreateDirectory ( AppIconPath ) ;
}
2020-07-15 08:11:38 +00:00
catch ( Exception ex )
2020-06-14 04:20:52 +00:00
{
2020-07-24 04:51:48 +00:00
Console . WriteLine ( $"Program/StartUpNormally exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}" ) ;
2020-06-14 04:20:52 +00:00
// TODO
}
}
try
{
// Save a copy of the HeliosPlus Icon, and all the game library ones in preparation for future use
if ( ! File . Exists ( AppHeliosPlusIconFilename ) )
{
Icon heliosIcon = ( Icon ) Properties . Resources . HeliosPlus ;
using ( FileStream fs = new FileStream ( AppHeliosPlusIconFilename , FileMode . Create ) )
heliosIcon . Save ( fs ) ;
}
// Save a copy of the Steam Icon, and all the game library ones in preparation for future use
if ( ! File . Exists ( AppSteamIconFilename ) )
{
Icon heliosIcon = ( Icon ) Properties . Resources . Steam ;
using ( FileStream fs = new FileStream ( AppSteamIconFilename , FileMode . Create ) )
heliosIcon . Save ( fs ) ;
}
// Save a copy of the Uplay Icon, and all the game library ones in preparation for future use
if ( ! File . Exists ( AppUplayIconFilename ) )
{
Icon heliosIcon = ( Icon ) Properties . Resources . Uplay ;
using ( FileStream fs = new FileStream ( AppUplayIconFilename , FileMode . Create ) )
heliosIcon . Save ( fs ) ;
}
// Save a copy of the Epic Icon, and all the game library ones in preparation for future use
if ( ! File . Exists ( AppEpicIconFilename ) )
{
Icon heliosIcon = ( Icon ) Properties . Resources . Epic ;
using ( FileStream fs = new FileStream ( AppEpicIconFilename , FileMode . Create ) )
heliosIcon . Save ( fs ) ;
}
// Save a copy of the Origin Icon, and all the game library ones in preparation for future use
if ( ! File . Exists ( AppOriginIconFilename ) )
{
Icon heliosIcon = ( Icon ) Properties . Resources . Origin ;
using ( FileStream fs = new FileStream ( AppOriginIconFilename , FileMode . Create ) )
heliosIcon . Save ( fs ) ;
}
}
2020-07-15 08:11:38 +00:00
catch ( Exception ex )
2020-06-14 04:20:52 +00:00
{
2020-07-24 04:51:48 +00:00
Console . WriteLine ( $"Program/StartUpNormally exception 2: {ex.Message}: {ex.StackTrace} - {ex.InnerException}" ) ;
2020-06-14 04:20:52 +00:00
// TODO
}
2020-04-19 05:37:29 +00:00
IPCService . GetInstance ( ) . Status = InstanceStatus . User ;
2020-05-03 08:39:35 +00:00
Application . Run ( new UIForms . MainForm ( ) ) ;
2020-04-19 05:37:29 +00:00
2020-04-14 10:18:52 +00:00
}
2020-05-19 09:41:26 +00:00
catch ( Exception ex )
2020-04-14 10:18:52 +00:00
{
2020-07-24 04:51:48 +00:00
Console . WriteLine ( $"Program/StartUpNormally exception 3: {ex.Message}: {ex.StackTrace} - {ex.InnerException}" ) ;
2020-04-14 10:18:52 +00:00
MessageBox . Show (
2020-05-19 09:41:26 +00:00
ex . Message ,
2020-04-14 10:18:52 +00:00
Language . Fatal_Error ,
MessageBoxButtons . OK ,
MessageBoxIcon . Error ) ;
}
2020-04-16 10:49:11 +00:00
2020-04-14 10:18:52 +00:00
}
2020-04-13 10:10:35 +00:00
2018-10-20 00:27:25 +00:00
2020-07-21 11:40:33 +00:00
// ReSharper disable once CyclomaticComplexity
private static void RunShortcut ( string shortcutUUID )
{
ShortcutItem shortcutToRun = null ;
2018-10-20 00:27:25 +00:00
2020-07-21 11:40:33 +00:00
// Check there is only one version of this application so we won't
// mess with another monitoring session
2017-02-26 19:23:31 +00:00
if (
IPCClient . QueryAll ( )
. Any (
client = >
2018-10-20 00:27:25 +00:00
client . Status = = InstanceStatus . Busy | |
client . Status = = InstanceStatus . OnHold ) )
{
2017-02-26 19:23:31 +00:00
throw new Exception (
Language
. Another_instance_of_this_program_is_in_working_state_Please_close_other_instances_before_trying_to_switch_profile ) ;
2018-10-20 00:27:25 +00:00
}
2020-07-21 11:40:33 +00:00
// Match the ShortcutName to the actual shortcut listed in the shortcut library
// And error if we can't find it.
if ( ShortcutRepository . ContainsShortcut ( shortcutUUID ) )
2017-02-26 19:23:31 +00:00
{
2020-07-21 11:40:33 +00:00
// make sure we trim the "" if there are any
shortcutUUID = shortcutUUID . Trim ( '"' ) ;
shortcutToRun = ShortcutRepository . GetShortcut ( shortcutUUID ) ;
2020-04-19 05:37:29 +00:00
}
2020-07-21 11:40:33 +00:00
else
2020-04-19 05:37:29 +00:00
{
2020-07-21 11:40:33 +00:00
throw new Exception ( Language . Cannot_find_shortcut_in_library ) ;
2020-04-19 05:37:29 +00:00
}
2018-10-20 00:27:25 +00:00
2020-07-23 06:31:00 +00:00
if ( shortcutToRun is ShortcutItem )
2020-04-19 05:37:29 +00:00
{
2020-07-23 06:31:00 +00:00
ShortcutRepository . RunShortcut ( shortcutToRun ) ;
2020-04-19 05:37:29 +00:00
}
2018-10-20 00:27:25 +00:00
2020-07-21 11:40:33 +00:00
IPCService . GetInstance ( ) . Status = InstanceStatus . Busy ;
2020-04-19 05:37:29 +00:00
}
2020-05-17 09:19:55 +00:00
public static bool IsValidFilename ( string testName )
{
string strTheseAreInvalidFileNameChars = new string ( Path . GetInvalidFileNameChars ( ) ) ;
Regex regInvalidFileName = new Regex ( "[" + Regex . Escape ( strTheseAreInvalidFileNameChars ) + "]" ) ;
if ( regInvalidFileName . IsMatch ( testName ) ) { return false ; } ;
return true ;
}
2020-10-04 10:01:03 +00:00
// ApplyProfile lives here so that the UI works.
public static bool ApplyProfile ( ProfileItem profile )
2020-05-17 09:19:55 +00:00
{
2020-10-04 10:01:03 +00:00
// If we're already on the wanted profile then no need to change!
if ( ProfileRepository . IsActiveProfile ( profile ) )
return true ;
// We need to check if the profile is valid
if ( ! profile . IsPossible )
return false ;
try
{
2020-10-04 11:18:22 +00:00
// Now lets prepare changing the display topology task
Task applyTopologyTask = new Task ( ( ) = >
{
Console . WriteLine ( "Program/ApplyProfile : Applying Profile Topology " + profile . Name ) ;
2020-10-07 08:58:05 +00:00
if ( ! ProfileRepository . ApplyNVIDIAGridTopology ( profile ) )
2020-10-04 11:18:22 +00:00
{
// Somehow return that this profile topology didn't apply
2020-10-13 07:22:42 +00:00
throw new ApplyTopologyException ( "Program/ApplyProfile: ApplyNVIDIAGridTopology: Error setting up the NVIDIA Surround Grid Topology" ) ;
2020-10-04 11:18:22 +00:00
}
} ) ;
Task applyPathInfoTask = new Task ( ( ) = > {
Console . WriteLine ( "Program/ApplyProfile : Applying Profile Path " + profile . Name ) ;
2020-10-07 08:58:05 +00:00
if ( ! ProfileRepository . ApplyWindowsDisplayPathInfo ( profile ) )
2020-10-04 11:18:22 +00:00
{
// Somehow return that this profile path info didn't apply
2020-10-13 07:22:42 +00:00
throw new ApplyPathInfoException ( "Program/ApplyProfile: ApplyWindowsDisplayPathInfo: Error configuring the Windows Display Devices" ) ;
2020-10-04 11:18:22 +00:00
}
} ) ;
2020-10-04 10:01:03 +00:00
// Set up the UI forms to show
2020-10-06 10:49:10 +00:00
ApplyingProfileForm timeoutForm = new ApplyingProfileForm ( null , 3 , $"Changing to '{profile.Name}' Profile" , "Press ESC to cancel" , Color . Orange , true ) ;
2020-10-08 09:03:02 +00:00
ApplyingProfileForm topologyForm = new ApplyingProfileForm ( applyTopologyTask , 30 , $"Changing to '{profile.Name}' Profile" , "Applying NVIDIA Grid Topology" , Color . Aquamarine ) ;
ApplyingProfileForm pathInfoForm = new ApplyingProfileForm ( applyPathInfoTask , 15 , $"Changing to '{profile.Name}' Profile" , "Adjusting Windows Display Device positions" , Color . LawnGreen ) ;
2020-10-04 10:01:03 +00:00
2020-10-04 11:18:22 +00:00
if ( timeoutForm . ShowDialog ( ) = = DialogResult . Cancel )
2020-10-04 10:01:03 +00:00
{
2020-10-04 11:18:22 +00:00
return false ;
}
2020-10-07 08:58:05 +00:00
// We only want to do the topology change if the profile we're on now
// or the profile we're going to are NVIDIA surround profiles
int toProfileSurroundTopologyCount =
profile . Paths . SelectMany ( paths = > paths . TargetDisplays )
. Select ( target = > target . SurroundTopology )
. Where ( topology = > topology ! = null )
. Select ( topology = > topology . ToGridTopology ( ) )
. Count ( ) ;
int fromProfileSurroundTopologyCount =
ProfileRepository . CurrentProfile . Paths . SelectMany ( paths = > paths . TargetDisplays )
. Select ( target = > target . SurroundTopology )
. Where ( topology = > topology ! = null )
. Select ( topology = > topology . ToGridTopology ( ) )
. Count ( ) ;
if ( toProfileSurroundTopologyCount > 0 | | fromProfileSurroundTopologyCount > 0 )
2020-10-06 07:07:21 +00:00
{
2020-10-07 08:58:05 +00:00
topologyForm . ShowDialog ( ) ;
try
2020-10-06 07:07:21 +00:00
{
2020-10-07 08:58:05 +00:00
applyTopologyTask . Wait ( ) ;
}
catch ( AggregateException ae )
{
foreach ( var e in ae . InnerExceptions )
2020-10-06 07:07:21 +00:00
{
2020-10-07 08:58:05 +00:00
// Handle the custom exception.
if ( e is ApplyTopologyException )
{
Console . WriteLine ( e . Message ) ;
}
// Rethrow any other exception.
else
{
throw ;
}
2020-10-06 07:07:21 +00:00
}
}
2020-10-04 10:01:03 +00:00
2020-10-07 08:58:05 +00:00
if ( applyTopologyTask . IsFaulted )
Console . WriteLine ( "Program/ApplyProfile : Applying Profile Topology stage failed to complete" ) ;
}
2020-10-04 11:18:22 +00:00
2020-10-07 08:58:05 +00:00
// We always want to do the WindowsDisplayAPI PathInfo part
2020-10-04 11:18:22 +00:00
pathInfoForm . ShowDialog ( ) ;
applyPathInfoTask . Wait ( ) ;
2020-10-06 07:07:21 +00:00
try
{
applyPathInfoTask . Wait ( ) ;
}
catch ( AggregateException ae )
{
foreach ( var e in ae . InnerExceptions )
{
// Handle the custom exception.
if ( e is ApplyPathInfoException )
{
Console . WriteLine ( e . Message ) ;
}
// Rethrow any other exception.
else
{
throw ;
}
}
}
2020-10-04 11:18:22 +00:00
if ( applyPathInfoTask . IsFaulted )
Console . WriteLine ( "Program/ApplyProfile : Applying Profile PathInfo stage failed to complete" ) ;
if ( applyTopologyTask . IsCompleted & & applyPathInfoTask . IsCompleted )
return true ;
else
return false ;
2020-10-04 10:01:03 +00:00
}
catch ( Exception ex )
2020-05-17 09:19:55 +00:00
{
2020-10-04 10:01:03 +00:00
Console . WriteLine ( $"ProfileRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}" ) ;
return false ;
2020-05-17 09:19:55 +00:00
}
2020-10-04 10:01:03 +00:00
}
2020-10-18 08:17:21 +00:00
public static bool LoadGamesInBackground ( )
{
Debug . WriteLine ( "Program/LoadGamesInBackground : Starting" ) ;
// Now lets prepare loading all the Steam games we have installed
Task loadSteamGamesTask = new Task ( ( ) = >
{
// Load Steam library games
Console . WriteLine ( "Program/LoadGamesInBackground : Loading Installed Steam Games " ) ;
if ( ! HeliosPlus . GameLibraries . SteamLibrary . LoadInstalledGames ( ) )
{
// Somehow return that this profile topology didn't apply
throw new LoadingInstalledGamesException ( "Program/LoadGamesInBackground: Cannot load installed Steam Games!" ) ;
}
} ) ;
// Now lets prepare loading all the Uplay games we have installed
Task loadUplayGamesTask = new Task ( ( ) = >
{
// Load Uplay library games
Console . WriteLine ( "Program/LoadGamesInBackground : Loading Installed Uplay Games " ) ;
/ * if ( ! HeliosPlus . GameLibraries . UplayLibrary . LoadInstalledGames ( ) )
{
// Somehow return that this profile topology didn't apply
throw new LoadingInstalledGamesException ( "Program/LoadGamesInBackground: Cannot load installed Uplay Games!" ) ;
}
* /
} ) ;
// Store all the tasks in an array so we can wait on them later
Task [ ] loadGamesTasks = new Task [ 2 ] ;
loadGamesTasks [ 0 ] = loadSteamGamesTask ;
loadGamesTasks [ 1 ] = loadUplayGamesTask ;
Console . WriteLine ( "Program/LoadGamesInBackground : Running tasks" ) ;
// Go through and start all the tasks
foreach ( Task loadGameTask in loadGamesTasks )
loadGameTask . Start ( ) ;
try
{
Console . WriteLine ( "Program/LoadGamesInBackground : Waiting for tasks to finish" ) ;
Task . WaitAll ( loadGamesTasks ) ;
Console . WriteLine ( "Program/LoadGamesInBackground : All tasks completed!" ) ;
}
catch ( AggregateException ae )
{
Console . WriteLine ( "Program/LoadGamesInBackground : Task exception!" ) ;
foreach ( var e in ae . InnerExceptions )
{
// Handle the custom exception.
if ( e is LoadingInstalledGamesException )
{
Console . WriteLine ( e . Message ) ;
}
// Rethrow any other exception.
else
{
throw ;
}
}
}
bool failedTask = false ;
foreach ( var loadGameTask in loadGamesTasks )
{
Console . WriteLine ( $"Program/LoadGamesInBackground: LoadGameTask #{loadGameTask.Id}: {loadGameTask.Status}" ) ;
if ( loadGameTask . Exception ! = null )
{
failedTask = true ;
foreach ( var ex in loadGameTask . Exception . InnerExceptions )
Console . WriteLine ( " {0}: {1}" , ex . GetType ( ) . Name ,
ex . Message ) ;
}
}
if ( failedTask )
return false ;
return true ;
}
2017-02-26 19:23:31 +00:00
}
2020-10-06 07:07:21 +00:00
2020-10-18 08:17:21 +00:00
2020-10-06 07:07:21 +00:00
public class ApplyTopologyException : Exception
{
public ApplyTopologyException ( String message ) : base ( message )
{ }
}
public class ApplyPathInfoException : Exception
{
public ApplyPathInfoException ( String message ) : base ( message )
{ }
}
2020-10-18 08:17:21 +00:00
public class LoadingInstalledGamesException : Exception
{
public LoadingInstalledGamesException ( String message ) : base ( message )
{ }
}
2017-02-26 19:23:31 +00:00
}