diff --git a/src/addoncreator/Addon/AddonFile.cs b/src/addoncreator/Addon/AddonFile.cs index ab1a5b7..6b5304a 100644 --- a/src/addoncreator/Addon/AddonFile.cs +++ b/src/addoncreator/Addon/AddonFile.cs @@ -3,9 +3,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using GarrysMod.AddonCreator.Hashing; using Newtonsoft.Json; +using TagLib; +using File = System.IO.File; namespace GarrysMod.AddonCreator.Addon { @@ -24,6 +27,7 @@ namespace GarrysMod.AddonCreator.Addon Files = new Dictionary(); RequiredContent = new List(); Version = 1; + MinimizeMedia = true; } /// @@ -67,6 +71,16 @@ namespace GarrysMod.AddonCreator.Addon /// public List RequiredContent { get; private set; } + /// + /// Indicates whether Lua files will have comments and unnecessary whitespace stripped out on export. + /// + public bool MinimizeLua { get; set; } + + /// + /// Indicates whether media files will have all game-irrelevant information (like ID3 tags) stripped out on export. + /// + public bool MinimizeMedia { get; set; } + /// /// Imports a gmod addon into this instance. /// @@ -85,8 +99,8 @@ namespace GarrysMod.AddonCreator.Addon throw new FormatException(); #if DEBUG - // Check addon's CRC32 hash - // TODO: Garry's code actually calculates CRC32 hashes differently in edge cases. The code used for it is from zlib-1.1.3. See https://github.com/garrynewman/bootil/blob/master/src/3rdParty/smhasher/crc.cpp#L4. + // Check addon's CRC32 hash + // TODO: Garry's code actually calculates CRC32 hashes differently in edge cases. The code used for it is from zlib-1.1.3. See https://github.com/garrynewman/bootil/blob/master/src/3rdParty/smhasher/crc.cpp#L4. { Debug.WriteLine("Checking CRC32..."); var baseAddon = new byte[stream.Length - sizeof (int)]; @@ -153,7 +167,7 @@ namespace GarrysMod.AddonCreator.Addon var fileSize = sr.ReadInt64(); var fileHash = sr.ReadInt32(); - Debug.WriteLine("\t#{2} : {0} ({1:0.0} kB)", filePath, (double)fileSize/1024, fileId); + Debug.WriteLine("\t#{2} : {0} ({1:0.0} kB)", filePath, (double) fileSize/1024, fileId); Debug.Assert(fileId == expectedFileId); expectedFileId++; @@ -177,7 +191,7 @@ namespace GarrysMod.AddonCreator.Addon var fileHash = file.Value.Item2; var filePosition = sr.BaseStream.Position; - Debug.WriteLine("Analyzing: {0} ({1:0.00} kB)", filePath, (double)fileSize / 1024); + Debug.WriteLine("Analyzing: {0} ({1:0.00} kB)", filePath, (double) fileSize/1024); var fileContent = new byte[fileSize]; @@ -226,12 +240,26 @@ namespace GarrysMod.AddonCreator.Addon if (MinimizeLua) files = files - // minimize lua code .Select(f => f.Key.EndsWith(".lua", StringComparison.OrdinalIgnoreCase) ? new KeyValuePair(f.Key, new MinifiedLuaAddonFileInfo(f.Value)) : f) .ToDictionary(i => i.Key, i => i.Value); + files = files + .Select(f => Assembly.LoadFrom("taglib-sharp.dll") + .GetTypes() + .Where(t => t.IsSubclassOf(typeof (TagLib.File))) + .Any(mediaSupport => mediaSupport.GetCustomAttributes(typeof (SupportedMimeType), false) + .Select(t => t as SupportedMimeType) + .Any(mediaSupportExt => mediaSupportExt.Extension != null + && f.Key.ToLower().EndsWith("." + + (mediaSupportExt.Extension.TrimStart('.') + .ToLower())))) + ? new KeyValuePair(f.Key, + new MinifiedMediaAddonFileInfo(f.Value, f.Key.Split('.').Last()) {StripTags = MinimizeMedia}) + : f) + .ToDictionary(i => i.Key, i => i.Value); + // Check for errors and ignores in addon.json var addonJson = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(Files["addon.json"].GetContents())); @@ -311,7 +339,8 @@ namespace GarrysMod.AddonCreator.Addon uint fileNum = 0; foreach (var file in resultingFiles) { - Console.WriteLine("Processing: {0} ({1:0.00} kB)", file.Key, (double)file.Value.Size / 1024); + Console.Write("Processing: {0}", file.Key); + Console.WriteLine(" ({0:0.00} kB)", (double) file.Value.Size/1024); fileNum++; sw.Write(fileNum); @@ -322,14 +351,41 @@ namespace GarrysMod.AddonCreator.Addon sw.Write((uint) 0); // End of file list // File contents - foreach (var file in resultingFiles) + foreach (var file in resultingFiles + .Where(file => file.Value.Size != 0)) { - if (file.Value.Size == 0) - continue; - +#if DEBUG + Console.WriteLine("Packing: {0} ({1})", file.Key, + file.Value.GetType().Name.Replace("AddonFileInfo", "")); +#else Console.WriteLine("Packing: {0}", file.Key); +#endif - sw.Write(file.Value.GetContents()); + byte[] contents; + try + { + contents = file.Value.GetContents(); + } + catch (CorruptFileException e) + { + var mediaFile = file.Value as MinifiedMediaAddonFileInfo; + if (mediaFile == null) + { + throw new Exception(); // what the fuck logic + } + + var oldColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + + Console.Error.WriteLine("Warning: File {0} possibly corrupted - {1}", file.Key, e.Message); + + Console.ForegroundColor = oldColor; + + mediaFile.IgnoreCorrupted = true; + contents = mediaFile.GetContents(); + } + + sw.Write(contents); } // Addon CRC @@ -343,10 +399,5 @@ namespace GarrysMod.AddonCreator.Addon } } } - - /// - /// Indicates whether Lua files will have comments and unnecessary whitespace stripped out on export. - /// - public bool MinimizeLua { get; set; } } } \ No newline at end of file diff --git a/src/addoncreator/Addon/MinifiedMediaAddonFileInfo.cs b/src/addoncreator/Addon/MinifiedMediaAddonFileInfo.cs new file mode 100644 index 0000000..f27e809 --- /dev/null +++ b/src/addoncreator/Addon/MinifiedMediaAddonFileInfo.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using TagLib; +using File = System.IO.File; + +namespace GarrysMod.AddonCreator.Addon +{ + /// + /// Represents a media addon file with the possibility of stripping tags and checking for corruptions. + /// + public class MinifiedMediaAddonFileInfo : AddonFileInfo + { + private readonly string _tempFile; + + private bool _processed; + + ~MinifiedMediaAddonFileInfo() + { + File.Delete(_tempFile); + } + + /// + /// Creates a new instance using the given addon file. + /// + /// The addon file, supposedly a media file + public MinifiedMediaAddonFileInfo(AddonFileInfo file, string extension) + { + _tempFile = Path.GetTempFileName(); + var dirName = Path.GetDirectoryName(_tempFile); + if (dirName == null) + throw new InvalidOperationException("Temporary directory is NULL"); + + // Fix extension, needed for TagLib to detect file format properly + var newTempFile = Path.Combine( + dirName, + Path.GetFileNameWithoutExtension(_tempFile) + "." + extension); + File.Move(_tempFile, newTempFile); + _tempFile = newTempFile; + + using (var s = new FileStream(_tempFile, FileMode.OpenOrCreate, FileAccess.Write)) + { + var buffer = file.GetContents(); + s.Write(buffer, 0, buffer.Length); + } + } + + /// + /// Indicates whether tags should be stripped or not. + /// + public bool StripTags { get; set; } + + /// + /// Indicates whether to ignore possible file corruption. + /// + public bool IgnoreCorrupted { get; set; } + + /// + /// Processes the media file, applies any wanted stripping and returns the new file contents. + /// + /// The contents of the media file with optional applied stripping + /// Will be thrown when TagLib detects possible media file corruptions + public override byte[] GetContents() + { + if (_processed) + { + using (var s = new FileStream(_tempFile, FileMode.Open, FileAccess.Read)) + { + var buffer = new byte[s.Length]; + s.Read(buffer, 0, buffer.Length); + return buffer; + } + } + + using (var tags = TagLib.File.Create(_tempFile)) + { + if (tags.PossiblyCorrupt && !IgnoreCorrupted) + { + throw new CorruptFileException(string.Join("; ", tags.CorruptionReasons)); + } + + if (StripTags) + { + tags.RemoveTags(TagTypes.AllTags); + } + + tags.Save(); + } + + _processed = true; + + // This will now return the file contents instead + return GetContents(); + } + } +} diff --git a/src/addoncreator/GarrysMod.AddonCreator.csproj b/src/addoncreator/GarrysMod.AddonCreator.csproj index 47b9196..7a29411 100644 --- a/src/addoncreator/GarrysMod.AddonCreator.csproj +++ b/src/addoncreator/GarrysMod.AddonCreator.csproj @@ -48,8 +48,14 @@ False $(SolutionDir)\packages\Newtonsoft.Json.6.0.5\lib\net40\Newtonsoft.Json.dll + + ..\..\packages\taglib.2.1.0.0\lib\policy.2.0.taglib-sharp.dll + + + ..\..\packages\taglib.2.1.0.0\lib\taglib-sharp.dll + @@ -57,6 +63,7 @@ + diff --git a/src/addoncreator/Program.cs b/src/addoncreator/Program.cs index 1495380..271b95e 100644 --- a/src/addoncreator/Program.cs +++ b/src/addoncreator/Program.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using GarrysMod.AddonCreator.Addon; namespace GarrysMod.AddonCreator @@ -12,6 +11,7 @@ namespace GarrysMod.AddonCreator private static void Main(string[] args) { var minimizeLua = false; + var minimizeMedia = true; while (args.Any()) { @@ -25,6 +25,14 @@ namespace GarrysMod.AddonCreator minimizeLua = false; args = args.Skip(1).ToArray(); break; + case "--minimize-media": + minimizeMedia = true; + args = args.Skip(1).ToArray(); + break; + case "--no-minimize-media": + minimizeMedia = false; + args = args.Skip(1).ToArray(); + break; case "create": { if (args.Length < 3) @@ -34,7 +42,7 @@ namespace GarrysMod.AddonCreator var folder = new DirectoryInfo(args[1]); var output = args[2]; - var addon = new AddonFile {MinimizeLua = minimizeLua}; + var addon = new AddonFile {MinimizeLua = minimizeLua, MinimizeMedia = minimizeMedia}; if (!folder.Exists) { @@ -143,7 +151,8 @@ namespace GarrysMod.AddonCreator } default: - Console.WriteLine("Usage: {0} ", Process.GetCurrentProcess().ProcessName); + Console.WriteLine("Usage: {0} ", + Process.GetCurrentProcess().ProcessName); Console.WriteLine(); Console.WriteLine("Commands:"); Console.WriteLine("\t{0}\t{1}", "extract", "Extracts a GMA file and shows information about it."); @@ -152,8 +161,14 @@ namespace GarrysMod.AddonCreator Console.WriteLine("\t\tArguments: Input folder path, output GMA file path"); Console.WriteLine(); Console.WriteLine("Options:"); - Console.WriteLine("\t{0}\t{1}", "--minimize-lua", "Causes exported GMAs to have all Lua comments and unneeded whitespace in Lua stripped out."); - Console.WriteLine("\t{0}\t{1}", "--no-minimize-lua", "(default) Will prevent Lua files getting minimized."); + Console.WriteLine("\t{0}\t{1}", "--minimize-lua", + "Causes exported GMAs to have all Lua comments and unneeded whitespace in Lua stripped out."); + Console.WriteLine("\t{0}\t{1}", "--no-minimize-lua", + "(default) Will prevent Lua files getting minimized."); + Console.WriteLine("\t{0}\t{1}", "--minimize-media", + "(default) Causes exported GMAs to have all media tags stripped out."); + Console.WriteLine("\t{0}\t{1}", "--no-minimize-media", + "Will prevent media files getting minimized."); Console.WriteLine(); return; } diff --git a/src/addoncreator/packages.config b/src/addoncreator/packages.config index 3dd77e6..abbd14a 100644 --- a/src/addoncreator/packages.config +++ b/src/addoncreator/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file