/// <summary> /// Initializes a new content file using pure content as storage. /// </summary> /// <param name="path">The path of the file WITHIN the addon.</param> /// <param name="content">The array of bytes containing the already set content.</param> public ContentFile(string path, byte[] content) { Storage = ContentStorageType.Filesystem; Path = path; ExternalFile = new FileStream(ContentFile.GenerateExternalPath(path), FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); Content = content; }
/// <summary> /// Updates the encapsulated addon object's file entry with the changes of a previously exported file. /// </summary> /// <param name="path">The path of the file within the addon to pull the changes for.</param> /// <exception cref="FileNotFoundException">Thrown if the specified path does not correspond to an export.</exception> /// <exception cref="IOException">Thrown if there was a problem opening the exported file.</exception> public void Pull(string path) { FileWatch search = null; try { search = WatchedFiles.Where(f => f.ContentPath == path).First(); } catch (InvalidOperationException) { throw new FileNotFoundException("There is no export for " + path); } if (search.Modified == false) { return; } ContentFile content = null; try { content = OpenAddon.Files.Where(f => f.Path == search.ContentPath).First(); } catch (InvalidOperationException) { // The file we exported and watched no longer exists in the addon. WatchedFiles.Remove(search); search.Watcher.Dispose(); } FileStream fs; try { fs = new FileStream(search.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read); } catch (IOException) { throw; } byte[] contBytes = new byte[fs.Length]; fs.Read(contBytes, 0, (int)fs.Length); content.Content = contBytes; fs.Close(); fs.Dispose(); search.Modified = false; // The exported file is no longer modified _modified = true; // But the addon itself is }
/// <summary> /// Extracts a file from the encapsulated addon and saves it on the local filesystem. /// </summary> /// <param name="path">The path of the file within the addon to extract.</param> /// <param name="to">The path on the local filesystem where the file should be saved. If omitted, /// the file will be extracted to the application's current working directory.</param> /// <exception cref="FileNotFoundException">Thrown if the specified file does not exist within the addon.</exception> /// <exception cref="UnauthorizedAccessException">Thrown if a file already exists at the specified extract location.</exception> /// <exception cref="IOException">Thrown if there was a problem creating the extracted file.</exception> public void ExtractFile(string path, string to = null) { if (to == null || to == String.Empty) { to = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Path.GetFileName(path); } else { string dir = Path.GetDirectoryName(to); if (dir == String.Empty) { to = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Path.GetFileName(to); } } ContentFile file = null; try { file = OpenAddon.Files.Where(f => f.Path == path).First(); } catch (InvalidOperationException) { throw new FileNotFoundException("The specified file " + path + " does not exist in the addon."); } if (File.Exists(to)) { throw new UnauthorizedAccessException("A file at " + to + " already exists."); } FileStream extract; try { extract = new FileStream(to, FileMode.Create, FileAccess.ReadWrite, FileShare.None); } catch (IOException) { throw; } extract.Write(file.Content, 0, (int)file.Size); extract.Flush(); extract.Dispose(); }
/// <summary> /// Sets up a new instance of Addon using the data provided by the specified addon reader. /// </summary> /// <param name="reader">The addon reader which handled reading the addon file.</param> /// <exception cref="ArgumentException">Happens if a file with the same path is already added.</exception> /// <exception cref="WhitelistException">The file is prohibited from storing by the global whitelist.</exception> /// <exception cref="IgnoredException">The file is prohibited from storing by the addon's ignore list.</exception> public Addon(Reader reader) : this() { //Author = reader.Author; Title = reader.Name; Description = reader.Description; Type = reader.Type; Tags = reader.Tags; /*AddonVersion = reader.Version;*/ FormatVersion = reader.FormatVersion; /*SteamID = reader.SteamID;*/ Timestamp = reader.Timestamp; foreach (Reader.IndexEntry file in reader.Index) { try { CheckRestrictions(file.Path); ContentFile contentFile = new ContentFile(reader, file); Files.Add(contentFile); } catch (WhitelistException) { throw; } catch (IgnoredException) { throw; } catch (ArgumentException) { throw; } } }
static int Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // Clean up mess the previous executions might have left behind. ContentFile.DisposeExternals(); // If there are parameters present, program starts as a CLI application. // // If there are no paremeters, the program is restarted in its own console // If there are no parameters and no console present, the GUI will start. // (This obviously means a small flickering of a console window (for the restart process) // but that's expendable for the fact that one compiled binary contains "both" faces.) if ((args != null && args.Length > 0) && (args[0] == "create" || args[0] == "extract" || args[0] == "realtime")) { // This is needed because we support "drag and drop" GMA onto the executable // and if a D&D happens, the first parameter (args[0]) is a path. // There was a requirement for the console interface. Parse the parameters. if (args[0] == "create" || args[0] == "extract") { // Load the legacy (gmad.exe) interface return(Legacy.Main(args)); } else if (args[0] == "realtime") { // Load the realtime command-line return(RealtimeCommandline.Main(args)); } } else { #if WINDOWS IntPtr consoleHandle = _GetConsoleWindow(); bool dontRestartMyself = consoleHandle == IntPtr.Zero; #if DEBUG dontRestartMyself = dontRestartMyself || AppDomain.CurrentDomain.FriendlyName.Contains(".vshost"); #endif if (dontRestartMyself) { // There is no console window or this is a debug run. // Start the main form Application.Run(new Main(args)); } else { // There is a console the program is running in. // Restart itself without one. // (This is why the little flicker happens.) Process.Start(new ProcessStartInfo(Assembly.GetEntryAssembly().Location, String.Join(" ", args)) { CreateNoWindow = true, UseShellExecute = false, }); } #endif #if MONO Application.Run(new Main(args)); #endif } return(0); }
private void btnCreate_Click(object sender, EventArgs e) { if (cmbTag1.SelectedItem == cmbTag2.SelectedItem && !(cmbTag1.SelectedItem.ToString() == "(empty)" && cmbTag2.SelectedItem.ToString() == "(empty)")) { MessageBox.Show("You selected the same tag twice!", "Update metadata", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); return; } List <CreateError> errors = new List <CreateError>(); // // Make sure there's a slash on the end // txtFolder.Text = txtFolder.Text.TrimEnd('/'); txtFolder.Text = txtFolder.Text + "/"; // // Make sure OutFile ends in .gma // txtFile.Text = Path.GetFileNameWithoutExtension(txtFile.Text); txtFile.Text += ".gma"; Addon addon = null; if (gboConvertMetadata.Visible == false) { // // Load the Addon Info file // Json addonInfo; try { addonInfo = new Json(txtFolder.Text + "addon.json"); } catch (AddonJSONException ex) { MessageBox.Show(ex.Message, "addon.json parse error", MessageBoxButtons.OK, MessageBoxIcon.Stop); return; } addon = new Addon(addonInfo); } else if (gboConvertMetadata.Visible == true) { // Load the addon metadata from the old file structure: info.txt/addon.txt if (!File.Exists(txtFolder.Text + Path.DirectorySeparatorChar + "info.txt") && !File.Exists(txtFolder.Text + Path.DirectorySeparatorChar + "addon.txt")) { MessageBox.Show("A legacy metadata file \"info.txt\" or \"addon.txt\" could not be found!", "Failed to create the addon", MessageBoxButtons.OK, MessageBoxIcon.Stop); return; } string legacyInfo = String.Empty;; try { if (File.Exists(txtFolder.Text + Path.DirectorySeparatorChar + "info.txt")) { legacyInfo = File.ReadAllText(txtFolder.Text + Path.DirectorySeparatorChar + "info.txt"); } else if (File.Exists(txtFolder.Text + Path.DirectorySeparatorChar + "addon.txt")) { legacyInfo = File.ReadAllText(txtFolder.Text + Path.DirectorySeparatorChar + "addon.txt"); } } catch (Exception ex) { MessageBox.Show("There was an error reading the metadata.\n\n" + ex.Message, "Failed to create the addon", MessageBoxButtons.OK, MessageBoxIcon.Stop); return; } addon = new Addon(); // Parse the read data Regex regex = new System.Text.RegularExpressions.Regex("\"([A-Za-z_\r\n]*)\"[\\s]*\"([\\s\\S]*?)\"", RegexOptions.IgnoreCase | RegexOptions.Multiline); MatchCollection matches = regex.Matches(legacyInfo); // info.txt/addon.txt files usually have these values not directly mapped into GMAs as well. string AuthorName = String.Empty; string AuthorEmail = String.Empty; string AuthorURL = String.Empty; string Version = String.Empty; string Date = String.Empty; foreach (Match keyMatch in matches) { if (keyMatch.Groups.Count == 3) { // All match should have 2 groups matched (the 0th group is the whole match.) switch (keyMatch.Groups[1].Value.ToLowerInvariant()) { case "name": addon.Title = keyMatch.Groups[2].Value; break; case "version": Version = keyMatch.Groups[2].Value; break; case "up_date": Date = keyMatch.Groups[2].Value; break; case "author_name": //addon.Author = keyMatch.Groups[2].Value; // GMAD writer only writes "Author Name" right now... AuthorName = keyMatch.Groups[2].Value; break; case "author_email": AuthorEmail = keyMatch.Groups[2].Value; break; case "author_url": AuthorURL = keyMatch.Groups[2].Value; break; case "info": addon.Description = keyMatch.Groups[2].Value; break; } } } // Prettify the loaded Description. string newDescription = String.Empty; bool hasNewDescription = (!String.IsNullOrWhiteSpace(AuthorName) || !String.IsNullOrWhiteSpace(AuthorEmail) || !String.IsNullOrWhiteSpace(AuthorURL) || !String.IsNullOrWhiteSpace(Version) || !String.IsNullOrWhiteSpace(Date)); if (hasNewDescription) { newDescription = "## Converted by SharpGMad " + Program.PrettyVersion + " at " + DateTime.Now.ToString("ddd MM dd hh:mm:ss yyyy", System.Globalization.CultureInfo.InvariantCulture) + " (+" + TimeZoneInfo.Local.BaseUtcOffset.ToString("hhmm") + ")"; } if (!String.IsNullOrWhiteSpace(AuthorName)) { newDescription += "\n# AuthorName: " + AuthorName; } if (!String.IsNullOrWhiteSpace(AuthorEmail)) { newDescription += "\n# AuthorEmail: " + AuthorEmail; } if (!String.IsNullOrWhiteSpace(AuthorURL)) { newDescription += "\n# AuthorURL: " + AuthorURL; } if (!String.IsNullOrWhiteSpace(Version)) { newDescription += "\n# Version: " + Version; } if (!String.IsNullOrWhiteSpace(Date)) { newDescription += "\n# Date: " + Date; } if (hasNewDescription) { // If anything was added to the prettifiction newDescription += "\n## End conversion info"; addon.Description = newDescription + (!String.IsNullOrWhiteSpace(addon.Description) ? Environment.NewLine + addon.Description : null); } if (cmbType.SelectedItem != null && Tags.TypeExists(cmbType.SelectedItem.ToString())) { addon.Type = cmbType.SelectedItem.ToString(); } else { // This should not happen in normal operation // nontheless we check against it MessageBox.Show("The selected type is invalid!", "Update metadata", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); return; } if (((cmbTag1.SelectedItem.ToString() != "(empty)") && !Tags.TagExists(cmbTag1.SelectedItem.ToString())) || ((cmbTag2.SelectedItem.ToString() != "(empty)") && !Tags.TagExists(cmbTag2.SelectedItem.ToString()))) { // This should not happen in normal operation // nontheless we check against it MessageBox.Show("The selected tags are invalid!", "Update metadata", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); return; } addon.Tags = new List <string>(2); if (cmbTag1.SelectedItem.ToString() != "(empty)") { addon.Tags.Add(cmbTag1.SelectedItem.ToString()); } if (cmbTag2.SelectedItem.ToString() != "(empty)") { addon.Tags.Add(cmbTag2.SelectedItem.ToString()); } } // // Get a list of files in the specified folder // foreach (string f in Directory.GetFiles(txtFolder.Text, "*", SearchOption.AllDirectories)) { string file = f; file = file.Replace(txtFolder.Text, String.Empty); file = file.Replace('\\', '/'); if (file == "addon.json" || file == "info.txt") { continue; // Don't read the metadata file } try { addon.CheckRestrictions(file); addon.AddFile(file, File.ReadAllBytes(f)); } catch (IOException) { errors.Add(new CreateError() { Path = file, Type = CreateErrorType.FileRead }); continue; } catch (IgnoredException) { errors.Add(new CreateError() { Path = file, Type = CreateErrorType.Ignored }); continue; } catch (WhitelistException) { errors.Add(new CreateError() { Path = file, Type = CreateErrorType.WhitelistFailure }); if (!chkWarnInvalid.Checked) { MessageBox.Show("The following file is not allowed by the whitelist:\n" + file, "Failed to create the addon", MessageBoxButtons.OK, MessageBoxIcon.Stop); return; } } } // // Sort the list into alphabetical order, no real reason - we're just ODC // addon.Sort(); // // Create an addon file in a buffer // // // Save the buffer to the provided name // FileStream gmaFS; try { gmaFS = new FileStream(txtFile.Text, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); gmaFS.SetLength(0); // Truncate the file Writer.Create(addon, gmaFS); } catch (Exception be) { MessageBox.Show("An exception happened while compiling the addon.\n\n" + be.Message, "Failed to create the addon", MessageBoxButtons.OK, MessageBoxIcon.Stop); return; } gmaFS.Flush(); // // Success! // if (errors.Count == 0) { MessageBox.Show("Successfully created the addon.", "Create addon", MessageBoxButtons.OK, MessageBoxIcon.Information); } else if (errors.Count == 1) { MessageBox.Show("Successfully created the addon.\nThe file " + errors[0].Path + " was not added " + "because " + errors[0].Type, "Create addon", MessageBoxButtons.OK, MessageBoxIcon.Warning); } else if (errors.Count > 1) { DialogResult showFailedFiles = MessageBox.Show(errors.Count + " files failed to add." + "\n\nShow a list of failures?", "Create addon", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (showFailedFiles == DialogResult.Yes) { string temppath = ContentFile.GenerateExternalPath( new Random().Next() + "_failedCreations") + ".txt"; string msgboxMessage = String.Empty; foreach (CreateError err in errors) { msgboxMessage += err.Path + ", "; switch (err.Type) { case CreateErrorType.FileRead: msgboxMessage += "failed to read the file"; break; case CreateErrorType.Ignored: msgboxMessage += "the file is ignored by the addon's configuration"; break; case CreateErrorType.WhitelistFailure: msgboxMessage += "the file is not allowed by the global whitelist"; break; } msgboxMessage += "\r\n"; } msgboxMessage = msgboxMessage.TrimEnd('\r', '\n'); try { File.WriteAllText(temppath, "These files failed to add:\r\n\r\n" + msgboxMessage); } catch (Exception) { MessageBox.Show("Can't show the list, an error happened generating it.", "Extract addon", MessageBoxButtons.OK, MessageBoxIcon.Stop); return; } // The file will be opened by the user's default text file handler (Notepad?) System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(temppath) { UseShellExecute = true, }); } } gmaFS.Dispose(); btnAbort_Click(sender, e); // Close the form return; }
private void btnExtract_Click(object sender, EventArgs e) { List <string> extractFailures = new List <string>(); // // If an out path hasn't been provided, make our own // if (txtFolder.Text == String.Empty) { txtFolder.Text = Path.GetFileNameWithoutExtension(txtFile.Text); } // // Remove slash, add slash (enforces a slash) // txtFolder.Text = txtFolder.Text.TrimEnd('/'); txtFolder.Text = txtFolder.Text + '/'; Addon addon; try { FileStream fs = new FileStream(txtFile.Text, FileMode.Open, FileAccess.ReadWrite); addon = new Addon(new Reader(fs)); } catch (Exception ex) { MessageBox.Show("Can't open the selected file.\nError happened: " + ex.Message, "Failed to extract addon", MessageBoxButtons.OK, MessageBoxIcon.Stop); return; } foreach (ContentFile entry in addon.Files) { // Make sure folder exists try { Directory.CreateDirectory(txtFolder.Text + Path.GetDirectoryName(entry.Path)); } catch (Exception) { // Noop } // Write the file to the disk try { using (FileStream file = new FileStream(txtFolder.Text + entry.Path, FileMode.Create, FileAccess.Write)) { file.Write(entry.Content, 0, (int)entry.Size); } } catch (Exception) { extractFailures.Add(entry.Path); } } if (chkWriteLegacy.Checked) // Write a legacy info.txt schema { // The description has paramteres if the addon was created by a conversion. // Extract them out. Regex regex = new System.Text.RegularExpressions.Regex("^# ([\\s\\S]*?): ([\\s\\S]*?)$", RegexOptions.IgnoreCase | RegexOptions.Multiline); MatchCollection matches = regex.Matches(addon.Description); // info.txt/addon.txt files usually have these values not directly mapped into GMAs as well. string AuthorName = String.Empty; string AuthorEmail = String.Empty; string AuthorURL = String.Empty; string Version = String.Empty; string Date = String.Empty; foreach (Match keyMatch in matches) { if (keyMatch.Groups.Count == 3) { // All match should have 2 groups matched (the 0th group is the whole match.) switch (keyMatch.Groups[1].Value.ToLowerInvariant()) { case "version": Version = keyMatch.Groups[2].Value.TrimEnd('\n', '\r', '\t'); break; case "date": Date = keyMatch.Groups[2].Value.TrimEnd('\n', '\r', '\t'); break; case "authorname": AuthorName = keyMatch.Groups[2].Value.TrimEnd('\n', '\r', '\t'); break; case "authoremail": AuthorEmail = keyMatch.Groups[2].Value.TrimEnd('\n', '\r', '\t'); break; case "authorurl": AuthorURL = keyMatch.Groups[2].Value.TrimEnd('\n', '\r', '\t'); break; } } } string endConversionInfo = "## End conversion info"; string description = addon.Description; if (addon.Description.IndexOf(endConversionInfo) > 0) { description = addon.Description.Substring(addon.Description.IndexOf(endConversionInfo) + endConversionInfo.Length); description = description.TrimStart('\r', '\n'); } File.WriteAllText(txtFolder.Text + Path.DirectorySeparatorChar + "info.txt", "\"AddonInfo\"\n" + "{\n" + "\t" + "\"name\"" + "\t" + "\"" + addon.Title + "\"\n" + "\t" + "\"version\"" + "\t" + "\"" + Version + "\"\n" + "\t" + "\"up_date\"" + "\t" + "\"" + (String.IsNullOrWhiteSpace(Date) ? addon.Timestamp.ToString("ddd MM dd hh:mm:ss yyyy", System.Globalization.CultureInfo.InvariantCulture) : DateTime.Now.ToString("ddd MM dd hh:mm:ss yyyy", System.Globalization.CultureInfo.InvariantCulture) + " (+" + TimeZoneInfo.Local.BaseUtcOffset.ToString("hhmm") + ")") + "\"\n" + "\t" + "\"author_name\"" + "\t" + "\"" + AuthorName + "\"\n" + // addon.Author would be nice "\t" + "\"author_email\"" + "\t" + "\"" + AuthorEmail + "\"\n" + "\t" + "\"author_url\"" + "\t" + "\"" + AuthorURL + "\"\n" + "\t" + "\"info\"" + "\t" + "\"" + description + "\"\n" + "\t" + "\"override\"" + "\t" + "\"1\"\n" + "}"); } if (extractFailures.Count == 0) { MessageBox.Show("Successfully extracted the addon.", "Extract addon", MessageBoxButtons.OK, MessageBoxIcon.Information); } else if (extractFailures.Count == 1) { MessageBox.Show("Failed to extract " + extractFailures[0], "Extract addon", MessageBoxButtons.OK, MessageBoxIcon.Warning); } else if (extractFailures.Count > 1) { DialogResult showFailedFiles = MessageBox.Show(extractFailures.Count + " files failed to extract." + "\n\nShow a list of failures?", "Extract addon", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (showFailedFiles == DialogResult.Yes) { string temppath = ContentFile.GenerateExternalPath( new Random().Next() + "_failedExtracts") + ".txt"; try { File.WriteAllText(temppath, "These files failed to extract:\r\n\r\n" + String.Join("\r\n", extractFailures.ToArray())); } catch (Exception) { MessageBox.Show("Can't show the list, an error happened generating it.", "Extract addon", MessageBoxButtons.OK, MessageBoxIcon.Stop); return; } // The file will be opened by the user's default text file handler (Notepad?) System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(temppath) { UseShellExecute = true, }); } } btnAbort_Click(sender, e); // Close the form return; }
/// <summary> /// Adds the specified file into the Addon's internal container. /// </summary> /// <param name="path">The path of the file.</param> /// <param name="content">Array of bytes containing the file content.</param> /// <exception cref="ArgumentException">Happens if a file with the same path is already added.</exception> /// <exception cref="WhitelistException">The file is prohibited from storing by the global whitelist.</exception> /// <exception cref="IgnoredException">The file is prohibited from storing by the addon's ignore list.</exception> public void AddFile(string path, byte[] content) { if (path.ToLowerInvariant() != path) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("\t\t[Filename contains capital letters]"); Console.ResetColor(); path = path.ToLowerInvariant(); } try { // This should throw the exception if error happens. CheckRestrictions(path); } catch (WhitelistException) { throw; } catch (IgnoredException) { throw; } catch (ArgumentException) { throw; } ContentFile file = new ContentFile(path, content); Files.Add(file); }
/// <summary> /// Sets up a new instance of Addon using the data provided by the specified addon reader. /// </summary> /// <param name="reader">The addon reader which handled reading the addon file.</param> /// <exception cref="ArgumentException">Happens if a file with the same path is already added.</exception> /// <exception cref="WhitelistException">The file is prohibited from storing by the global whitelist.</exception> /// <exception cref="IgnoredException">The file is prohibited from storing by the addon's ignore list.</exception> public Addon(Reader reader) : this() { Author = reader.Author; Title = reader.Name; Description = reader.Description; Type = reader.Type; Tags = reader.Tags; AddonVersion = reader.Version; FormatVersion = reader.FormatVersion; SteamID = reader.SteamID; Timestamp = reader.Timestamp; foreach (Reader.IndexEntry file in reader.Index) { try { CheckRestrictions(file.Path); ContentFile contentFile = new ContentFile(reader, file); Files.Add(contentFile); } catch (WhitelistException) { throw; } catch (IgnoredException) { throw; } catch (ArgumentException) { throw; } } }