From 836e46c4d0d4ff49acced946dd0afad12c4b0cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Pivo=C5=88ka?= Date: Sun, 8 Dec 2024 20:55:05 +0100 Subject: [PATCH] Add BruteforceHashFileNamesAsync --- Src/GBX.NET.PAK/Pak.cs | 102 ++++++++++++++++++++++++++++++++++++-- Tools/PakToZip/Program.cs | 6 ++- 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/Src/GBX.NET.PAK/Pak.cs b/Src/GBX.NET.PAK/Pak.cs index 6f6fbb0a5..3da6b9471 100644 --- a/Src/GBX.NET.PAK/Pak.cs +++ b/Src/GBX.NET.PAK/Pak.cs @@ -1,4 +1,5 @@ -using GBX.NET.Exceptions; +using GBX.NET.Crypto; +using GBX.NET.Exceptions; using GBX.NET.Serialization; using System.Collections.Immutable; using System.IO.Compression; @@ -18,6 +19,7 @@ public sealed partial class Pak : IDisposable private readonly Stream stream; private readonly byte[] key; + private readonly byte[] headerMD5; private readonly int metadataStart; private readonly int dataStart; @@ -25,11 +27,19 @@ public sealed partial class Pak : IDisposable public int Flags { get; } public ImmutableDictionary Files { get; } - private Pak(Stream stream, byte[] key, int version, int metadataStart, int dataStart, int flags, ImmutableDictionary files) + private Pak(Stream stream, + byte[] key, + int version, + byte[] headerMD5, + int metadataStart, + int dataStart, + int flags, + ImmutableDictionary files) { this.stream = stream; this.key = key; Version = version; + this.headerMD5 = headerMD5; this.metadataStart = metadataStart; this.dataStart = dataStart; Flags = flags; @@ -54,6 +64,12 @@ public static async Task ParseAsync(Stream stream, byte[] key, Cancellation return await ParseEncryptedAsync(decryptReader, stream, version, key, cancellationToken); } + public static async Task ParseAsync(string filePath, byte[] key, CancellationToken cancellationToken = default) + { + var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true); + return await ParseAsync(fs, key, cancellationToken); + } + private static async Task ParseEncryptedAsync( GbxReader r, Stream originalStream, @@ -88,7 +104,7 @@ private static async Task ParseEncryptedAsync( var files = ReadAllFiles(r, allFolders); - return new Pak(originalStream, key, version, metadataStart, dataStart, flags, files); + return new Pak(originalStream, key, version, headerMD5, metadataStart, dataStart, flags, files); } private static PakFolder[] ReadAllFolders(GbxReader r) @@ -162,7 +178,10 @@ public Stream OpenFile(PakFile file, out EncryptionInitializer encryptionInitial stream.Position = dataStart + file.Offset; var ivBuffer = new byte[8]; - stream.Read(ivBuffer, 0, 8); + if (stream.Read(ivBuffer, 0, 8) != 8) + { + throw new EndOfStreamException("Could not read IV from file."); + } var iv = BitConverter.ToUInt64(ivBuffer, 0); var blowfish = new BlowfishStream(stream, key, iv); @@ -184,6 +203,13 @@ public async Task OpenGbxFileAsync(PakFile file, GbxReadSettings settings = return await Gbx.ParseAsync(stream, settings with { EncryptionInitializer = encryptionInitializer }, cancellationToken); } + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public Gbx OpenGbxFileHeader(PakFile file, GbxReadSettings settings = default) + { + using var stream = OpenFile(file, out var encryptionInitializer); + return Gbx.ParseHeader(stream, settings with { EncryptionInitializer = encryptionInitializer }); + } + public void Dispose() { stream.Dispose(); @@ -195,4 +221,72 @@ public async ValueTask DisposeAsync() await stream.DisposeAsync(); } #endif + + public static async Task> BruteforceHashFileNamesAsync( + string directoryPath, + IProgress>? progress = null, + CancellationToken cancellationToken = default) + { + var pakList = PakList.Parse(Path.Combine(directoryPath, "packlist.dat")); + + var hashFileNames = new Dictionary(); + + foreach (var pakInfo in pakList) + { + var fileName = $"{char.ToUpperInvariant(pakInfo.Key[0])}{pakInfo.Key.Substring(1)}.pak"; + var fullFileName = Path.Combine(directoryPath, fileName); + + if (!File.Exists(fullFileName)) + { + continue; + } + + using var pak = await ParseAsync(fullFileName, pakInfo.Value.Key, cancellationToken); + + foreach (var file in pak.Files.Values) + { + if (!file.Name.EndsWith(".gbx", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + cancellationToken.ThrowIfCancellationRequested(); + + Gbx gbx; + try + { + gbx = pak.OpenGbxFileHeader(file); + } + catch (NotAGbxException) + { + continue; + } + + var refTable = gbx.RefTable; + + if (refTable is null) + { + continue; + } + + foreach (var refTableFile in refTable.Files) + { + var filePath = refTableFile.FilePath; + var hash = MD5.Compute136(filePath); + progress?.Report(new KeyValuePair(hash, filePath)); + hashFileNames[hash] = filePath; + + while (filePath.Contains('\\')) + { + filePath = filePath.Substring(filePath.IndexOf('\\') + 1); + hash = MD5.Compute136(filePath); + progress?.Report(new KeyValuePair(hash, filePath)); + hashFileNames[hash] = filePath; + } + } + } + } + + return hashFileNames; + } } \ No newline at end of file diff --git a/Tools/PakToZip/Program.cs b/Tools/PakToZip/Program.cs index 38e14173e..0b5ead955 100644 --- a/Tools/PakToZip/Program.cs +++ b/Tools/PakToZip/Program.cs @@ -5,8 +5,11 @@ Gbx.ZLib = new ZLib(); var fileName = args[0]; +var directoryPath = Path.GetDirectoryName(fileName)!; -var packlistFileName = Path.Combine(Path.GetDirectoryName(fileName)!, "packlist.dat"); +var hashes = await Pak.BruteforceHashFileNamesAsync(directoryPath); + +var packlistFileName = Path.Combine(directoryPath, "packlist.dat"); var packlist = await PakList.ParseAsync(packlistFileName); var key = packlist[Path.GetFileNameWithoutExtension(fileName).ToLowerInvariant()].Key; @@ -15,6 +18,7 @@ using var pak = await Pak.ParseAsync(fs, key); var file = pak.Files.Values.First(x => x.Name == "B2FC497BF7F81AB02D01FE8FB2F707CD8F"); +var fileItemName = hashes[file.Name]; var gbx = await pak.OpenGbxFileAsync(file);