diff --git a/SabreTools.FileTypes/Archives/ZipArchive.cs b/SabreTools.FileTypes/Archives/ZipArchive.cs index 32c782cf..6b8a004d 100644 --- a/SabreTools.FileTypes/Archives/ZipArchive.cs +++ b/SabreTools.FileTypes/Archives/ZipArchive.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.IO; +#if NET452_OR_GREATER || NETCOREAPP +using System.IO.Compression; +#endif using System.Linq; using Compress; using Compress.ZipFile; @@ -77,6 +80,7 @@ namespace SabreTools.FileTypes.Archives // Create the temp directory Directory.CreateDirectory(outDir); +#if NET20 || NET35 || NET40 // Extract all files to the temp directory var zf = new Zip(); ZipReturn zr = zf.ZipFileOpen(this.Filename!, -1, true); @@ -85,23 +89,26 @@ namespace SabreTools.FileTypes.Archives for (int i = 0; i < zf.LocalFilesCount() && zr == ZipReturn.ZipGood; i++) { + // Get the entry + var entry = zf.GetLocalFile(i); + // Open the read stream zr = zf.ZipFileOpenReadStream(i, false, out Stream? readStream, out ulong streamsize, out ushort cm); - // Create the rest of the path, if needed - if (!string.IsNullOrEmpty(Path.GetDirectoryName(zf.GetLocalFile(i).Filename))) - Directory.CreateDirectory(Path.Combine(outDir, Path.GetDirectoryName(zf.GetLocalFile(i).Filename)!)); - // If the entry ends with a directory separator, continue to the next item, if any - if (zf.GetLocalFile(i).Filename!.EndsWith(Path.DirectorySeparatorChar.ToString()) - || zf.GetLocalFile(i).Filename!.EndsWith(Path.AltDirectorySeparatorChar.ToString()) - || zf.GetLocalFile(i).Filename!.EndsWith(Path.PathSeparator.ToString())) + if (entry.Filename!.EndsWith(Path.DirectorySeparatorChar.ToString()) + || entry.Filename!.EndsWith(Path.AltDirectorySeparatorChar.ToString()) + || entry.Filename!.EndsWith(Path.PathSeparator.ToString())) { zf.ZipFileCloseReadStream(); continue; } - FileStream writeStream = File.Create(Path.Combine(outDir, zf.GetLocalFile(i).Filename!)); + // Create the rest of the path, if needed + if (!string.IsNullOrEmpty(Path.GetDirectoryName(entry.Filename))) + Directory.CreateDirectory(Path.Combine(outDir, Path.GetDirectoryName(entry.Filename)!)); + + FileStream writeStream = File.Create(Path.Combine(outDir, entry.Filename!)); // If the stream is smaller than the buffer, just run one loop through to avoid issues if (streamsize < _bufferSize) @@ -130,6 +137,39 @@ namespace SabreTools.FileTypes.Archives zf.ZipFileClose(); encounteredErrors = false; +#else + // Extract all files to the temp directory + var zf = ZipFile.OpenRead(this.Filename); + if (zf == null) + throw new Exception($"Could not open {Filename} as a zip file"); + + for (int i = 0; i < zf.Entries.Count; i++) + { + // Get the entry + var entry = zf.Entries[i]; + var readStream = entry.Open(); + + // If the entry ends with a directory separator, continue to the next item, if any + if (entry.FullName.EndsWith(Path.DirectorySeparatorChar.ToString()) + || entry.FullName.EndsWith(Path.AltDirectorySeparatorChar.ToString()) + || entry.FullName.EndsWith(Path.PathSeparator.ToString())) + { + readStream.Dispose(); + continue; + } + + // Create the rest of the path, if needed + if (!string.IsNullOrEmpty(Path.GetDirectoryName(entry.FullName))) + Directory.CreateDirectory(Path.Combine(outDir, Path.GetDirectoryName(entry.FullName)!)); + + // Extract the file to the output directory + entry.ExtractToFile(Path.Combine(outDir, entry.FullName)); + readStream.Dispose(); + } + + zf.Dispose(); + encounteredErrors = false; +#endif } catch (EndOfStreamException ex) { @@ -204,6 +244,7 @@ namespace SabreTools.FileTypes.Archives Stream? stream = null; string? realEntry = null; +#if NET20 || NET35 || NET40 var zf = new Zip(); ZipReturn zr = zf.ZipFileOpen(this.Filename!, -1, true); if (zr != ZipReturn.ZipGood) @@ -234,6 +275,41 @@ namespace SabreTools.FileTypes.Archives zf.ZipFileClose(); return (stream, realEntry); +#else + var zf = ZipFile.OpenRead(this.Filename); + if (zf == null) + throw new Exception($"Could not open {Filename} as a zip file"); + + for (int i = 0; i < zf.Entries.Count; i++) + { + // Get the entry + var entry = zf.Entries[i]; ; + + // Skip invalid entries + if (entry.FullName == null) + continue; + + // Skip directory entries + if (entry.FullName.EndsWith(Path.DirectorySeparatorChar.ToString()) + || entry.FullName.EndsWith(Path.AltDirectorySeparatorChar.ToString()) + || entry.FullName.EndsWith(Path.PathSeparator.ToString())) + { + continue; + } + + // Skip non-matching keys + if (!entry.FullName.Contains(entryName)) + continue; + + // Open the entry stream + realEntry = entry.FullName; + stream = entry.Open(); + break; + } + + zf.Dispose(); + return (stream, realEntry); +#endif } catch (Exception ex) { @@ -249,11 +325,16 @@ namespace SabreTools.FileTypes.Archives /// public override List? GetChildren() { + // If we have an invalid file + if (this.Filename == null) + return null; + var found = new List(); string? gamename = Path.GetFileNameWithoutExtension(this.Filename); try { +#if NET20 || NET35 || NET40 var zf = new Zip(); ZipReturn zr = zf.ZipFileOpen(this.Filename!, -1, true); if (zr != ZipReturn.ZipGood) @@ -261,51 +342,115 @@ namespace SabreTools.FileTypes.Archives for (int i = 0; i < zf.LocalFilesCount(); i++) { + // Get the local file + var localFile = zf.GetLocalFile(i); + if (localFile == null) + continue; + // If the entry is a directory (or looks like a directory), we don't want to open it - if (zf.GetLocalFile(i).IsDirectory - || zf.GetLocalFile(i).Filename!.EndsWith(Path.DirectorySeparatorChar.ToString()) - || zf.GetLocalFile(i).Filename!.EndsWith(Path.AltDirectorySeparatorChar.ToString()) - || zf.GetLocalFile(i).Filename!.EndsWith(Path.PathSeparator.ToString())) + if (localFile.IsDirectory + || localFile.Filename!.EndsWith(Path.DirectorySeparatorChar.ToString()) + || localFile.Filename!.EndsWith(Path.AltDirectorySeparatorChar.ToString()) + || localFile.Filename!.EndsWith(Path.PathSeparator.ToString())) { continue; } // Open the read stream - zr = zf.ZipFileOpenReadStream(i, false, out Stream? readStream, out ulong streamsize, out ushort cm); + zr = zf.ZipFileOpenReadStream(i, false, out Stream? readStream, out _, out _); // If we get a read error, log it and continue - if (zr != ZipReturn.ZipGood) + if (zr != ZipReturn.ZipGood || readStream == null) { logger.Warning($"An error occurred while reading archive {this.Filename}: Zip Error - {zr}"); - zr = zf.ZipFileCloseReadStream(); continue; } // Create a blank item for the entry - BaseFile zipEntryRom = new(); + var zipEntryRom = new BaseFile(); // Perform a quickscan, if flagged to if (this.AvailableHashTypes.Length == 1 && this.AvailableHashTypes[0] == HashType.CRC32) { - zipEntryRom.Size = (long)zf.GetLocalFile(i).UncompressedSize; - zipEntryRom.CRC = zf.GetLocalFile(i).CRC; + zipEntryRom.Size = (long)localFile.UncompressedSize; + zipEntryRom.CRC = localFile.CRC; } // Otherwise, use the stream directly else { - zipEntryRom = GetInfo(readStream, size: (long)zf.GetLocalFile(i).UncompressedSize, hashes: this.AvailableHashTypes, keepReadOpen: true); + zipEntryRom = GetInfo(readStream, + size: (long)localFile.UncompressedSize, + hashes: this.AvailableHashTypes, + keepReadOpen: true); } - // Fill in comon details and add to the list - zipEntryRom.Filename = zf.GetLocalFile(i).Filename; + // Fill in common details and add to the list + zipEntryRom.Filename = localFile.Filename; zipEntryRom.Parent = gamename; - zipEntryRom.Date = zf.GetLocalFile(i).LastModified.ToString("yyyy/MM/dd hh:mm:ss"); + zipEntryRom.Date = localFile.LastModified.ToString("yyyy/MM/dd hh:mm:ss"); found.Add(zipEntryRom); } // Dispose of the archive zr = zf.ZipFileCloseReadStream(); zf.ZipFileClose(); +#else + var zf = ZipFile.OpenRead(this.Filename); + if (zf == null) + throw new Exception($"Could not open {Filename} as a zip file"); + + for (int i = 0; i < zf.Entries.Count; i++) + { + // Get the local file + var localFile = zf.Entries[i]; + if (localFile == null) + continue; + + // If the entry is a directory (or looks like a directory), we don't want to open it + if (localFile.FullName!.EndsWith(Path.DirectorySeparatorChar.ToString()) + || localFile.FullName!.EndsWith(Path.AltDirectorySeparatorChar.ToString()) + || localFile.FullName!.EndsWith(Path.PathSeparator.ToString())) + { + continue; + } + + // Open the read stream + var readStream = localFile.Open(); + if (readStream == null) + continue; + + // Create a blank item for the entry + var zipEntryRom = new BaseFile(); + + // Perform a quickscan, if flagged to + if (this.AvailableHashTypes.Length == 1 && this.AvailableHashTypes[0] == HashType.CRC32) + { + zipEntryRom.Size = localFile.Length; +#if NETCOREAPP + zipEntryRom.CRC = BitConverter.GetBytes(localFile.Crc32); +#else + // TODO: Figure out how to get the CRC from the header +#endif + } + // Otherwise, use the stream directly + else + { + zipEntryRom = GetInfo(readStream, + size: localFile.Length, + hashes: this.AvailableHashTypes, + keepReadOpen: true); + } + + // Fill in common details and add to the list + zipEntryRom.Filename = localFile.FullName; + zipEntryRom.Parent = gamename; + zipEntryRom.Date = localFile.LastWriteTime.ToString("yyyy/MM/dd hh:mm:ss"); + found.Add(zipEntryRom); + } + + // Dispose of the archive + zf.Dispose(); +#endif } catch (Exception ex) { @@ -319,10 +464,15 @@ namespace SabreTools.FileTypes.Archives /// public override List GetEmptyFolders() { + // If we have an invalid file + if (this.Filename == null) + return []; + List empties = []; try { +#if NET20 || NET35 || NET40 var zf = new Zip(); ZipReturn zr = zf.ZipFileOpen(this.Filename!, -1, true); if (zr != ZipReturn.ZipGood) @@ -352,6 +502,50 @@ namespace SabreTools.FileTypes.Archives lastZipEntry = entry.Item1; } } +#else + var zf = ZipFile.OpenRead(this.Filename); + if (zf == null) + throw new Exception($"Could not open {Filename} as a zip file"); + + var zipEntries = new List<(string, bool)>(); + for (int i = 0; i < zf.Entries.Count; i++) + { + // Get the local file + var entry = zf.Entries[i]; + if (entry == null) + continue; + + // If the entry is a directory (or looks like a directory) + bool isDirectory = false; + if (entry.FullName!.EndsWith(Path.DirectorySeparatorChar.ToString()) + || entry.FullName!.EndsWith(Path.AltDirectorySeparatorChar.ToString()) + || entry.FullName!.EndsWith(Path.PathSeparator.ToString())) + { + isDirectory = true; + } + + zipEntries.Add((entry.FullName, isDirectory)); + } + + zipEntries = zipEntries.OrderBy(p => p.Item1, new NaturalReversedComparer()).ToList(); + string? lastZipEntry = null; + foreach ((string, bool) entry in zipEntries) + { + // If the current is a superset of last, we skip it + if (lastZipEntry != null && lastZipEntry.StartsWith(entry.Item1)) + { + // No-op + } + // If the entry is a directory, we add it + else + { + if (entry.Item2) + empties.Add(entry.Item1); + + lastZipEntry = entry.Item1; + } + } +#endif } catch (Exception ex) { diff --git a/SabreTools.FileTypes/SabreTools.FileTypes.csproj b/SabreTools.FileTypes/SabreTools.FileTypes.csproj index 96ff2295..d856a8b3 100644 --- a/SabreTools.FileTypes/SabreTools.FileTypes.csproj +++ b/SabreTools.FileTypes/SabreTools.FileTypes.csproj @@ -26,6 +26,9 @@ + + +