static void Main(string[] args) { // Parse args. All CLI args override the configuration loaded from the file Options o = ParseArgs(args); // Read configuration file, or get a new default configuration if no file exists AppConfiguration config = ParseConfig(o.ConfigFile, o); // Get the emulator configuration EmulatorConfiguration emulators = ParseEmulators(config, o); if (o.Frontend) { HideConsole(); InitializeFrontend(); } else if (o.Gui) { HideConsole(); InitializeGui(); } else { ShowConsole(); ConsoleProcessor.Main(emulators, o); // Pausing after completion is done for when you forced the console on // because the app is set to compile as a windowed app instead of a console app // but when it is, i get no console display even when i force the console to open // using ShowConsole(), and when outputting as a console app I have to press a key // twice. Removing it for now. //Console.WriteLine("Press any key to exit..."); //Console.ReadKey(); } }
/// <summary> /// This method walks through every configured emulator and uses it to find roms and /// turn those roms into shortcuts for Steam. /// Each rom will be turned into a shortcut where the Target is the configured emulator EXE, /// the Launch Options will be the configured emulator parameters (with substitutions), including /// rom file and any emulator start parameters like fullscreen mode or config files, and whose /// name is parsed from the rom regex grouping, or from the rom filename if no grouping is specified. /// Icons will also be assigned, once I figure out how that works. To start with, icons will be /// loaded from files using the image regex. /// </summary> /// <param name="emulators"></param> /// <param name="shortcuts"></param> private static void GenerateShortcutsFromRoms(EmulatorConfiguration emulators, ICollection <Shortcut> shortcuts) { foreach (Emulator emu in emulators) { GenerateSteamShortcutsFromRoms(emu, shortcuts); } }
/// <summary> /// /// </summary> /// <param name="emulatorsFilename"></param> /// <returns></returns> private static EmulatorConfiguration ParseEmulators(AppConfiguration config, Options options) { if (File.Exists(options.EmulatorsFile)) { return(EmulatorConfiguration.Parse(options.EmulatorsFile, config)); } else { return(null); } }
public static void Main(EmulatorConfiguration emulators, Options o) { if (emulators == null) { // Error out? Console.Error.WriteLine("Couldn't read emulator configuration from file " + o.EmulatorsFile); return; } SteamTools steam = new SteamTools(); string shortcutsFile = o.ShortcutsFile; string user = null; if ("steam".Equals(o.Output)) { if (String.IsNullOrWhiteSpace(shortcutsFile)) { // Load Steam shortcuts. SteamTools will worry about where the file is and how to read it string steamBase = SteamTools.GetSteamLocation(); if (steamBase == null) { Console.Error.WriteLine("No Steam installation found."); return; } Console.WriteLine($"Found Steam installed at '{steamBase}'", steamBase); string[] users = SteamTools.GetUsers(steamBase); if (users.Length == 0) { Console.Error.WriteLine("No Steam users found."); return; } else if (users.Length > 1) { if (!string.IsNullOrWhiteSpace(o.SteamUser)) { user = o.SteamUser; } else { Console.Error.WriteLine("More than 1 Steam user found and no Steam User found in command line options, quitting..."); return; } } else { user = users[0]; } Console.WriteLine($"Using Steam user '{user}'", user); shortcutsFile = SteamTools.GetShortcutsFile(steamBase, user); } Console.WriteLine($"Loading Steam shortcuts file '{shortcutsFile}'", shortcutsFile); SteamShortcuts shortcuts = SteamTools.LoadShortcuts(shortcutsFile); // Analyze all emulator configs, read matching roms, create new shortcuts for each rom GenerateShortcutsFromRoms(emulators, shortcuts); // Don't do anything if Steam is going! if (steam.IsSteamRunning()) { Console.Error.WriteLine("Steam is running! Exit Steam before running this app."); return; } SteamTools.WriteSteamShortcuts(shortcuts, shortcutsFile); SteamTools.AddGridImages(shortcuts, user); return; } else { ICollection <Shortcut> shortcuts; if (!string.IsNullOrWhiteSpace(o.Input)) { FileStream file = System.IO.File.OpenRead(o.Input); // TODO: error checking on file open. shortcuts = ReadShortcuts(file); file.Close(); } else { shortcuts = new List <Shortcut>(); // Analyze all emulator configs, read matching roms, create new shortcuts for each rom GenerateShortcutsFromRoms(emulators, shortcuts); } // Write the steam shortcuts back to the file if ("console".Equals(o.Output)) { PrintShortcuts(shortcuts); } else if (string.IsNullOrWhiteSpace(o.Output) || "none".Equals(o.Output)) { // do nothing } else { // Anything else is a file or directory // A path with an extension is hopefully a valid path to a file for saving the results if (Path.HasExtension(o.Output)) { try { string path = Path.GetFullPath(o.Output); if (path == null) // I feel like this shouldn't happen, since it would throw an exception rather than returning null { Console.Error.WriteLine($"The output path '{o.Output}' is null, empty or not a valid windows path."); return; } string filename = Path.GetFileName(path); string dirname = Path.GetDirectoryName(path); Directory.CreateDirectory(dirname); StreamWriter outfile = System.IO.File.CreateText(path); WriteShortcutsFile(shortcuts, outfile); outfile.Close(); } catch (System.Security.SecurityException) { Console.Error.WriteLine($"Security exception when trying to open file {o.Output} for writing."); return; } catch (UnauthorizedAccessException) { Console.Error.WriteLine($"You don't have permission to open file {o.Output} for writing."); return; } catch (ArgumentException) { Console.Error.WriteLine($"The output path '{o.Output}' is null, empty or not a valid windows path."); return; } catch (PathTooLongException) { Console.Error.WriteLine($"The output path '{o.Output}' is too long."); return; } catch (NotSupportedException e) { Console.Error.WriteLine($"Threw NotSupportedException when trying to open file at '{o.Output}', with this message: {e.Message}"); return; } } else // otherwise we're looking at a directoy, where we spit out windows shortcuts { try { string path = Path.GetFullPath(o.Output); if (path == null) // I feel like this shouldn't happen, since it would throw an exception rather than returning null { Console.Error.WriteLine($"The output path '{o.Output}' is null, empty or not a valid windows path."); return; } DirectoryInfo dir = null; try { dir = Directory.CreateDirectory(path); } catch (ArgumentException) { Console.Error.WriteLine($"The output directory '{o.Output}' is null, empty or not a valid windows path."); return; } MakeWindowsShortcuts(shortcuts, dir); } catch (System.Security.SecurityException) { Console.Error.WriteLine($"Security exception when trying to write shortcuts to directory '{o.Output}'."); return; } catch (UnauthorizedAccessException) { Console.Error.WriteLine($"You don't have permission to access directory '{o.Output}'."); return; } catch (PathTooLongException) { Console.Error.WriteLine($"The output path '{o.Output}' is too long."); return; } catch (NotSupportedException e) { Console.Error.WriteLine($"Threw NotSupportedException when trying to write shortcuts to directory '{o.Output}', with this message: {e.Message}"); return; } } } } }
/// <summary> /// Document this, here and in the actual code /// </summary> /// <param name="emulatorsFilename"></param> /// <returns></returns> internal static EmulatorConfiguration Parse(string emulatorsFilename, AppConfiguration appConfig) { // TODO: Should the defined variables be cleared between calls to parse? var parser = new FileIniDataParser(); IniData data = parser.ReadFile(emulatorsFilename); EmulatorConfiguration config = new EmulatorConfiguration { emulators = new List <Emulator>() }; foreach (KeyData key in data.Global) { config.keys[key.KeyName] = config.ExpandVariables(key.Value, config.keys); } foreach (SectionData section in data.Sections) { Emulator emu = new Emulator { Category = section.SectionName }; // config.keys has global keys, and we have a local set of keys that has global keys and keys defined in this section Dictionary <string, string> emuKeys = new Dictionary <string, string>(config.keys); foreach (KeyData key in section.Keys) { // see the documentation for Validate for more info on these attributes and what they should be switch (key.KeyName) { case "Category": if (!string.IsNullOrWhiteSpace(key.Value)) { emu.Category = config.ExpandVariables(key.Value, emuKeys); } break; case "Platform": emu.Executable = config.ExpandVariables(key.Value, emuKeys); break; case "Executable": emu.Executable = config.ExpandVariables(key.Value, emuKeys); break; case "StartIn": emu.StartIn = config.ExpandVariables(key.Value, emuKeys); break; case "Parameters": emu.Parameters = config.ExpandVariables(key.Value, emuKeys); break; case "RomBasePath": emu.RomBasePath = config.ExpandVariables(key.Value, emuKeys); break; case "TitlePattern": emu.TitlePattern = config.ExpandVariables(key.Value, emuKeys); break; case "RomRegex": emu.RomRegex = config.ExpandVariables(key.Value, emuKeys); break; case "GridBasePath": emu.GridBasePath = config.ExpandVariables(key.Value, emuKeys); break; case "GridRegex": emu.GridRegex = config.ExpandVariables(key.Value, emuKeys); break; case "IconBasePath": emu.IconBasePath = config.ExpandVariables(key.Value, emuKeys); break; case "IconRegex": emu.IconRegex = config.ExpandVariables(key.Value, emuKeys); break; case "BoxartBasePath": emu.BoxartBasePath = config.ExpandVariables(key.Value, emuKeys); break; case "BoxartRegex": emu.BoxartRegex = config.ExpandVariables(key.Value, emuKeys); break; default: // Default is to add new key/value pairs for use elsewhere emuKeys[key.KeyName] = config.ExpandVariables(key.Value, emuKeys); break; } } emu.SetDefaults(appConfig); emu.ExpandVariables(); config.emulators.Add(emu); } Validate(config); return(config); }
/// <summary> /// Validates the emu config, ensuring that all required variables are provided, /// values are in the correct format and paths are valid. /// This doesn't validate that the executable exists or that the parameters /// are valid for the executable, but paths must be valid. /// The values stored in each emulator will be validated. /// Any failures to valid will be thrown altogether in an exception. /// Also, ANY attributes that contain an unexpanded %{...} variable will result in a warning. /// /// Category: (required) Replaces the emu category if it is not empty, otherwise the category is the section name. Should always be valid. /// Error if missing (shouldn't be missing). /// Error if contains characters not allowed in a file path. /// Platform: (optional) Should be a lowercase platform "code" like nes,snes,wii. For now I don't validate this value, just store it. /// No errors or warnings. /// Executable: (required) An executable program. /// Error if missing. /// Error if not a valid URI. /// Warning if file doesn't exist. /// StartIn: (required) Technically optional but if missing it will be defaulted to the parent of the executable. This is the working directory for the executable and should be a valid URI. /// Error if missing (shouldn't be missing). /// Error if not a valid URI. /// Warning if directory doesn't exist. /// Parameters: (optional) The parameters to pass to the executable. Can be anything. /// No errors or warnings. /// RomBasePath: (required) The directory that you keep all your roms in. RomRegex will match child paths to this directory. /// Error if missing. /// Error if not a valid path. /// Error if directory doesn't exist. /// RomRegex: (required) Regular expression to match relative rom paths. /// Error if missing. /// Error if not a valid regular expression. /// TitlePattern: (optional) Text to use as each rom's title. There should be at least one rom-specific substitution parameter in it or every rom will have the same title! /// If missing, defaults to %n. /// Warning if no rom-specific substitutions are included - %P, %p, %R, %r, %n, %D, %d /// IconBasePath: (optional) The base directory that contains .ico or .exe files to use for rom shortcut icons. /// Error if path not valid. /// Error if directory doesn't exist. /// IconRegex: (optional) Regex file used to locate an icon for each rom. Can be the same icon for each rom, or use rom-specific substitution parameters to get different icons for each rom. /// Error if path not valid. /// Error if directory doesn't exist. /// GridBasePath: (optional) The base directory that contains image files to use for the steam grid. /// Error if path not valid. /// Error if directory doesn't exist. /// GridRegex: (optional) Regex file used to locate a steam grid image for each rom. Can be the same image for each rom, or use rom-specific substitution parameters to get different icons for each rom. /// Error if not a valid regular expression. /// BoxartBasePath: (optional) The base directory that contains box art image files. /// Error if path not valid. /// Error if directory doesn't exist. /// BoxartRegex: (optional) Regex file used to locate the box art image for each rom. Can be the same image for each rom, or use rom-specific substitution parameters to get different icons for each rom. /// Error if not a valid regular expression. /// </summary> /// <exception cref="ParseError">Errors and warnings indicated any part of the config that failed validation.</exception> /// <param name="config">Emulator config to validate.</param> private static void Validate(EmulatorConfiguration config) { // TODO: Validate parsed configuration }