diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d6326c5..439ee46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,9 +22,6 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x - - - name: Restore dependencies - run: dotnet restore - name: Build run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c Release -p:DebugType=None -p:DebugSymbols=false @@ -34,3 +31,43 @@ jobs: with: name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }} path: ${{ matrix.project }}/bin/Release/${{ matrix.framework }}/${{ matrix.runtime }}/publish/* + + pack: + runs-on: windows-latest + + strategy: + matrix: + project: [LibIRD] + + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Setup NuGet + uses: NuGet/setup-nuget@v1 + with: + nuget-version: 'latest' + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Restore NuGet packages + run: nuget restore LibIRD/LibIRD.csproj + + - name: Build + run: msbuild ${{ matrix.project }}/${{ matrix.project }}.csproj /p:Configuration=Release + + - name: Pack + run: dotnet pack ${{ matrix.project }}/${{ matrix.project }}.csproj -c Release -o ./artifacts + + - name: Upload nupkg + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.project }} + path: ./artifacts/*.nupkg diff --git a/IRDKit/Program.cs b/IRDKit/Program.cs index b76cd91..371eff0 100644 --- a/IRDKit/Program.cs +++ b/IRDKit/Program.cs @@ -113,6 +113,19 @@ namespace IRDKit public bool Verbose { get; set; } } + /// + /// IRD verify command + /// + [Verb("verify", HelpText = "Verify a folder using an IRD")] + public class VerifyOptions + { + [Value(0, Required = true, HelpText = "Path to IRD file to use for validation")] + public string IRDPath { get; set; } + + [Value(1, Required = true, HelpText = "Folder path to validate")] + public string FolderPath { get; set; } + } + #endregion #region Program @@ -127,7 +140,7 @@ namespace IRDKit Console.OutputEncoding = Encoding.UTF8; // Parse arguments - var result = Parser.Default.ParseArguments(args).WithParsed(Run); + var result = Parser.Default.ParseArguments(args).WithParsed(Run); } /// @@ -452,6 +465,60 @@ namespace IRDKit break; + // Process options from a 'rename' command + case VerifyOptions opt: + + string verifyIRDPath = opt.IRDPath; + string verifyFolderPath = opt.FolderPath; + + // Validate required parameters + if (string.IsNullOrEmpty(verifyIRDPath)) + { + Console.Error.WriteLine("Provide a IRD path to verify with"); + return; + } + if (string.IsNullOrEmpty(verifyFolderPath)) + { + Console.Error.WriteLine("Provide a folder path to verify"); + return; + } + + bool switched = false; + if (!File.Exists(verifyIRDPath)) + { + // Check if arguments are switched + if (File.Exists(verifyFolderPath)) + { + switched = true; + string temp = verifyIRDPath; + verifyIRDPath = verifyFolderPath; + verifyFolderPath = temp; + } + else + { + Console.Error.WriteLine("Provide a valid IRD file to verify with"); + return; + } + } + + if (!Directory.Exists(verifyFolderPath)) + { + if (switched) + { + Console.Error.WriteLine("Provide a valid IRD file to verify with"); + return; + } + else + { + Console.Error.WriteLine("Provide a valid folder path to verify"); + return; + } + } + + VerifyIRD(verifyIRDPath, verifyFolderPath); + + break; + // Unknown command default: break; @@ -772,6 +839,32 @@ namespace IRDKit Console.WriteLine(printText.ToString()); } + /// + /// Verify a folder matches against an IRD + /// + /// IRD path to verify with + /// Folder containing files to verify against + public static void VerifyIRD(string irdPath, string folderPath) + { + // Get file paths and their hashes to verify with + var fileHashes = IRD.Read(irdPath).GetFileHashes(); + + // Check that all files and their hashes exist in the folder path + foreach (var kvp in fileHashes) + { + string filePath = Path.Combine(folderPath, kvp.Key); + if (!File.Exists(filePath)) + { + Console.WriteLine($"{filePath} doesn't exist"); + continue; + } + var hash = HashTool.GetFileHashArray(filePath, HashType.MD5); + if (!hash.SequenceEqual(kvp.Value)) + Console.WriteLine($"{filePath}: {IRD.ByteArrayToHexString(hash)}"); + } + Console.WriteLine("Verification complete!"); + } + /// /// Creates an IRD file from an ISO file /// diff --git a/LibIRD/IRD.cs b/LibIRD/IRD.cs index 86d83f9..975f382 100644 --- a/LibIRD/IRD.cs +++ b/LibIRD/IRD.cs @@ -1733,7 +1733,83 @@ namespace LibIRD } } -#endregion + #endregion + + #region IRD Validation + + /// + /// Returns dictionary of file paths and their offset + /// + /// CDReader object + public Dictionary GetFileHashes() + { + using MemoryStream headerStream = new MemoryStream(Header); + using GZipStream gzStream = new GZipStream(headerStream, CompressionMode.Decompress); + using MemoryStream isoStream = new MemoryStream(); +#if NETCOREAPP || NETSTANDARD || NET40_OR_GREATER + gzStream.CopyTo(isoStream); +#else + byte[] buffer = new byte[SectorSize]; + int bytesRead; + while ((bytesRead = gzStream.Read(buffer, 0, buffer.Length)) > 0) + { + isoStream.Write(buffer, 0, bytesRead); + } +#endif + isoStream.Position = 0; + using CDReader reader = new CDReader(isoStream); + + // Get all file paths and their offsets + var files = new Dictionary(); + DiscDirectoryInfo rootDir = reader.GetDirectoryInfo("\\"); + GetFileOffsets(reader, rootDir, files); + + // Get all paths at each offset + var offsetToHash = new Dictionary(); + for (int i = 0; i < FileCount; i++) + offsetToHash[FileKeys[i]] = FileHashes[i]; + + // Return dictionary of paths and hashes + var result = new Dictionary(); + foreach (KeyValuePair kvp in files) + { + if (offsetToHash.ContainsKey(kvp.Value)) + result[kvp.Key] = offsetToHash[kvp.Value]; + } + return result; + } + + /// + /// Gets all file paths from the ISO header + /// + /// CDReader object + /// Path to look within + /// Dictionary of files and their offset + public void GetFileOffsets(CDReader reader, DiscDirectoryInfo path, Dictionary files) + { + foreach (DiscFileInfo fileInfo in path.GetFiles()) + { + string filePath = fileInfo.FullName; + Range[] fileExtent = reader.PathToClusters(filePath); + if (fileExtent == null && fileExtent.Length == 0) + throw new IOException($"Unexpected file extents for {filePath}"); + long offset = fileExtent[0].Offset; + for (int i = 1; i < fileExtent.Length; i++) + { + if (fileExtent[i] == null) + throw new IOException($"Unexpected file extents for {filePath}"); + if (fileExtent[i].Offset < offset) + offset = fileExtent[i].Offset; + } + files[filePath] = offset; + } + foreach (DiscDirectoryInfo dirInfo in path.GetDirectories()) + { + GetFileOffsets(reader, dirInfo, files); + } + } + + #endregion #region Helper Functions diff --git a/LibIRD/LibIRD.csproj b/LibIRD/LibIRD.csproj index 0eac5fd..aa3a380 100644 --- a/LibIRD/LibIRD.csproj +++ b/LibIRD/LibIRD.csproj @@ -2,12 +2,12 @@ - win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64 + net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0 false latest true true - 0.9.4 + 0.9.5 ../nupkg @@ -24,14 +24,6 @@ GPL-3.0-only True - - - net6.0;net7.0;net8.0;net9.0 - - - - net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0 - @@ -44,7 +36,7 @@ - +