Enforce nullability in SabreTools program

This commit is contained in:
Matt Nadareski
2024-03-06 00:53:32 -05:00
parent f0fa7bb6bf
commit fe0dccb8d2
15 changed files with 118 additions and 115 deletions

View File

@@ -39,7 +39,7 @@ namespace SabreTools.DatTools
/// <returns>True if the DAT was written correctly, false otherwise</returns> /// <returns>True if the DAT was written correctly, false otherwise</returns>
public static bool Write( public static bool Write(
DatFile datFile, DatFile datFile,
string outDir, string ?outDir,
bool overwrite = true, bool overwrite = true,
bool ignoreblanks = false, bool ignoreblanks = false,
bool quotes = true, bool quotes = true,
@@ -53,7 +53,7 @@ namespace SabreTools.DatTools
} }
// Ensure the output directory is set and created // Ensure the output directory is set and created
outDir = outDir.Ensure(create: true); outDir = outDir?.Ensure(create: true);
InternalStopwatch watch = new($"Writing out internal dat to '{outDir}'"); InternalStopwatch watch = new($"Writing out internal dat to '{outDir}'");
@@ -74,7 +74,7 @@ namespace SabreTools.DatTools
logger.User($"A total of {datFile.Items.TotalCount - datFile.Items.RemovedCount} items will be written out to '{datFile.Header.FileName}'"); logger.User($"A total of {datFile.Items.TotalCount - datFile.Items.RemovedCount} items will be written out to '{datFile.Header.FileName}'");
// Get the outfile names // Get the outfile names
Dictionary<DatFormat, string> outfiles = datFile.Header.CreateOutFileNames(outDir, overwrite); Dictionary<DatFormat, string> outfiles = datFile.Header.CreateOutFileNames(outDir!, overwrite);
try try
{ {

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using SabreTools.Core; using SabreTools.Core;
using SabreTools.Core.Tools; using SabreTools.Core.Tools;
using SabreTools.DatFiles; using SabreTools.DatFiles;
@@ -25,7 +24,7 @@ namespace SabreTools.Features
public Batch() public Batch()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "bt", "batch" }; Flags = ["bt", "batch"];
Description = "Enable batch mode"; Description = "Enable batch mode";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = @"Run a special mode that takes input files as lists of batch commands to run sequentially. Each command has to be its own line and must be followed by a semicolon (`;`). Commented lines may start with either `REM` or `#`. Multiple batch files are allowed but they will be run independently from each other. LongDescription = @"Run a special mode that takes input files as lists of batch commands to run sequentially. Each command has to be its own line and must be followed by a semicolon (`;`). Commented lines may start with either `REM` or `#`. Multiple batch files are allowed but they will be run independently from each other.
@@ -62,7 +61,7 @@ Reset the internal state: reset();";
// Try to read each input as a batch run file // Try to read each input as a batch run file
foreach (string path in Inputs) foreach (string path in Inputs)
{ {
InternalStopwatch watch = new($"Processing '{path}'..."); var watch = new InternalStopwatch($"Processing '{path}'...");
ProcessScript(path); ProcessScript(path);
watch.Stop(); watch.Stop();
} }
@@ -90,7 +89,7 @@ Reset the internal state: reset();";
string[] lines = File.ReadAllLines(path); string[] lines = File.ReadAllLines(path);
// Each batch file has its own state // Each batch file has its own state
BatchState batchState = new(); var batchState = new BatchState();
// Process each command line // Process each command line
foreach (string line in lines) foreach (string line in lines)
@@ -139,7 +138,7 @@ Reset the internal state: reset();";
/// </summary> /// </summary>
private abstract class BatchCommand private abstract class BatchCommand
{ {
public string Name { get; set; } public string? Name { get; set; }
public List<string> Arguments { get; private set; } = []; public List<string> Arguments { get; private set; } = [];
/// <summary> /// <summary>
@@ -872,12 +871,12 @@ Reset the internal state: reset();";
public override void Process(BatchState batchState) public override void Process(BatchState batchState)
{ {
// Get overwrite value, if possible // Get overwrite value, if possible
bool? overwrite = true; bool overwrite = true;
if (Arguments.Count == 1) if (Arguments.Count == 1)
overwrite = Arguments[0].AsYesNo(); overwrite = Arguments[0].AsYesNo() ?? true;
// Write out the dat with the current state // Write out the dat with the current state
Writer.Write(batchState.DatFile, batchState.OutputDirectory, overwrite: overwrite.Value); Writer.Write(batchState.DatFile, batchState.OutputDirectory, overwrite: overwrite);
} }
} }

View File

@@ -15,11 +15,11 @@ namespace SabreTools.Features
public DatFromDir() public DatFromDir()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "d", "d2d", "dfd" }; Flags = ["d", "d2d", "dfd"];
Description = "Create DAT(s) from an input directory"; Description = "Create DAT(s) from an input directory";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = "Create a DAT file from an input directory or set of files. By default, this will output a DAT named based on the input directory and the current date. It will also treat all archives as possible games and add all three hashes (CRC, MD5, SHA-1) for each file."; LongDescription = "Create a DAT file from an input directory or set of files. By default, this will output a DAT named based on the input directory and the current date. It will also treat all archives as possible games and add all three hashes (CRC, MD5, SHA-1) for each file.";
Features = new Dictionary<string, Help.Feature>(); Features = [];
// Common Features // Common Features
AddCommonFeatures(); AddCommonFeatures();
@@ -68,10 +68,10 @@ namespace SabreTools.Features
// Apply the specialized field removals to the cleaner // Apply the specialized field removals to the cleaner
if (!addFileDates) if (!addFileDates)
Remover.PopulateExclusionsFromList(new List<string> { "DatItem.Date" }); Remover!.PopulateExclusionsFromList(["DatItem.Date"]);
// Create a new DATFromDir object and process the inputs // Create a new DATFromDir object and process the inputs
DatFile basedat = DatFile.Create(Header); DatFile basedat = DatFile.Create(Header!);
basedat.Header.Date = DateTime.Now.ToString("yyyy-MM-dd"); basedat.Header.Date = DateTime.Now.ToString("yyyy-MM-dd");
// For each input directory, create a DAT // For each input directory, create a DAT
@@ -98,11 +98,11 @@ namespace SabreTools.Features
if (success) if (success)
{ {
// Perform additional processing steps // Perform additional processing steps
Extras.ApplyExtras(datdata); Extras!.ApplyExtras(datdata);
Splitter.ApplySplitting(datdata, useTags: false); Splitter!.ApplySplitting(datdata, useTags: false);
datdata.ExecuteFilters(FilterRunner); datdata.ExecuteFilters(FilterRunner!);
Cleaner.ApplyCleaning(datdata); Cleaner!.ApplyCleaning(datdata);
Remover.ApplyRemovals(datdata); Remover!.ApplyRemovals(datdata);
// Write out the file // Write out the file
Writer.Write(datdata, OutputDir); Writer.Write(datdata, OutputDir);

View File

@@ -1,6 +1,4 @@
using System.Collections.Generic; using SabreTools.Help;
using SabreTools.Help;
namespace SabreTools.Features namespace SabreTools.Features
{ {
@@ -11,7 +9,7 @@ namespace SabreTools.Features
public DisplayHelp() public DisplayHelp()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "?", "h", "help" }; Flags = ["?", "h", "help"];
Description = "Show this help"; Description = "Show this help";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = "Built-in to most of the programs is a basic help text."; LongDescription = "Built-in to most of the programs is a basic help text.";

View File

@@ -1,6 +1,4 @@
using System.Collections.Generic; using SabreTools.Help;
using SabreTools.Help;
namespace SabreTools.Features namespace SabreTools.Features
{ {
@@ -11,7 +9,7 @@ namespace SabreTools.Features
public DisplayHelpDetailed() public DisplayHelpDetailed()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "??", "hd", "help-detailed" }; Flags = ["??", "hd", "help-detailed"];
Description = "Show this detailed help"; Description = "Show this detailed help";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = "Display a detailed help text to the screen."; LongDescription = "Display a detailed help text to the screen.";

View File

@@ -1,7 +1,6 @@
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using SabreTools.Core;
using SabreTools.Core.Tools; using SabreTools.Core.Tools;
using SabreTools.FileTypes; using SabreTools.FileTypes;
using SabreTools.Hashing; using SabreTools.Hashing;
@@ -18,7 +17,7 @@ namespace SabreTools.Features
public Extract() public Extract()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "ex", "extract" }; Flags = ["ex", "extract"];
Description = "Extract and remove copier headers"; Description = "Extract and remove copier headers";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = @"This will detect, store, and remove copier headers from a file or folder of files. The headers are backed up and collated by the hash of the unheadered file. Files are then output without the detected copier header alongside the originals with the suffix .new. No input files are altered in the process. Only uncompressed files will be processed. LongDescription = @"This will detect, store, and remove copier headers from a file or folder of files. The headers are backed up and collated by the hash of the unheadered file. Files are then output without the detected copier header alongside the originals with the suffix .new. No input files are altered in the process. Only uncompressed files will be processed.
@@ -67,7 +66,7 @@ The following systems have headers that this program can work with:
/// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param> /// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param>
/// <param name="nostore">True if headers should not be stored in the database, false otherwise</param> /// <param name="nostore">True if headers should not be stored in the database, false otherwise</param>
/// <returns>True if the output file was created, false otherwise</returns> /// <returns>True if the output file was created, false otherwise</returns>
private bool DetectTransformStore(string file, string outDir, bool nostore) private bool DetectTransformStore(string file, string? outDir, bool nostore)
{ {
// Create the output directory if it doesn't exist // Create the output directory if it doesn't exist
if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir)) if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir))
@@ -91,9 +90,10 @@ The following systems have headers that this program can work with:
{ {
// Extract the header as a string for the database // Extract the header as a string for the database
using var fs = File.OpenRead(file); using var fs = File.OpenRead(file);
byte[] hbin = new byte[int.Parse(rule.StartOffset)]; int startOffset = int.Parse(rule.StartOffset ?? "0");
fs.Read(hbin, 0, int.Parse(rule.StartOffset)); byte[] hbin = new byte[startOffset];
hstr = TextHelper.ByteArrayToString(hbin); fs.Read(hbin, 0, startOffset);
hstr = TextHelper.ByteArrayToString(hbin)!;
} }
catch catch
{ {
@@ -101,7 +101,7 @@ The following systems have headers that this program can work with:
} }
// Apply the rule to the file // Apply the rule to the file
string newfile = (string.IsNullOrWhiteSpace(outDir) ? Path.GetFullPath(file) + ".new" : Path.Combine(outDir, Path.GetFileName(file))); string newfile = string.IsNullOrWhiteSpace(outDir) ? Path.GetFullPath(file) + ".new" : Path.Combine(outDir, Path.GetFileName(file));
rule.TransformFile(file, newfile); rule.TransformFile(file, newfile);
// If the output file doesn't exist, return false // If the output file doesn't exist, return false
@@ -111,8 +111,8 @@ The following systems have headers that this program can work with:
// Now add the information to the database if it's not already there // Now add the information to the database if it's not already there
if (!nostore) if (!nostore)
{ {
BaseFile baseFile = BaseFile.GetInfo(newfile, hashes: [HashType.SHA1], asFiles: TreatAsFile.NonArchive); BaseFile? baseFile = BaseFile.GetInfo(newfile, hashes: [HashType.SHA1], asFiles: TreatAsFile.NonArchive);
AddHeaderToDatabase(hstr, TextHelper.ByteArrayToString(baseFile.SHA1), rule.SourceFile); AddHeaderToDatabase(hstr, TextHelper.ByteArrayToString(baseFile!.SHA1)!, rule.SourceFile!);
} }
return true; return true;

View File

@@ -1,7 +1,6 @@
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using SabreTools.Core;
using SabreTools.Core.Tools; using SabreTools.Core.Tools;
using SabreTools.FileTypes; using SabreTools.FileTypes;
using SabreTools.Hashing; using SabreTools.Hashing;
@@ -17,7 +16,7 @@ namespace SabreTools.Features
public Restore() public Restore()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "re", "restore" }; Flags = ["re", "restore"];
Description = "Restore header to file based on SHA-1"; Description = "Restore header to file based on SHA-1";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = @"This will make use of stored copier headers and reapply them to files if they match the included hash. More than one header can be applied to a file, so they will be output to new files, suffixed with .newX, where X is a number. No input files are altered in the process. Only uncompressed files will be processed. LongDescription = @"This will make use of stored copier headers and reapply them to files if they match the included hash. More than one header can be applied to a file, so they will be output to new files, suffixed with .newX, where X is a number. No input files are altered in the process. Only uncompressed files will be processed.
@@ -35,7 +34,7 @@ The following systems have headers that this program can work with:
// Common Features // Common Features
AddCommonFeatures(); AddCommonFeatures();
AddFeature(OutputDirStringInput); AddFeature(OutputDirStringInput);
} }
@@ -54,24 +53,24 @@ The following systems have headers that this program can work with:
return true; return true;
} }
/// <summary> /// <summary>
/// Detect and replace header(s) to the given file /// Detect and replace header(s) to the given file
/// </summary> /// </summary>
/// <param name="file">Name of the file to be parsed</param> /// <param name="file">Name of the file to be parsed</param>
/// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param> /// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param>
/// <returns>True if a header was found and appended, false otherwise</returns> /// <returns>True if a header was found and appended, false otherwise</returns>
public bool RestoreHeader(string file, string outDir) public bool RestoreHeader(string file, string? outDir)
{ {
// Create the output directory if it doesn't exist // Create the output directory if it doesn't exist
if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir)) if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir))
Directory.CreateDirectory(outDir); Directory.CreateDirectory(outDir);
// First, get the SHA-1 hash of the file // First, get the SHA-1 hash of the file
BaseFile baseFile = BaseFile.GetInfo(file, hashes: [HashType.SHA1], asFiles: TreatAsFile.NonArchive); BaseFile? baseFile = BaseFile.GetInfo(file, hashes: [HashType.SHA1], asFiles: TreatAsFile.NonArchive);
// Retrieve a list of all related headers from the database // Retrieve a list of all related headers from the database
List<string> headers = RetrieveHeadersFromDatabase(TextHelper.ByteArrayToString(baseFile.SHA1)); List<string> headers = RetrieveHeadersFromDatabase(TextHelper.ByteArrayToString(baseFile!.SHA1)!);
// If we have nothing retrieved, we return false // If we have nothing retrieved, we return false
if (headers.Count == 0) if (headers.Count == 0)
@@ -100,14 +99,14 @@ The following systems have headers that this program can work with:
EnsureDatabase(); EnsureDatabase();
// Open the database connection // Open the database connection
SqliteConnection dbc = new(HeadererConnectionString); SqliteConnection dbc = new SqliteConnection(HeadererConnectionString);
dbc.Open(); dbc.Open();
// Create the output list of headers // Create the output list of headers
List<string> headers = new(); List<string> headers = [];
string query = $"SELECT header, type FROM data WHERE sha1='{SHA1}'"; string query = $"SELECT header, type FROM data WHERE sha1='{SHA1}'";
SqliteCommand slc = new(query, dbc); SqliteCommand slc = new SqliteCommand(query, dbc);
SqliteDataReader sldr = slc.ExecuteReader(); SqliteDataReader sldr = slc.ExecuteReader();
if (sldr.HasRows) if (sldr.HasRows)
@@ -130,7 +129,7 @@ The following systems have headers that this program can work with:
return headers; return headers;
} }
/// <summary> /// <summary>
/// Add an aribtrary number of bytes to the inputted file /// Add an aribtrary number of bytes to the inputted file
/// </summary> /// </summary>
@@ -138,7 +137,7 @@ The following systems have headers that this program can work with:
/// <param name="output">Outputted file</param> /// <param name="output">Outputted file</param>
/// <param name="bytesToAddToHead">Bytes to be added to head of file</param> /// <param name="bytesToAddToHead">Bytes to be added to head of file</param>
/// <param name="bytesToAddToTail">Bytes to be added to tail of file</param> /// <param name="bytesToAddToTail">Bytes to be added to tail of file</param>
private static void AppendBytes(string input, string output, byte[] bytesToAddToHead, byte[] bytesToAddToTail) private static void AppendBytes(string input, string output, byte[]? bytesToAddToHead, byte[]? bytesToAddToTail)
{ {
// If any of the inputs are invalid, skip // If any of the inputs are invalid, skip
if (!File.Exists(input)) if (!File.Exists(input))
@@ -157,7 +156,7 @@ The following systems have headers that this program can work with:
/// <param name="output">Outputted stream</param> /// <param name="output">Outputted stream</param>
/// <param name="bytesToAddToHead">Bytes to be added to head of stream</param> /// <param name="bytesToAddToHead">Bytes to be added to head of stream</param>
/// <param name="bytesToAddToTail">Bytes to be added to tail of stream</param> /// <param name="bytesToAddToTail">Bytes to be added to tail of stream</param>
private static void AppendBytes(Stream input, Stream output, byte[] bytesToAddToHead, byte[] bytesToAddToTail) private static void AppendBytes(Stream input, Stream output, byte[]? bytesToAddToHead, byte[]? bytesToAddToTail)
{ {
// Write out prepended bytes // Write out prepended bytes
if (bytesToAddToHead != null && bytesToAddToHead.Length > 0) if (bytesToAddToHead != null && bytesToAddToHead.Length > 0)

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using SabreTools.DatFiles; using SabreTools.DatFiles;
using SabreTools.DatTools; using SabreTools.DatTools;
using SabreTools.FileTypes; using SabreTools.FileTypes;
@@ -17,7 +16,7 @@ namespace SabreTools.Features
public Sort() public Sort()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "ss", "sort" }; Flags = ["ss", "sort"];
Description = "Sort inputs by a set of DATs"; Description = "Sort inputs by a set of DATs";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = "This feature allows the user to quickly rebuild based on a supplied DAT file(s). By default all files will be rebuilt to uncompressed folders in the output directory."; LongDescription = "This feature allows the user to quickly rebuild based on a supplied DAT file(s). By default all files will be rebuilt to uncompressed folders in the output directory.";
@@ -43,7 +42,7 @@ namespace SabreTools.Features
AddFeature(TarFlag); AddFeature(TarFlag);
AddFeature(TorrentGzipFlag); AddFeature(TorrentGzipFlag);
this[TorrentGzipFlag]!.AddFeature(RombaFlag); this[TorrentGzipFlag]!.AddFeature(RombaFlag);
this[TorrentGzipFlag][RombaFlag]!.AddFeature(RombaDepthInt32Input); this[TorrentGzipFlag]![RombaFlag]!.AddFeature(RombaDepthInt32Input);
//AddFeature(SharedInputs.TorrentLrzipFlag); //AddFeature(SharedInputs.TorrentLrzipFlag);
//AddFeature(SharedInputs.TorrentLz4Flag); //AddFeature(SharedInputs.TorrentLz4Flag);
//AddFeature(SharedInputs.TorrentRarFlag); //AddFeature(SharedInputs.TorrentRarFlag);
@@ -74,7 +73,7 @@ namespace SabreTools.Features
var outputFormat = GetOutputFormat(features); var outputFormat = GetOutputFormat(features);
// If we have the romba flag // If we have the romba flag
if (Header.OutputDepot?.IsActive == true) if (Header!.OutputDepot?.IsActive == true)
{ {
// Update TorrentGzip output // Update TorrentGzip output
if (outputFormat == OutputFormat.TorrentGzip) if (outputFormat == OutputFormat.TorrentGzip)
@@ -108,9 +107,9 @@ namespace SabreTools.Features
// If we have the depot flag, respect it // If we have the depot flag, respect it
bool success; bool success;
if (Header.InputDepot?.IsActive ?? false) if (Header.InputDepot?.IsActive ?? false)
success = Rebuilder.RebuildDepot(datdata, Inputs, Path.Combine(OutputDir, datdata.Header.FileName), date, delete, inverse, outputFormat); success = Rebuilder.RebuildDepot(datdata, Inputs, Path.Combine(OutputDir!, datdata.Header.FileName!), date, delete, inverse, outputFormat);
else else
success = Rebuilder.RebuildGeneric(datdata, Inputs, Path.Combine(OutputDir, datdata.Header.FileName), quickScan, date, delete, inverse, outputFormat, asFiles); success = Rebuilder.RebuildGeneric(datdata, Inputs, Path.Combine(OutputDir!, datdata.Header.FileName!), quickScan, date, delete, inverse, outputFormat, asFiles);
// If we have a success and we're updating the DAT, write it out // If we have a success and we're updating the DAT, write it out
if (success && updateDat) if (success && updateDat)
@@ -127,7 +126,7 @@ namespace SabreTools.Features
// Otherwise, process all DATs into the same output // Otherwise, process all DATs into the same output
else else
{ {
InternalStopwatch watch = new("Populating internal DAT"); var watch = new InternalStopwatch("Populating internal DAT");
// Add all of the input DATs into one huge internal DAT // Add all of the input DATs into one huge internal DAT
DatFile datdata = DatFile.Create(); DatFile datdata = DatFile.Create();
@@ -149,9 +148,9 @@ namespace SabreTools.Features
// If we have the depot flag, respect it // If we have the depot flag, respect it
bool success; bool success;
if (Header.InputDepot?.IsActive ?? false) if (Header.InputDepot?.IsActive ?? false)
success = Rebuilder.RebuildDepot(datdata, Inputs, OutputDir, date, delete, inverse, outputFormat); success = Rebuilder.RebuildDepot(datdata, Inputs, OutputDir!, date, delete, inverse, outputFormat);
else else
success = Rebuilder.RebuildGeneric(datdata, Inputs, OutputDir, quickScan, date, delete, inverse, outputFormat, asFiles); success = Rebuilder.RebuildGeneric(datdata, Inputs, OutputDir!, quickScan, date, delete, inverse, outputFormat, asFiles);
// If we have a success and we're updating the DAT, write it out // If we have a success and we're updating the DAT, write it out
if (success && updateDat) if (success && updateDat)

View File

@@ -16,11 +16,11 @@ namespace SabreTools.Features
public Split() public Split()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "sp", "split" }; Flags = ["sp", "split"];
Description = "Split input DATs by a given criteria"; Description = "Split input DATs by a given criteria";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = "This feature allows the user to split input DATs by a number of different possible criteria. See the individual input information for details. More than one split type is allowed at a time."; LongDescription = "This feature allows the user to split input DATs by a number of different possible criteria. See the individual input information for details. More than one split type is allowed at a time.";
Features = new Dictionary<string, Help.Feature>(); Features = [];
// Common Features // Common Features
AddCommonFeatures(); AddCommonFeatures();
@@ -64,7 +64,7 @@ namespace SabreTools.Features
foreach (ParentablePath file in files) foreach (ParentablePath file in files)
{ {
// Create and fill the new DAT // Create and fill the new DAT
DatFile internalDat = DatFile.Create(Header); DatFile internalDat = DatFile.Create(Header!);
Parser.ParseInto(internalDat, file); Parser.ParseInto(internalDat, file);
// Get the output directory // Get the output directory
@@ -75,14 +75,16 @@ namespace SabreTools.Features
if (splittingMode.HasFlag(SplittingMode.Extension)) if (splittingMode.HasFlag(SplittingMode.Extension))
{ {
(DatFile? extADat, DatFile? extBDat) = DatTools.Splitter.SplitByExtension(internalDat, GetList(features, ExtAListValue), GetList(features, ExtBListValue)); (DatFile? extADat, DatFile? extBDat) = DatTools.Splitter.SplitByExtension(internalDat, GetList(features, ExtAListValue), GetList(features, ExtBListValue));
if (extADat != null && extBDat != null)
{
var watch = new InternalStopwatch("Outputting extension-split DATs");
InternalStopwatch watch = new("Outputting extension-split DATs"); // Output both possible DatFiles
Writer.Write(extADat, OutputDir);
Writer.Write(extBDat, OutputDir);
// Output both possible DatFiles watch.Stop();
Writer.Write(extADat, OutputDir); }
Writer.Write(extBDat, OutputDir);
watch.Stop();
} }
// Hash splitting // Hash splitting
@@ -90,7 +92,7 @@ namespace SabreTools.Features
{ {
Dictionary<string, DatFile> typeDats = DatTools.Splitter.SplitByHash(internalDat); Dictionary<string, DatFile> typeDats = DatTools.Splitter.SplitByHash(internalDat);
InternalStopwatch watch = new("Outputting hash-split DATs"); var watch = new InternalStopwatch("Outputting hash-split DATs");
// Loop through each type DatFile // Loop through each type DatFile
#if NET452_OR_GREATER || NETCOREAPP #if NET452_OR_GREATER || NETCOREAPP
@@ -117,7 +119,7 @@ namespace SabreTools.Features
logger.Warning("This feature is not implemented: level-split"); logger.Warning("This feature is not implemented: level-split");
DatTools.Splitter.SplitByLevel( DatTools.Splitter.SplitByLevel(
internalDat, internalDat,
OutputDir, OutputDir!,
GetBoolean(features, ShortValue), GetBoolean(features, ShortValue),
GetBoolean(features, BaseValue)); GetBoolean(features, BaseValue));
} }
@@ -127,7 +129,7 @@ namespace SabreTools.Features
{ {
(DatFile lessThan, DatFile greaterThan) = DatTools.Splitter.SplitBySize(internalDat, GetInt64(features, RadixInt64Value)); (DatFile lessThan, DatFile greaterThan) = DatTools.Splitter.SplitBySize(internalDat, GetInt64(features, RadixInt64Value));
InternalStopwatch watch = new("Outputting size-split DATs"); var watch = new InternalStopwatch("Outputting size-split DATs");
// Output both possible DatFiles // Output both possible DatFiles
Writer.Write(lessThan, OutputDir); Writer.Write(lessThan, OutputDir);
@@ -142,7 +144,7 @@ namespace SabreTools.Features
logger.Warning("This feature is not implemented: level-split"); logger.Warning("This feature is not implemented: level-split");
List<DatFile> sizedDats = DatTools.Splitter.SplitByTotalSize(internalDat, GetInt64(features, ChunkSizeInt64Value)); List<DatFile> sizedDats = DatTools.Splitter.SplitByTotalSize(internalDat, GetInt64(features, ChunkSizeInt64Value));
InternalStopwatch watch = new("Outputting total-size-split DATs"); var watch = new InternalStopwatch("Outputting total-size-split DATs");
// Loop through each type DatFile // Loop through each type DatFile
#if NET452_OR_GREATER || NETCOREAPP #if NET452_OR_GREATER || NETCOREAPP
@@ -168,7 +170,7 @@ namespace SabreTools.Features
{ {
Dictionary<ItemType, DatFile> typeDats = DatTools.Splitter.SplitByType(internalDat); Dictionary<ItemType, DatFile> typeDats = DatTools.Splitter.SplitByType(internalDat);
InternalStopwatch watch = new("Outputting ItemType DATs"); var watch = new InternalStopwatch("Outputting ItemType DATs");
// Loop through each type DatFile // Loop through each type DatFile
#if NET452_OR_GREATER || NETCOREAPP #if NET452_OR_GREATER || NETCOREAPP

View File

@@ -13,7 +13,7 @@ namespace SabreTools.Features
public Stats() public Stats()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "st", "stats" }; Flags = ["st", "stats"];
Description = "Get statistics on all input DATs"; Description = "Get statistics on all input DATs";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = @"This will output by default the combined statistics for all input DAT files. LongDescription = @"This will output by default the combined statistics for all input DAT files.
@@ -49,13 +49,13 @@ The stats that are outputted are as follows:
if (!base.ProcessFeatures(features)) if (!base.ProcessFeatures(features))
return false; return false;
string filename = Header.FileName; string filename = Header!.FileName!;
if (Path.GetFileName(filename) != filename) if (Path.GetFileName(filename) != filename)
{ {
if (string.IsNullOrWhiteSpace(OutputDir)) if (string.IsNullOrWhiteSpace(OutputDir))
OutputDir = Path.GetDirectoryName(filename); OutputDir = Path.GetDirectoryName(filename);
else else
OutputDir = Path.Combine(OutputDir, Path.GetDirectoryName(filename)); OutputDir = Path.Combine(OutputDir, Path.GetDirectoryName(filename)!);
filename = Path.GetFileName(filename); filename = Path.GetFileName(filename);
} }

View File

@@ -18,11 +18,11 @@ namespace SabreTools.Features
public Update() public Update()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "ud", "update" }; Flags = ["ud", "update"];
Description = "Update and manipulate DAT(s)"; Description = "Update and manipulate DAT(s)";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = "This is the multitool part of the program, allowing for almost every manipulation to a DAT, or set of DATs. This is also a combination of many different programs that performed DAT manipulation that work better together."; LongDescription = "This is the multitool part of the program, allowing for almost every manipulation to a DAT, or set of DATs. This is also a combination of many different programs that performed DAT manipulation that work better together.";
Features = new Dictionary<string, Help.Feature>(); Features = [];
// Common Features // Common Features
AddCommonFeatures(); AddCommonFeatures();
@@ -38,7 +38,7 @@ namespace SabreTools.Features
this[OutputTypeListInput]!.AddFeature(ReplaceExtensionStringInput); this[OutputTypeListInput]!.AddFeature(ReplaceExtensionStringInput);
this[OutputTypeListInput]!.AddFeature(RemoveExtensionsFlag); this[OutputTypeListInput]!.AddFeature(RemoveExtensionsFlag);
this[OutputTypeListInput]!.AddFeature(RombaFlag); this[OutputTypeListInput]!.AddFeature(RombaFlag);
this[OutputTypeListInput][RombaFlag]!.AddFeature(RombaDepthInt32Input); this[OutputTypeListInput]![RombaFlag]!.AddFeature(RombaDepthInt32Input);
this[OutputTypeListInput]!.AddFeature(DeprecatedFlag); this[OutputTypeListInput]!.AddFeature(DeprecatedFlag);
AddHeaderFeatures(); AddHeaderFeatures();
@@ -68,11 +68,11 @@ namespace SabreTools.Features
AddFeature(BaseReplaceFlag); AddFeature(BaseReplaceFlag);
this[BaseReplaceFlag]!.AddFeature(BaseDatListInput); this[BaseReplaceFlag]!.AddFeature(BaseDatListInput);
this[BaseReplaceFlag]!.AddFeature(UpdateFieldListInput); this[BaseReplaceFlag]!.AddFeature(UpdateFieldListInput);
this[BaseReplaceFlag][UpdateFieldListInput]!.AddFeature(OnlySameFlag); this[BaseReplaceFlag]![UpdateFieldListInput]!.AddFeature(OnlySameFlag);
AddFeature(ReverseBaseReplaceFlag); AddFeature(ReverseBaseReplaceFlag);
this[ReverseBaseReplaceFlag]!.AddFeature(BaseDatListInput); this[ReverseBaseReplaceFlag]!.AddFeature(BaseDatListInput);
this[ReverseBaseReplaceFlag]!.AddFeature(UpdateFieldListInput); this[ReverseBaseReplaceFlag]!.AddFeature(UpdateFieldListInput);
this[ReverseBaseReplaceFlag][UpdateFieldListInput]!.AddFeature(OnlySameFlag); this[ReverseBaseReplaceFlag]![UpdateFieldListInput]!.AddFeature(OnlySameFlag);
AddFeature(DiffCascadeFlag); AddFeature(DiffCascadeFlag);
this[DiffCascadeFlag]!.AddFeature(SkipFirstOutputFlag); this[DiffCascadeFlag]!.AddFeature(SkipFirstOutputFlag);
AddFeature(DiffReverseCascadeFlag); AddFeature(DiffReverseCascadeFlag);
@@ -95,7 +95,7 @@ namespace SabreTools.Features
var updateMode = GetUpdateMode(features); var updateMode = GetUpdateMode(features);
// Normalize the extensions // Normalize the extensions
Header.AddExtension = (string.IsNullOrWhiteSpace(Header.AddExtension) || Header.AddExtension.StartsWith(".") Header!.AddExtension = (string.IsNullOrWhiteSpace(Header.AddExtension) || Header.AddExtension.StartsWith(".")
? Header.AddExtension ? Header.AddExtension
: $".{Header.AddExtension}"); : $".{Header.AddExtension}");
Header.ReplaceExtension = (string.IsNullOrWhiteSpace(Header.ReplaceExtension) || Header.ReplaceExtension.StartsWith(".") Header.ReplaceExtension = (string.IsNullOrWhiteSpace(Header.ReplaceExtension) || Header.ReplaceExtension.StartsWith(".")
@@ -114,14 +114,14 @@ namespace SabreTools.Features
{ {
Header.Name = (updateMode != 0 ? "DiffDAT" : "MergeDAT") Header.Name = (updateMode != 0 ? "DiffDAT" : "MergeDAT")
+ (Header.Type == "SuperDAT" ? "-SuperDAT" : string.Empty) + (Header.Type == "SuperDAT" ? "-SuperDAT" : string.Empty)
+ (Cleaner.DedupeRoms != DedupeType.None ? "-deduped" : string.Empty); + (Cleaner!.DedupeRoms != DedupeType.None ? "-deduped" : string.Empty);
} }
if (string.IsNullOrWhiteSpace(Header.Description)) if (string.IsNullOrWhiteSpace(Header.Description))
{ {
Header.Description = (updateMode != 0 ? "DiffDAT" : "MergeDAT") Header.Description = (updateMode != 0 ? "DiffDAT" : "MergeDAT")
+ (Header.Type == "SuperDAT" ? "-SuperDAT" : string.Empty) + (Header.Type == "SuperDAT" ? "-SuperDAT" : string.Empty)
+ (Cleaner.DedupeRoms != DedupeType.None ? " - deduped" : string.Empty); + (Cleaner!.DedupeRoms != DedupeType.None ? " - deduped" : string.Empty);
if (!GetBoolean(features, NoAutomaticDateValue)) if (!GetBoolean(features, NoAutomaticDateValue))
Header.Description += $" ({Header.Date})"; Header.Description += $" ({Header.Date})";
@@ -149,7 +149,7 @@ namespace SabreTools.Features
List<ParentablePath> basePaths = PathTool.GetFilesOnly(GetList(features, BaseDatListValue)); List<ParentablePath> basePaths = PathTool.GetFilesOnly(GetList(features, BaseDatListValue));
// Ensure the output directory // Ensure the output directory
OutputDir = OutputDir.Ensure(); OutputDir = OutputDir?.Ensure();
// If we're in standard update mode, run through all of the inputs // If we're in standard update mode, run through all of the inputs
if (updateMode == UpdateMode.None) if (updateMode == UpdateMode.None)
@@ -172,14 +172,14 @@ namespace SabreTools.Features
|| datFile.Header.DatFormat.HasFlag(DatFormat.SSV)); || datFile.Header.DatFormat.HasFlag(DatFormat.SSV));
// Perform additional processing steps // Perform additional processing steps
Extras.ApplyExtras(datFile); Extras!.ApplyExtras(datFile);
Splitter.ApplySplitting(datFile, useTags: false); Splitter!.ApplySplitting(datFile, useTags: false);
datFile.ExecuteFilters(FilterRunner); datFile.ExecuteFilters(FilterRunner!);
Cleaner.ApplyCleaning(datFile); Cleaner!.ApplyCleaning(datFile);
Remover.ApplyRemovals(datFile); Remover!.ApplyRemovals(datFile);
// Get the correct output path // Get the correct output path
string realOutDir = inputPath.GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); string realOutDir = inputPath.GetOutputPath(OutputDir, GetBoolean(features, InplaceValue))!;
// Try to output the file, overwriting only if it's not in the current directory // Try to output the file, overwriting only if it's not in the current directory
Writer.Write(datFile, realOutDir, overwrite: GetBoolean(features, InplaceValue)); Writer.Write(datFile, realOutDir, overwrite: GetBoolean(features, InplaceValue));
@@ -215,11 +215,11 @@ namespace SabreTools.Features
datHeaders = DatFileTool.PopulateUserData(userInputDat, inputPaths); datHeaders = DatFileTool.PopulateUserData(userInputDat, inputPaths);
// Perform additional processing steps // Perform additional processing steps
Extras.ApplyExtras(userInputDat); Extras!.ApplyExtras(userInputDat);
Splitter.ApplySplitting(userInputDat, useTags: false); Splitter!.ApplySplitting(userInputDat, useTags: false);
userInputDat.ExecuteFilters(FilterRunner); userInputDat.ExecuteFilters(FilterRunner!);
Cleaner.ApplyCleaning(userInputDat); Cleaner!.ApplyCleaning(userInputDat);
Remover.ApplyRemovals(userInputDat); Remover!.ApplyRemovals(userInputDat);
// Output only DatItems that are duplicated across inputs // Output only DatItems that are duplicated across inputs
if (updateMode.HasFlag(UpdateMode.DiffDupesOnly)) if (updateMode.HasFlag(UpdateMode.DiffDupesOnly))
@@ -258,7 +258,7 @@ namespace SabreTools.Features
for (int j = 0; j < inputPaths.Count; j++) for (int j = 0; j < inputPaths.Count; j++)
#endif #endif
{ {
string path = inputPaths[j].GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); string path = inputPaths[j].GetOutputPath(OutputDir, GetBoolean(features, InplaceValue))!;
// Try to output the file // Try to output the file
Writer.Write(datFiles[j], path, overwrite: GetBoolean(features, InplaceValue)); Writer.Write(datFiles[j], path, overwrite: GetBoolean(features, InplaceValue));
@@ -314,7 +314,7 @@ namespace SabreTools.Features
for (int j = startIndex; j < inputPaths.Count; j++) for (int j = startIndex; j < inputPaths.Count; j++)
#endif #endif
{ {
string path = inputPaths[j].GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); string path = inputPaths[j].GetOutputPath(OutputDir, GetBoolean(features, InplaceValue))!;
// Try to output the file // Try to output the file
Writer.Write(datFiles[j], path, overwrite: GetBoolean(features, InplaceValue)); Writer.Write(datFiles[j], path, overwrite: GetBoolean(features, InplaceValue));
@@ -346,7 +346,7 @@ namespace SabreTools.Features
// Perform additional processing steps // Perform additional processing steps
Extras.ApplyExtras(repDat); Extras.ApplyExtras(repDat);
Splitter.ApplySplitting(repDat, useTags: false); Splitter.ApplySplitting(repDat, useTags: false);
repDat.ExecuteFilters(FilterRunner); repDat.ExecuteFilters(FilterRunner!);
Cleaner.ApplyCleaning(repDat); Cleaner.ApplyCleaning(repDat);
Remover.ApplyRemovals(repDat); Remover.ApplyRemovals(repDat);
@@ -354,7 +354,7 @@ namespace SabreTools.Features
DatFileTool.DiffAgainst(userInputDat, repDat, GetBoolean(Features, ByGameValue)); DatFileTool.DiffAgainst(userInputDat, repDat, GetBoolean(Features, ByGameValue));
// Finally output the diffed DatFile // Finally output the diffed DatFile
string interOutDir = inputPath.GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); string interOutDir = inputPath.GetOutputPath(OutputDir, GetBoolean(features, InplaceValue))!;
Writer.Write(repDat, interOutDir, overwrite: GetBoolean(features, InplaceValue)); Writer.Write(repDat, interOutDir, overwrite: GetBoolean(features, InplaceValue));
#if NET40_OR_GREATER || NETCOREAPP #if NET40_OR_GREATER || NETCOREAPP
}); });
@@ -382,7 +382,7 @@ namespace SabreTools.Features
// Perform additional processing steps // Perform additional processing steps
Extras.ApplyExtras(repDat); Extras.ApplyExtras(repDat);
Splitter.ApplySplitting(repDat, useTags: false); Splitter.ApplySplitting(repDat, useTags: false);
repDat.ExecuteFilters(FilterRunner); repDat.ExecuteFilters(FilterRunner!);
Cleaner.ApplyCleaning(repDat); Cleaner.ApplyCleaning(repDat);
Remover.ApplyRemovals(repDat); Remover.ApplyRemovals(repDat);
@@ -395,7 +395,7 @@ namespace SabreTools.Features
GetBoolean(features, OnlySameValue)); GetBoolean(features, OnlySameValue));
// Finally output the replaced DatFile // Finally output the replaced DatFile
string interOutDir = inputPath.GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); string interOutDir = inputPath.GetOutputPath(OutputDir, GetBoolean(features, InplaceValue))!;
Writer.Write(repDat, interOutDir, overwrite: GetBoolean(features, InplaceValue)); Writer.Write(repDat, interOutDir, overwrite: GetBoolean(features, InplaceValue));
#if NET40_OR_GREATER || NETCOREAPP #if NET40_OR_GREATER || NETCOREAPP
}); });

View File

@@ -16,7 +16,7 @@ namespace SabreTools.Features
public Verify() public Verify()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "ve", "verify" }; Flags = ["ve", "verify"];
Description = "Verify a folder against DATs"; Description = "Verify a folder against DATs";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = "When used, this will use an input DAT or set of DATs to blindly check against an input folder. The base of the folder is considered the base for the combined DATs and games are either the directories or archives within. This will only do a direct verification of the items within and will create a fixdat afterwards for missing files."; LongDescription = "When used, this will use an input DAT or set of DATs to blindly check against an input folder. The base of the folder is considered the base for the combined DATs and games are either the directories or archives within. This will only do a direct verification of the items within and will create a fixdat afterwards for missing files.";
@@ -103,7 +103,7 @@ namespace SabreTools.Features
// Otherwise, process all DATs into the same output // Otherwise, process all DATs into the same output
else else
{ {
InternalStopwatch watch = new("Populating internal DAT"); var watch = new InternalStopwatch("Populating internal DAT");
// Add all of the input DATs into one huge internal DAT // Add all of the input DATs into one huge internal DAT
DatFile datdata = DatFile.Create(); DatFile datdata = DatFile.Create();

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using SabreTools.Core; using SabreTools.Core;
using SabreTools.Help; using SabreTools.Help;
@@ -12,7 +11,7 @@ namespace SabreTools.Features
public Version() public Version()
{ {
Name = Value; Name = Value;
Flags = new List<string>() { "v", "version" }; Flags = ["v", "version"];
Description = "Prints version"; Description = "Prints version";
_featureType = ParameterType.Flag; _featureType = ParameterType.Flag;
LongDescription = "Prints current program version."; LongDescription = "Prints current program version.";

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using SabreTools.Core; using SabreTools.Core;
using SabreTools.Features; using SabreTools.Features;
using SabreTools.Help; using SabreTools.Help;
@@ -17,7 +16,7 @@ namespace SabreTools
/// <summary> /// <summary>
/// Help object that determines available functionality /// Help object that determines available functionality
/// </summary> /// </summary>
private static FeatureSet _help; private static FeatureSet? _help;
/// <summary> /// <summary>
/// Logging object /// Logging object
@@ -43,7 +42,7 @@ namespace SabreTools
_help = RetrieveHelp(); _help = RetrieveHelp();
// Credits take precidence over all // Credits take precidence over all
if ((new List<string>(args)).Contains("--credits")) if (new List<string>(args).Contains("--credits"))
{ {
FeatureSet.OutputCredits(); FeatureSet.OutputCredits();
LoggerImpl.Close(); LoggerImpl.Close();
@@ -81,7 +80,7 @@ namespace SabreTools
featureName = _help.GetFeatureName(featureName); featureName = _help.GetFeatureName(featureName);
// Get the associated feature // Get the associated feature
BaseFeature feature = _help[featureName] as BaseFeature; BaseFeature feature = (_help[featureName] as BaseFeature)!;
// If we had the help feature first // If we had the help feature first
if (featureName == DisplayHelp.Value || featureName == DisplayHelpDetailed.Value) if (featureName == DisplayHelp.Value || featureName == DisplayHelpDetailed.Value)
@@ -161,16 +160,16 @@ namespace SabreTools
{ {
// Create and add the header to the Help object // Create and add the header to the Help object
string barrier = "-----------------------------------------"; string barrier = "-----------------------------------------";
List<string> helpHeader = new() List<string> helpHeader =
{ [
"SabreTools - Manipulate, convert, and use DAT files", "SabreTools - Manipulate, convert, and use DAT files",
barrier, barrier,
"Usage: SabreTools [option] [flags] [filename|dirname] ...", "Usage: SabreTools [option] [flags] [filename|dirname] ...",
string.Empty string.Empty
}; ];
// Create the base help object with header // Create the base help object with header
FeatureSet help = new(helpHeader); var help = new FeatureSet(helpHeader);
// Add all of the features // Add all of the features
help.Add(new DisplayHelp()); help.Add(new DisplayHelp());
@@ -198,7 +197,7 @@ namespace SabreTools
if (inputs.Count == 0) if (inputs.Count == 0)
{ {
logger.Error("This feature requires at least one input"); logger.Error("This feature requires at least one input");
_help.OutputIndividualFeature(feature.Name); _help?.OutputIndividualFeature(feature.Name);
Environment.Exit(0); Environment.Exit(0);
} }
} }

View File

@@ -1,14 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks> <TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.1.2</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors> <Authors>Matt Nadareski</Authors>
<Copyright>Copyright (c)2016-2024 Matt Nadareski</Copyright> <Copyright>Copyright (c)2016-2024 Matt Nadareski</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/SabreTools/SabreTools</RepositoryUrl> <RepositoryUrl>https://github.com/SabreTools/SabreTools</RepositoryUrl>
</PropertyGroup> </PropertyGroup>