From ba0e89ded2220c9a2e611c488c4f9983d8e13c33 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Mon, 10 Dec 2018 22:29:11 -0500 Subject: [PATCH] Added globbing pattern support (Currently untested). --- Source/Nexus.Archive.Tests/UnitTest1.cs | 16 ++++++ Source/Nexus.Archive.sln | 2 +- Source/Nexus.Archive/ArchiveFileBase.cs | 54 ++++++++++----------- Source/Nexus.Archive/DataHeader.cs | 15 ++++-- Source/Nexus.Archive/IndexFile.cs | 51 +++++++++++++++++++ Source/Nexus.Archive/Nexus.Archive.csproj | 5 ++ Source/Nexus.Archive/RootIndexBlock.cs | 22 +++++++-- Source/TableExtractor/TableExtractor.csproj | 4 +- Source/TableExtractor/nuget.config | 9 ++++ 9 files changed, 141 insertions(+), 37 deletions(-) create mode 100644 Source/TableExtractor/nuget.config diff --git a/Source/Nexus.Archive.Tests/UnitTest1.cs b/Source/Nexus.Archive.Tests/UnitTest1.cs index 8637be3..7c577fe 100644 --- a/Source/Nexus.Archive.Tests/UnitTest1.cs +++ b/Source/Nexus.Archive.Tests/UnitTest1.cs @@ -39,7 +39,23 @@ public void FileLookupTest() var entry = enIndex.FindEntry("ClientDataEN\\en-US.bin"); Assert.NotNull(entry); } + [Fact] + public void FileTypeHeaderChecks() + { + var clientDataArchive = LoadArchiveFromPath(Path.Combine("Patch", "ClientData.archive"), true); + + } + private ArchiveFile LoadArchiveFromPath(string path, bool required = false) + { + var realPath = Path.Combine(GamePath, path); + if (!File.Exists(realPath)) + { + if (required) throw new FileNotFoundException("File not found", realPath); + return null; + } + return (ArchiveFile)ArchiveFileBase.FromFile(realPath); + } private IndexFile LoadIndexFromPath(string path, bool required = false) { var realPath = Path.Combine(GamePath, path); diff --git a/Source/Nexus.Archive.sln b/Source/Nexus.Archive.sln index 0fd66f0..163eaba 100644 --- a/Source/Nexus.Archive.sln +++ b/Source/Nexus.Archive.sln @@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nexus.Archive", "Nexus.Arch EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nexus.Archive.Tests", "Nexus.Archive.Tests\Nexus.Archive.Tests.csproj", "{758DE6FD-18C4-4153-B3B1-AA08D000C3C1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TableExtractor", "TableExtractor\TableExtractor.csproj", "{3985642C-5636-467A-A71D-3DB6B7712701}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TableExtractor", "TableExtractor\TableExtractor.csproj", "{3985642C-5636-467A-A71D-3DB6B7712701}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Source/Nexus.Archive/ArchiveFileBase.cs b/Source/Nexus.Archive/ArchiveFileBase.cs index 06774e3..6e041bc 100644 --- a/Source/Nexus.Archive/ArchiveFileBase.cs +++ b/Source/Nexus.Archive/ArchiveFileBase.cs @@ -12,29 +12,32 @@ namespace Nexus.Archive { public abstract class ArchiveFileBase : IDisposable { - private static readonly Dictionary TypeHandlers = - new Dictionary(); + private delegate ArchiveFileBase ArchiveFactory(string filePath, MemoryMappedFile file, ArchiveHeader header, BlockInfoHeader[] blockTable, RootIndexBlock rootBlock); + private static readonly Dictionary TypeHandlers = + new Dictionary(); static ArchiveFileBase() { - foreach (var type in typeof(ArchiveFileBase).Assembly.GetTypes() - .Where(i => typeof(ArchiveFileBase).IsAssignableFrom(i) && !i.IsAbstract)) - { - var attribute = type.GetCustomAttribute(); - if (attribute == null) continue; - var argumentTypes = new[] - { - typeof(string), typeof(MemoryMappedFile), typeof(ArchiveHeader), typeof(BlockInfoHeader[]), - typeof(RootIndexBlock) - }; - MemberInfo constructor = type.GetConstructor(argumentTypes); - MemberInfo method = type.GetMethod("FromFile", BindingFlags.Static, null, argumentTypes, null); - if (!(method is MethodInfo methodInfo) || - !typeof(ArchiveFileBase).IsAssignableFrom(methodInfo.ReturnType)) - method = null; - if (constructor == null && method == null) continue; - TypeHandlers[attribute.Type] = method ?? constructor; - } + TypeHandlers.Add(ArchiveType.Index, (filePath, file, header, blockTable, rootIndexBlock) => new IndexFile(filePath, file, header, blockTable, rootIndexBlock)); + TypeHandlers.Add(ArchiveType.Archive, (filePath, file, header, blockTable, rootIndexBlock) => new ArchiveFile(filePath, file, header, blockTable, rootIndexBlock)); + //foreach (var type in typeof(ArchiveFileBase).Assembly.GetTypes() + // .Where(i => typeof(ArchiveFileBase).IsAssignableFrom(i) && !i.IsAbstract)) + //{ + // var attribute = type.GetCustomAttribute(); + // if (attribute == null) continue; + // var argumentTypes = new[] + // { + // typeof(string), typeof(MemoryMappedFile), typeof(ArchiveHeader), typeof(BlockInfoHeader[]), + // typeof(RootIndexBlock) + // }; + // MemberInfo constructor = type.GetConstructor(argumentTypes); + // MemberInfo method = type.GetMethod("FromFile", BindingFlags.Static, null, argumentTypes, null); + // if (!(method is MethodInfo methodInfo) || + // !typeof(ArchiveFileBase).IsAssignableFrom(methodInfo.ReturnType)) + // method = null; + // if (constructor == null && method == null) continue; + // TypeHandlers[attribute.Type] = method ?? constructor; + //} } protected ArchiveFileBase(string fileName, MemoryMappedFile file, ArchiveHeader header, @@ -81,7 +84,7 @@ internal Stream GetBlockView(int index) private static Stream GetBlockView(BlockInfoHeader blockInfo, MemoryMappedFile file) { if (blockInfo.Size == 0) return null; - return file.CreateViewStream((long) blockInfo.Offset, (long) blockInfo.Size); + return file.CreateViewStream((long)blockInfo.Offset, (long)blockInfo.Size); } protected Stream GetBlockView(BlockInfoHeader blockInfo) @@ -99,7 +102,7 @@ private static (int rootDescriptorIndex, BlockInfoHeader[] blockPointers) ReadBl var startPosition = header.DataHeader.BlockTableOffset; var length = header.DataHeader.BlockCount * Marshal.SizeOf(); var archiveDescriptorIndex = -1; - using (var reader = new BinaryReader(file.CreateViewStream((long) startPosition, length))) + using (var reader = new BinaryReader(file.CreateViewStream((long)startPosition, length))) { for (var x = 0; x < header.DataHeader.BlockCount; x++) { @@ -140,12 +143,7 @@ public static ArchiveFileBase FromFile(string fileName) blockPointerInfo.blockPointers[blockPointerInfo.rootDescriptorIndex]); if (!TypeHandlers.TryGetValue(rootBlock.ArchiveType, out var creator)) throw new InvalidOperationException($"Unknown archive type: {rootBlock.ArchiveType:G}"); - var creatorArguments = new object[] {fileName, file, header, blockPointerInfo.blockPointers, rootBlock}; - if (creator is MethodInfo method) - return (ArchiveFileBase) method.Invoke(null, creatorArguments); - var constructorInfo = creator as ConstructorInfo; - Debug.Assert(constructorInfo != null); - return (ArchiveFileBase) constructorInfo.Invoke(creatorArguments); + return creator(fileName, file, header, blockPointerInfo.blockPointers, rootBlock); } catch { diff --git a/Source/Nexus.Archive/DataHeader.cs b/Source/Nexus.Archive/DataHeader.cs index 3766d65..08b75cc 100644 --- a/Source/Nexus.Archive/DataHeader.cs +++ b/Source/Nexus.Archive/DataHeader.cs @@ -5,10 +5,16 @@ namespace Nexus.Archive { public struct DataHeader { - private const int UnknownDataSize = 28; + private const int UnknownDataSize = 24; public ulong Unknown1; public ulong FileSize; - public ulong Unknown2; + public byte UnknownFileIdentifier; + // Always 2 for on disk files. + public byte Unknown2; + public ushort Unknown3; + public uint Unknown4; + //public ulong Unknown2; // Seems to denote file type, 0x60 XX XX XX for on disk files, other values for others. + // Remaining 3 bytes unknown. public ulong BlockTableOffset; public uint BlockCount; @@ -21,7 +27,10 @@ public static DataHeader ReadFrom(BinaryReader binaryReader) { Unknown1 = binaryReader.ReadUInt64(), FileSize = binaryReader.ReadUInt64(), - Unknown2 = binaryReader.ReadUInt64(), + UnknownFileIdentifier = binaryReader.ReadByte(), + Unknown2 = binaryReader.ReadByte(), + Unknown3 = binaryReader.ReadUInt16(), + Unknown4 = binaryReader.ReadUInt32(), BlockTableOffset = binaryReader.ReadUInt64(), BlockCount = binaryReader.ReadUInt32(), UnknownData = binaryReader.ReadBytes(UnknownDataSize) diff --git a/Source/Nexus.Archive/IndexFile.cs b/Source/Nexus.Archive/IndexFile.cs index 90c16c3..4cd1c28 100644 --- a/Source/Nexus.Archive/IndexFile.cs +++ b/Source/Nexus.Archive/IndexFile.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; using System.IO.MemoryMappedFiles; using System.Linq; using System.Text; +using DotNet.Globbing; namespace Nexus.Archive { @@ -17,6 +19,55 @@ public IndexFile(string fileName, MemoryMappedFile file, ArchiveHeader header, new BinaryReader(GetBlockView(rootIndex.BlockIndex), Encoding.UTF8)); } + public IEnumerable GetFilesystemEntries() + { + return RootFolder.EnumerateChildren(true); + } + public IEnumerable GetFilesystemEntries(string searchPattern) + { + return SearchWithGlob(GetFilesystemEntries(), searchPattern); + } + + public IEnumerable GetFolders() + { + return RootFolder.EnumerateFolders(true); + } + + public IEnumerable GetFolders(string searchPattern) + { + return SearchWithGlob(GetFolders(), searchPattern); + } + + public IEnumerable GetFiles() + { + return RootFolder.EnumerateFiles(true); + } + + public IEnumerable GetFiles(string searchPattern) + { + return SearchWithGlob(GetFiles(), searchPattern); + } + + private static IEnumerable SearchWithGlob(IEnumerable items, string searchPattern) + where T : IArchiveFilesystemEntry + { + var glob = ParseGlob(searchPattern); + foreach(var item in items.Where(i => glob.IsMatch(i.Path))) + yield return item; + } + + private static Glob ParseGlob(string glob) + { + var options = new GlobOptions() + { + Evaluation = + { + CaseInsensitive = true + } + }; + return Glob.Parse(glob, options); + } + public IArchiveFolderEntry RootFolder { get; } public IArchiveFilesystemEntry FindEntry(string archivePath) diff --git a/Source/Nexus.Archive/Nexus.Archive.csproj b/Source/Nexus.Archive/Nexus.Archive.csproj index da42d3e..8dce0c8 100644 --- a/Source/Nexus.Archive/Nexus.Archive.csproj +++ b/Source/Nexus.Archive/Nexus.Archive.csproj @@ -13,7 +13,12 @@ + + + + + diff --git a/Source/Nexus.Archive/RootIndexBlock.cs b/Source/Nexus.Archive/RootIndexBlock.cs index e01f6a0..94c3633 100644 --- a/Source/Nexus.Archive/RootIndexBlock.cs +++ b/Source/Nexus.Archive/RootIndexBlock.cs @@ -7,17 +7,31 @@ public struct RootIndexBlock public ArchiveType ArchiveType; public uint Version; public uint BlockCount; + public uint BuildNumber; public int BlockIndex; public static RootIndexBlock FromReader(BinaryReader reader) { - return new RootIndexBlock + var ret = new RootIndexBlock() { - ArchiveType = (ArchiveType) reader.ReadUInt32(), + ArchiveType = (ArchiveType)reader.ReadUInt32(), Version = reader.ReadUInt32(), - BlockCount = reader.ReadUInt32(), - BlockIndex = reader.ReadInt32() }; + if (ret.Version == 1) + { + ret.BuildNumber = reader.ReadUInt32(); + } + else if (ret.Version == 2) + { + ret.BlockCount = reader.ReadUInt32(); + } + else + { + throw new InvalidDataException($"Unknown file version {ret.Version} for archive type {ret.ArchiveType}"); + } + + ret.BlockIndex = reader.ReadInt32(); + return ret; } } } \ No newline at end of file diff --git a/Source/TableExtractor/TableExtractor.csproj b/Source/TableExtractor/TableExtractor.csproj index 1073f54..19612a8 100644 --- a/Source/TableExtractor/TableExtractor.csproj +++ b/Source/TableExtractor/TableExtractor.csproj @@ -4,7 +4,9 @@ Exe netcoreapp2.1 - + + + diff --git a/Source/TableExtractor/nuget.config b/Source/TableExtractor/nuget.config new file mode 100644 index 0000000..cb2591a --- /dev/null +++ b/Source/TableExtractor/nuget.config @@ -0,0 +1,9 @@ + + + + + + + + +