Skip to content

Commit

Permalink
Add BruteforceHashFileNamesAsync
Browse files Browse the repository at this point in the history
  • Loading branch information
BigBang1112 committed Dec 8, 2024
1 parent 3eb67a0 commit 836e46c
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 5 deletions.
102 changes: 98 additions & 4 deletions Src/GBX.NET.PAK/Pak.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,18 +19,27 @@ 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;

public int Version { get; }
public int Flags { get; }
public ImmutableDictionary<string, PakFile> Files { get; }

private Pak(Stream stream, byte[] key, int version, int metadataStart, int dataStart, int flags, ImmutableDictionary<string, PakFile> files)
private Pak(Stream stream,
byte[] key,
int version,
byte[] headerMD5,
int metadataStart,
int dataStart,
int flags,
ImmutableDictionary<string, PakFile> files)
{
this.stream = stream;
this.key = key;
Version = version;
this.headerMD5 = headerMD5;
this.metadataStart = metadataStart;
this.dataStart = dataStart;
Flags = flags;
Expand All @@ -54,6 +64,12 @@ public static async Task<Pak> ParseAsync(Stream stream, byte[] key, Cancellation
return await ParseEncryptedAsync(decryptReader, stream, version, key, cancellationToken);
}

public static async Task<Pak> 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<Pak> ParseEncryptedAsync(
GbxReader r,
Stream originalStream,
Expand Down Expand Up @@ -88,7 +104,7 @@ private static async Task<Pak> 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)
Expand Down Expand Up @@ -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);
Expand All @@ -184,6 +203,13 @@ public async Task<Gbx> 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();
Expand All @@ -195,4 +221,72 @@ public async ValueTask DisposeAsync()
await stream.DisposeAsync();
}
#endif

public static async Task<Dictionary<string, string>> BruteforceHashFileNamesAsync(
string directoryPath,
IProgress<KeyValuePair<string, string>>? progress = null,
CancellationToken cancellationToken = default)
{
var pakList = PakList.Parse(Path.Combine(directoryPath, "packlist.dat"));

var hashFileNames = new Dictionary<string, string>();

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<string, string>(hash, filePath));
hashFileNames[hash] = filePath;

while (filePath.Contains('\\'))
{
filePath = filePath.Substring(filePath.IndexOf('\\') + 1);
hash = MD5.Compute136(filePath);
progress?.Report(new KeyValuePair<string, string>(hash, filePath));
hashFileNames[hash] = filePath;
}
}
}
}

return hashFileNames;
}
}
6 changes: 5 additions & 1 deletion Tools/PakToZip/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);

Expand Down

0 comments on commit 836e46c

Please sign in to comment.