public FPakEntry(PakFileReader reader, string path, FArchive Ar) : base(reader) { Path = path; // FPakEntry is duplicated before each stored file, without a filename. So, // remember the serialized size of this structure to avoid recomputation later. var startOffset = Ar.Position; Offset = Ar.Read <long>(); CompressedSize = Ar.Read <long>(); UncompressedSize = Ar.Read <long>(); Size = UncompressedSize; if (reader.Info.Version < EPakFileVersion.PakFile_Version_FNameBasedCompressionMethod) { try { CompressionMethod = reader.Info.CompressionMethods[Ar.Read <int>()]; } catch { CompressionMethod = CompressionMethod.Unknown; } } else if (reader.Info.Version == EPakFileVersion.PakFile_Version_FNameBasedCompressionMethod && !reader.Info.IsSubVersion) { CompressionMethod = reader.Info.CompressionMethods[Ar.Read <byte>()]; } else { CompressionMethod = reader.Info.CompressionMethods[Ar.Read <int>()]; } if (reader.Info.Version < EPakFileVersion.PakFile_Version_NoTimestamps) { Ar.Position += 8; // Timestamp } Ar.Position += 20; // Hash if (reader.Info.Version >= EPakFileVersion.PakFile_Version_CompressionEncryption) { if (CompressionMethod != CompressionMethod.None) { CompressionBlocks = Ar.ReadArray <FPakCompressedBlock>(); } IsEncrypted = Ar.ReadFlag(); CompressionBlockSize = Ar.Read <uint>(); } if (reader.Info.Version >= EPakFileVersion.PakFile_Version_RelativeChunkOffsets) { // Convert relative compressed offsets to absolute for (var i = 0; i < CompressionBlocks.Length; i++) { CompressionBlocks[i].CompressedStart += Offset; CompressionBlocks[i].CompressedEnd += Offset; } } StructSize = (ushort)(Ar.Position - startOffset); }
public static async Task PopulateMenu() { PopulateBase(); await Task.Run(() => { if (string.IsNullOrEmpty(Properties.Settings.Default.PakPath)) { Application.Current.Dispatcher.Invoke(delegate { var launcher = new FLauncher(); if ((bool)launcher.ShowDialog()) { Properties.Settings.Default.PakPath = launcher.Path; Properties.Settings.Default.Save(); } }); } // define the current game thank to the pak path Folders.SetGameName(Properties.Settings.Default.PakPath); // Add Pak Files if (Directory.Exists(Properties.Settings.Default.PakPath)) { string[] paks = Directory.GetFiles(Properties.Settings.Default.PakPath, "*.pak"); for (int i = 0; i < paks.Length; i++) { if (!Utils.Paks.IsFileReadLocked(new FileInfo(paks[i]))) { PakFileReader pakFile = new PakFileReader(paks[i]); DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Registering]", $"{pakFile.FileName} with GUID {pakFile.Info.EncryptionKeyGuid.Hex}"); if (i == 0) { Globals.Game.Version = pakFile.Info.Version; Globals.Game.SubVersion = pakFile.Info.SubVersion; } Application.Current.Dispatcher.Invoke(delegate { MenuItems.pakFiles.Add(new PakMenuItemViewModel { PakFile = pakFile, IsEnabled = false }); }); } else { FConsole.AppendText(string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(paks[i])), FColors.Red, true); DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Locked]", paks[i]); } } } }); }
public static void Set(this PakPropertiesViewModel vm, PakFileReader pakFileReader) { Application.Current.Dispatcher.Invoke(delegate { vm.PakName = pakFileReader.FileName; vm.Version = ((int)pakFileReader.Info.Version).ToString(); vm.MountPoint = pakFileReader.MountPoint; vm.AesKey = pakFileReader.AesKey?.ToStringKey(); vm.Guid = pakFileReader.Info.EncryptionKeyGuid.Hex; vm.FileCount = (pakFileReader as IReadOnlyDictionary <string, FPakEntry>).Count.ToString(); }); }
public void Initialize(string file = "", Stream[] stream = null !) { var ext = file.SubstringAfter('.'); if (string.IsNullOrEmpty(ext)) { return; } if (ext.Equals("pak", StringComparison.OrdinalIgnoreCase)) { try { var reader = new PakFileReader(file, stream[0], Versions) { IsConcurrent = true }; if (reader.IsEncrypted && !_requiredKeys.ContainsKey(reader.Info.EncryptionKeyGuid)) { _requiredKeys[reader.Info.EncryptionKeyGuid] = null; } _unloadedVfs[reader] = null; } catch (Exception e) { Log.Warning(e.ToString()); } } else if (ext.Equals("utoc", StringComparison.OrdinalIgnoreCase)) { try { var reader = new IoStoreReader(file, stream[0], stream[1], EIoStoreTocReadOptions.ReadDirectoryIndex, Versions) { IsConcurrent = true }; if (reader.IsEncrypted && !_requiredKeys.ContainsKey(reader.Info.EncryptionKeyGuid)) { _requiredKeys[reader.Info.EncryptionKeyGuid] = null; } _unloadedVfs[reader] = null; } catch (Exception e) { Log.Warning(e.ToString()); } } }
private async void OpenPak(string filename) { Enabled = false; pbProgress.Visible = true; reader?.Dispose(); await Task.Run(() => reader = new PakFileReader(new FileStream(filename, FileMode.Open, FileAccess.Read))); BuildFilesTree(); treeView.Enabled = btnExtractAll.Enabled = true; slblFile.Text = Path.GetFileName(filename); slblFiles.Text = $"Files: {reader.Files.Count}"; pbProgress.Visible = false; Enabled = true; this.filename = filename; Text = $"{Path.GetFileName(filename)} - {TITLE}"; }
public static async Task PopulateMenu() { PopulateBase(); if (string.IsNullOrEmpty(Properties.Settings.Default.PakPath)) { var launcher = new FLauncher(); if ((bool)launcher.ShowDialog()) { Properties.Settings.Default.PakPath = launcher.Path; Properties.Settings.Default.Save(); } } // define the current game thank to the pak path Folders.SetGameName(Properties.Settings.Default.PakPath); // Add Pak Files if (Directory.Exists(Properties.Settings.Default.PakPath)) { foreach (string pak in Directory.GetFiles(Properties.Settings.Default.PakPath, "*.pak")) { if (!Utils.Paks.IsFileReadLocked(new FileInfo(pak))) { PakFileReader pakFile = new PakFileReader(pak); DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Registering]", $"{pakFile.FileName} with GUID {pakFile.Info.EncryptionKeyGuid.Hex}"); MenuItems.pakFiles.Add(new PakMenuItemViewModel { PakFile = pakFile, IsEnabled = false }); } else { Globals.gNotifier.ShowCustomMessage(Properties.Resources.PakFiles, string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(pak))); DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Locked]", pak); } } } await Task.CompletedTask.ConfigureAwait(false); }
public static async Task PopulateMenu() { PopulateBase(); await Task.Run(async() => { if (string.IsNullOrEmpty(Properties.Settings.Default.PakPath)) { await Application.Current.Dispatcher.InvokeAsync(delegate { var launcher = new FLauncher(); bool?result = launcher.ShowDialog(); if (result.HasValue && result.Value) { Properties.Settings.Default.PakPath = launcher.Path; Properties.Settings.Default.Save(); } }); } // Add Pak Files if (Properties.Settings.Default.PakPath.EndsWith(".manifest")) { ManifestInfo manifestInfo = await ManifestGrabber.TryGetLatestManifestInfo().ConfigureAwait(false); byte[] manifestData = await manifestInfo.DownloadManifestDataAsync().ConfigureAwait(false); Manifest manifest = new Manifest(manifestData, new ManifestOptions { ChunkBaseUri = new Uri("http://download.epicgames.com/Builds/Fortnite/CloudDir/ChunksV3/", UriKind.Absolute), ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks")) }); int pakFiles = 0; foreach (FileManifest fileManifest in manifest.FileManifests) { if (!_pakFileRegex.IsMatch(fileManifest.Name)) { continue; } var pakFileName = fileManifest.Name.Replace('/', '\\'); PakFileReader pakFile = new PakFileReader(pakFileName, fileManifest.GetStream()); if (pakFiles++ == 0) { // define the current game thank to the pak path Folders.SetGameName(pakFileName); Globals.Game.Version = pakFile.Info.Version; Globals.Game.SubVersion = pakFile.Info.SubVersion; } await Application.Current.Dispatcher.InvokeAsync(delegate { MenuItems.pakFiles.Add(new PakMenuItemViewModel { PakFile = pakFile, IsEnabled = false }); }); } } else if (Directory.Exists(Properties.Settings.Default.PakPath)) { // define the current game thank to the pak path Folders.SetGameName(Properties.Settings.Default.PakPath); string[] paks = Directory.GetFiles(Properties.Settings.Default.PakPath, "*.pak"); for (int i = 0; i < paks.Length; i++) { if (!Utils.Paks.IsFileReadLocked(new FileInfo(paks[i]))) { PakFileReader pakFile = new PakFileReader(paks[i]); DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Registering]", $"{pakFile.FileName} with GUID {pakFile.Info.EncryptionKeyGuid.Hex}"); if (i == 0) { Globals.Game.Version = pakFile.Info.Version; Globals.Game.SubVersion = pakFile.Info.SubVersion; } await Application.Current.Dispatcher.InvokeAsync(delegate { MenuItems.pakFiles.Add(new PakMenuItemViewModel { PakFile = pakFile, IsEnabled = false }); }); } else { FConsole.AppendText(string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(paks[i])), FColors.Red, true); DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Locked]", paks[i]); } } } }); }
public static async Task PopulateMenu() { PopulateBase(); await Task.Run(async() => { if (string.IsNullOrEmpty(Properties.Settings.Default.PakPath)) { await Application.Current.Dispatcher.InvokeAsync(delegate { var launcher = new FLauncher(); bool?result = launcher.ShowDialog(); if (result.HasValue && result.Value) { Properties.Settings.Default.PakPath = launcher.Path; Properties.Settings.Default.Save(); } }); } // Add Pak Files if (Properties.Settings.Default.PakPath.EndsWith("-fn.manifest")) { ManifestInfo manifestInfo = await ManifestGrabber.TryGetLatestManifestInfo().ConfigureAwait(false); if (manifestInfo == null) { throw new Exception("Failed to load latest manifest."); } DirectoryInfo chunksDir = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks")); string manifestPath = Path.Combine(chunksDir.FullName, manifestInfo.Filename); byte[] manifestData; if (File.Exists(manifestPath)) { manifestData = await File.ReadAllBytesAsync(manifestPath); } else { manifestData = await manifestInfo.DownloadManifestDataAsync().ConfigureAwait(false); await File.WriteAllBytesAsync(manifestPath, manifestData).ConfigureAwait(false); } Manifest manifest = new Manifest(manifestData, new ManifestOptions { ChunkBaseUri = new Uri("http://download.epicgames.com/Builds/Fortnite/CloudDir/ChunksV3/", UriKind.Absolute), ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks")) }); int pakFiles = 0; foreach (FileManifest fileManifest in manifest.FileManifests) { if (!_pakFileRegex.IsMatch(fileManifest.Name)) { continue; } var pakStream = fileManifest.GetStream(); if (pakStream.Length == 365) { continue; } var pakFileName = fileManifest.Name.Replace('/', '\\'); if (pakFileName.EndsWith(".pak")) { PakFileReader pakFile = new PakFileReader(pakFileName, pakStream); if (pakFiles++ == 0) { // define the current game thank to the pak path Folders.SetGameName(pakFileName); Globals.Game.Version = pakFile.Info.Version; Globals.Game.SubVersion = pakFile.Info.SubVersion; } await Application.Current.Dispatcher.InvokeAsync(delegate { MenuItems.pakFiles.Add(new PakMenuItemViewModel { PakFile = pakFile, IsEnabled = false }); }); } else if (pakFileName.EndsWith(".ucas")) { var utocStream = manifest.FileManifests.FirstOrDefault(x => x.Name.Equals(fileManifest.Name.Replace(".ucas", ".utoc"))); var ioStore = new FFileIoStoreReader(pakFileName.SubstringAfterLast('\\'), pakFileName.SubstringBeforeLast('\\'), utocStream.GetStream(), pakStream); await Application.Current.Dispatcher.InvokeAsync(delegate { MenuItems.pakFiles.Add(new PakMenuItemViewModel { IoStore = ioStore, IsEnabled = false }); }); } } } else if (Properties.Settings.Default.PakPath.EndsWith("-val.manifest")) { ValorantAPIManifest manifest = await ValorantAPIManifest.DownloadAndParse(Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"))).ConfigureAwait(false); if (manifest == null) { throw new Exception("Failed to load latest manifest."); } for (int i = 0; i < manifest.Paks.Length; i++) { ValorantPak pak = manifest.Paks[i]; var pakFileName = @$ "ShooterGame\Content\Paks\{pak.Name}"; PakFileReader pakFile = new PakFileReader(pakFileName, manifest.GetPakStream(i)); if (i == 0) { // define the current game thank to the pak path Folders.SetGameName(pakFileName); Globals.Game.Version = pakFile.Info.Version; Globals.Game.SubVersion = pakFile.Info.SubVersion; } await Application.Current.Dispatcher.InvokeAsync(delegate { MenuItems.pakFiles.Add(new PakMenuItemViewModel { PakFile = pakFile, IsEnabled = false }); }); } }
public FPakEntry(PakFileReader reader, string path, FArchive Ar) : base(reader) { Path = path; // FPakEntry is duplicated before each stored file, without a filename. So, // remember the serialized size of this structure to avoid recomputation later. var startOffset = Ar.Position; Offset = Ar.Read <long>(); CompressedSize = Ar.Read <long>(); UncompressedSize = Ar.Read <long>(); Size = UncompressedSize; if (reader.Info.Version < EPakFileVersion.PakFile_Version_FNameBasedCompressionMethod) { var LegacyCompressionMethod = Ar.Read <ECompressionFlags>(); int CompressionMethodIndex; if (LegacyCompressionMethod == ECompressionFlags.COMPRESS_None) { CompressionMethodIndex = 0; } else if (LegacyCompressionMethod == ECompressionFlags.COMPRESS_LZ4) { CompressionMethodIndex = 4; } else if (LegacyCompressionMethod.HasFlag(ECompressionFlags.COMPRESS_ZLIB)) { CompressionMethodIndex = 1; } else if (LegacyCompressionMethod.HasFlag(ECompressionFlags.COMPRESS_GZIP)) { CompressionMethodIndex = 2; } else if (LegacyCompressionMethod.HasFlag(ECompressionFlags.COMPRESS_Custom)) { CompressionMethodIndex = 3; } else { CompressionMethodIndex = -1; //throw new ParserException("Found an unknown compression type in pak file, will need to be supported for legacy files"); } CompressionMethod = CompressionMethodIndex == -1 ? CompressionMethod.Unknown : reader.Info.CompressionMethods[CompressionMethodIndex]; } else if (reader.Info.Version == EPakFileVersion.PakFile_Version_FNameBasedCompressionMethod && !reader.Info.IsSubVersion) { CompressionMethod = reader.Info.CompressionMethods[Ar.Read <byte>()]; } else { CompressionMethod = reader.Info.CompressionMethods[Ar.Read <int>()]; } if (reader.Info.Version < EPakFileVersion.PakFile_Version_NoTimestamps) { Ar.Position += 8; // Timestamp } Ar.Position += 20; // Hash if (reader.Info.Version >= EPakFileVersion.PakFile_Version_CompressionEncryption) { if (CompressionMethod != CompressionMethod.None) { CompressionBlocks = Ar.ReadArray <FPakCompressedBlock>(); } IsEncrypted = Ar.ReadFlag(); CompressionBlockSize = Ar.Read <uint>(); } if (reader.Info.Version >= EPakFileVersion.PakFile_Version_RelativeChunkOffsets) { // Convert relative compressed offsets to absolute for (var i = 0; i < CompressionBlocks.Length; i++) { CompressionBlocks[i].CompressedStart += Offset; CompressionBlocks[i].CompressedEnd += Offset; } } StructSize = (int)(Ar.Position - startOffset); }
public unsafe FPakEntry(PakFileReader reader, string path, byte *data) : base(reader) { Path = path; Versions = reader.Ar.Versions; // UE4 reference: FPakFile::DecodePakEntry() uint bitfield = *(uint *)data; data += sizeof(uint); CompressionBlockSize = 0; if ((bitfield & 0x3f) == 0x3f) // flag value to load a field { CompressionBlockSize = *(uint *)data; data += sizeof(uint); } else { // for backwards compatibility with old paks : CompressionBlockSize = (bitfield & 0x3f) << 11; } CompressionMethod = reader.Info.CompressionMethods[(int)((bitfield >> 23) & 0x3f)]; // Offset follows - either 32 or 64 bit value if ((bitfield & 0x80000000) != 0) { Offset = *(uint *)data; data += sizeof(uint); } else { Offset = *(long *)data; // Should be ulong data += sizeof(long); } // The same for UncompressedSize if ((bitfield & 0x40000000) != 0) { UncompressedSize = *(uint *)data; data += sizeof(uint); } else { UncompressedSize = *(long *)data; // Should be ulong data += sizeof(long); } Size = UncompressedSize; // Size field if (CompressionMethod != CompressionMethod.None) { if ((bitfield & 0x20000000) != 0) { CompressedSize = *(uint *)data; data += sizeof(uint); } else { CompressedSize = *(long *)data; data += sizeof(long); } } else { CompressedSize = UncompressedSize; } // bEncrypted IsEncrypted = ((bitfield >> 22) & 1) != 0; // Compressed block count var blockCount = (bitfield >> 6) & 0xffff; // Compute StructSize: each file still have FPakEntry data prepended, and it should be skipped. StructSize = sizeof(long) * 3 + sizeof(int) * 2 + 1 + 20; // Take into account CompressionBlocks if (CompressionMethod != CompressionMethod.None) { StructSize += (ushort)(sizeof(int) + blockCount * 2 * sizeof(long)); } // Compression information CompressionBlocks = new FPakCompressedBlock[blockCount]; if (blockCount > 0) { // CompressionBlockSize if (UncompressedSize < 65536) { CompressionBlockSize = (uint)UncompressedSize; } // CompressionBlocks if (blockCount == 1 && !IsEncrypted) { ref var b = ref CompressionBlocks[0]; b.CompressedStart = Offset + StructSize; b.CompressedEnd = b.CompressedStart + CompressedSize; } else { var currentOffset = Offset + StructSize; var alignment = IsEncrypted ? Aes.ALIGN : 1; for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { var currentBlockSize = *(uint *)data; data += sizeof(uint); ref var block = ref CompressionBlocks[blockIndex]; block.CompressedStart = currentOffset; block.CompressedEnd = currentOffset + currentBlockSize; currentOffset += currentBlockSize.Align(alignment); } }
public unsafe FPakEntry(PakFileReader reader, string path, byte *data) : base(reader) { Path = path; // UE4 reference: FPakFile::DecodePakEntry() uint bitfield = *(uint *)data; data += sizeof(uint); uint compressionBlockSize; if ((bitfield & 0x3f) == 0x3f) // flag value to load a field { compressionBlockSize = *(uint *)data; data += sizeof(uint); } else { // for backwards compatibility with old paks : compressionBlockSize = (bitfield & 0x3f) << 11; } // Filter out the CompressionMethod. CompressionMethod = reader.Info.CompressionMethods[(int)((bitfield >> 23) & 0x3f)]; // Test for 32-bit safe values. Grab it, or memcpy the 64-bit value // to avoid alignment exceptions on platforms requiring 64-bit alignment // for 64-bit variables. // // Read the Offset. var bIsOffset32BitSafe = (bitfield & (1 << 31)) != 0; if (bIsOffset32BitSafe) { Offset = *(uint *)data; data += sizeof(uint); } else { Offset = *(long *)data; // Should be ulong data += sizeof(long); } // Read the UncompressedSize. var bIsUncompressedSize32BitSafe = (bitfield & (1 << 30)) != 0; if (bIsUncompressedSize32BitSafe) { UncompressedSize = *(uint *)data; data += sizeof(uint); } else { UncompressedSize = *(long *)data; // Should be ulong data += sizeof(long); } Size = UncompressedSize; // Fill in the Size. if (CompressionMethod != CompressionMethod.None) { var bIsSize32BitSafe = (bitfield & (1 << 29)) != 0; if (bIsSize32BitSafe) { CompressedSize = *(uint *)data; data += sizeof(uint); } else { CompressedSize = *(long *)data; data += sizeof(long); } } else { // The Size is the same thing as the UncompressedSize when // CompressionMethod == CompressionMethod.None. CompressedSize = UncompressedSize; } // Filter the encrypted flag. Flags |= (bitfield & (1 << 22)) != 0 ? 1u : 0u; // This should clear out any excess CompressionBlocks that may be valid in the user's // passed in entry. var compressionBlocksCount = (bitfield >> 6) & 0xffff; CompressionBlocks = new FPakCompressedBlock[compressionBlocksCount]; CompressionBlockSize = 0; if (compressionBlocksCount > 0) { CompressionBlockSize = compressionBlockSize; // Per the comment in Encode, if compressionBlocksCount == 1, we use UncompressedSize for CompressionBlockSize if (compressionBlocksCount == 1) { CompressionBlockSize = (uint)UncompressedSize; } } // Compute StructSize: each file still have FPakEntry data prepended, and it should be skipped. StructSize = sizeof(long) * 3 + sizeof(int) * 2 + 1 + 20; // Take into account CompressionBlocks if (CompressionMethod != CompressionMethod.None) { StructSize += (int)(sizeof(int) + compressionBlocksCount * 2 * sizeof(long)); } // Handle building of the CompressionBlocks array. if (compressionBlocksCount == 1 && !IsEncrypted) { // If the number of CompressionBlocks is 1, we didn't store any extra information. // Derive what we can from the entry's file offset and size. ref var b = ref CompressionBlocks[0]; b.CompressedStart = Offset + StructSize; b.CompressedEnd = b.CompressedStart + CompressedSize; }
public void Initialize() { if (!_workingDirectory.Exists) { throw new ArgumentException("Given directory must exist", nameof(_workingDirectory)); } var osFiles = new Dictionary <string, GameFile>(); foreach (var file in _workingDirectory.EnumerateFiles("*.*", _searchOption)) { var ext = file.Extension.SubstringAfter('.'); if (!file.Exists || string.IsNullOrEmpty(ext)) { return; } if (ext.Equals("pak", StringComparison.OrdinalIgnoreCase)) { try { var reader = new PakFileReader(file, Game, Ver) { IsConcurrent = true }; if (reader.IsEncrypted && !_requiredKeys.ContainsKey(reader.Info.EncryptionKeyGuid)) { _requiredKeys[reader.Info.EncryptionKeyGuid] = null; } _unloadedVfs[reader] = null; } catch (Exception e) { Log.Warning(e.ToString()); } } else if (ext.Equals("utoc", StringComparison.OrdinalIgnoreCase)) { try { var reader = new IoStoreReader(file, EIoStoreTocReadOptions.ReadDirectoryIndex, Game, Ver) { IsConcurrent = true }; if (reader.IsEncrypted && !_requiredKeys.ContainsKey(reader.Info.EncryptionKeyGuid)) { _requiredKeys[reader.Info.EncryptionKeyGuid] = null; } _unloadedVfs[reader] = null; } catch (Exception e) { Log.Warning(e.ToString()); } } else { // Register local file only if it has a known extension, we don't need every file if (!GameFile.Ue4KnownExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)) { continue; } var osFile = new OsGameFile(_workingDirectory, file, Game, Ver); if (IsCaseInsensitive) { osFiles[osFile.Path.ToLowerInvariant()] = osFile; } else { osFiles[osFile.Path] = osFile; } } } _files.AddFiles(osFiles); }