### BundledGGPK Source: https://context7.com/aianlinb/libggpk3/llms.txt Provides unified GGPK and bundle access for standalone clients. It extends GGPK by automatically parsing the embedded `Bundles2/_.index.bin` into an `Index`, making it the primary entry point for standalone Path of Exile installations. ```APIDOC ## BundledGGPK — Unified GGPK + bundle access for standalone clients `BundledGGPK` extends `GGPK` by automatically parsing the embedded `Bundles2/_.index.bin` into an `Index`. This is the primary entry point for standalone Path of Exile installations. ### Methods - **BundledGGPK(filePath)**: Constructor that opens the GGPK file and automatically parses the bundle index. - **Index.ExtractParallel(node, destinationPath)**: Extracts a node from the bundle index to the specified destination path. - **Index.Replace(index, entries, saveIndex)**: Patches bundled files from a ZIP archive into the provided index. ``` -------------------------------- ### Look up files in Bundle Index with Index.TryGetFile / TryFindNode Source: https://context7.com/aianlinb/libggpk3/llms.txt Use TryGetFile for fast hash-based lookups or TryFindNode for tree-based lookups in the bundle index. Ensure paths are parsed and the tree is built for tree-based lookups. ```csharp using LibBundle3; using LibBundle3.Records; using var index = new Index(@"C:\\Games\\PathOfExile\\Bundles2\\_.index.bin"); // Direct hash lookup — no tree construction needed if (index.TryGetFile("Art/2DArt/UIImages/loading.png", out FileRecord? fr)) Console.WriteLine($"Found in bundle: {fr.BundleRecord.Path}, offset: {fr.Offset}, size: {fr.Size}"); // Tree-based lookup (requires ParsePaths + BuildTree) if (index.TryFindNode("Art/2DArt/UIImages", out var node)) Console.WriteLine($"Node: {LibBundle3.Nodes.ITreeNode.GetPath(node)}"); ``` -------------------------------- ### Open Bundle Index with LibBundle3 Index Source: https://context7.com/aianlinb/libggpk3/llms.txt Initialize the Index class to parse bundle index files. Supports lazy path parsing for faster startup. ```csharp using LibBundle3; // Open by file path — DriveBundleFactory is inferred from the directory using var index = new Index(@"C:\\Games\\PathOfExile\\Bundles2\\_.index.bin"); // Lazy path parsing (faster startup; call ParsePaths() manually later) using var indexLazy = new Index(@"C:\\Games\\PathOfExile\\Bundles2\\_.index.bin", parsePaths: false); int failed = indexLazy.ParsePaths(); if (failed != 0) Console.WriteLine($"Warning: {failed} file paths could not be resolved."); Console.WriteLine($"Bundles: {index.Bundles.Length}"); Console.WriteLine($"Files: {index.Files.Count}"); ``` -------------------------------- ### Open and Close a Content.ggpk File with GGPK Source: https://context7.com/aianlinb/libggpk3/llms.txt Demonstrates opening a GGPK file from a file path or an arbitrary stream. Disposing the GGPK instance automatically renews hashes and flushes the stream. ```csharp using LibGGPK3; using LibGGPK3.Records; // Open from a file path (read-write, shared-read) using var ggpk = new GGPK(@"C:\\Games\\PathOfExile\\Content.ggpk"); Console.WriteLine($"GGPK version: {ggpk.Version}"); // 3 = PC, 4 = Mac Console.WriteLine($"GGPK size: {ggpk.Length} bytes"); // Open from an arbitrary stream (leaveOpen: true keeps the stream alive after Dispose) using var fs = File.OpenRead(@"C:\\Games\\PathOfExile\\Content.ggpk"); using var ggpk2 = new GGPK(fs, leaveOpen: true); ``` -------------------------------- ### Navigate the GGPK Directory Tree Source: https://context7.com/aianlinb/libggpk3/llms.txt Shows how to navigate the GGPK directory structure using `GGPK.Root`. Child nodes are loaded lazily and can be accessed by name or Murmur2 name-hash. `TryFindNode` allows path-based lookups. ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\\Games\\PathOfExile\\Content.ggpk"); DirectoryRecord root = ggpk.Root; // Access child by name (case-insensitive Murmur2 hash lookup) var metadata = root["Metadata"] as DirectoryRecord; var file = metadata?["monsters.txt"] as FileRecord; // Navigate with TryFindNode (forward-slash separated path, no leading slash) if (root.TryFindNode("Metadata/monsters.txt", out TreeNode? node) && node is FileRecord fr) Console.WriteLine($"Found: {fr.GetPath()}, size: {fr.DataLength} bytes"); // Enumerate all direct children foreach (TreeNode child in root) Console.WriteLine($"{(child is DirectoryRecord ? "DIR " : "FILE")} {child.Name}"); ``` -------------------------------- ### GGPK Constructor Source: https://context7.com/aianlinb/libggpk3/llms.txt Opens a Content.ggpk file for reading and writing. It can be initialized from a file path or an existing stream. Disposing the GGPK instance automatically flushes changes and renews hashes. ```APIDOC ## GGPK — Open and close a Content.ggpk file `GGPK` is the root handle for a `.ggpk` file. It parses the file header and root directory on construction and exposes the full tree of `DirectoryRecord` / `FileRecord` nodes. Disposing the instance automatically calls `RenewHashes()` and flushes the stream. ```csharp using LibGGPK3; using LibGGPK3.Records; // Open from a file path (read-write, shared-read) using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); Console.WriteLine($"GGPK version: {ggpk.Version}"); // 3 = PC, 4 = Mac Console.WriteLine($"GGPK size: {ggpk.Length} bytes"); // Open from an arbitrary stream (leaveOpen: true keeps the stream alive after Dispose) using var fs = File.OpenRead(@"C:\Games\PathOfExile\Content.ggpk"); using var ggpk2 = new GGPK(fs, leaveOpen: true); ``` ``` -------------------------------- ### Connect to PoE Patch Server with PatchClient Source: https://context7.com/aianlinb/libggpk3/llms.txt Use PatchClient to retrieve CDN URLs, query directory listings, and download/apply game updates. Ensure you have the necessary using directives. ```csharp using System.Net; using LibGGPK3; using LibGGPK3.Records; // Get CDN URL without keeping a connection string cdnUrl = await PatchClient.GetPatchCdnUrlAsync(PatchClient.ServerEndPoints.US); Console.WriteLine("CDN: " + cdnUrl); // Full patching workflow using var ggpk = new GGPK(@"C:\\Games\\PathOfExile\\Content.ggpk"); using var client = new PatchClient(); await client.ConnectAsync(PatchClient.ServerEndPoints.US); Console.WriteLine("CDN: " + client.CdnUrl); // Query a directory on the patch server PatchClient.EntryInfo[]? entries = await client.QueryDirectoryAsync("Art"); foreach (var e in entries!) Console.WriteLine($"{e.Name} ({e.FileSize} bytes)"); // Update a subtree in the local ggpk to match the server int updated = await client.UpdateNodeAsync(ggpk.Root["Art"]!); Console.WriteLine($"Updated {updated} files."); // Get patch notes URL string patchNotesUrl = await client.QueryPatchNotesUrlAsync(); Console.WriteLine("Patch notes: " + patchNotesUrl); ``` -------------------------------- ### Index (LibBundle3) Source: https://context7.com/aianlinb/libggpk3/llms.txt Parses the bundle index file, mapping game file paths to their positions within Oodle-compressed bundle files. ```APIDOC ## Index (LibBundle3) ### Description `Index` parses `_.index.bin`, which maps every game file path to its position within the Oodle-compressed `*.bundle.bin` files in the `Bundles2` directory. On Steam/Epic, bundles live on disk; provide a `DriveBundleFactory` (the default) or a custom `IBundleFactory`. ### Constructor #### `Index(string indexPath, bool parsePaths = true)` Opens and parses the bundle index file. - **Parameters** - `indexPath` (string): The path to the `_.index.bin` file. - `parsePaths` (bool, optional): Whether to parse file paths immediately. Defaults to `true`. ### Methods #### `ParsePaths()` Manually parses the file paths if `parsePaths` was set to `false` in the constructor. - **Returns** - `int`: The number of file paths that could not be resolved. ### Example Usage ```csharp // Open by file path — DriveBundleFactory is inferred from the directory using var index = new Index(@"C:\Games\PathOfExile\Bundles2\_.index.bin"); // Lazy path parsing (faster startup; call ParsePaths() manually later) using var indexLazy = new Index(@"C:\Games\PathOfExile\Bundles2\_.index.bin", parsePaths: false); int failed = indexLazy.ParsePaths(); if (failed != 0) Console.WriteLine($"Warning: {failed} file paths could not be resolved."); Console.WriteLine($"Bundles: {index.Bundles.Length}"); Console.WriteLine($"Files: {index.Files.Count}"); ``` ``` -------------------------------- ### Read File Content from GGPK Source: https://context7.com/aianlinb/libggpk3/llms.txt Illustrates reading the binary content of a file entry from a GGPK file using `FileRecord.Read`. Supports both allocating and non-allocating reads, as well as reading specific sub-ranges. ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\\Games\\PathOfExile\\Content.ggpk"); if (ggpk.Root.TryFindNode("Art/2DArt/loading.jpg", out var node) && node is FileRecord fr) { // Allocating read (convenient for one-off access) byte[] data = fr.Read(); File.WriteAllBytes("loading.jpg", data); // Non-allocating read into a pre-allocated buffer Span buf = stackalloc byte[fr.DataLength]; fr.Read(buf); // Read a sub-range (e.g., first 16 bytes only) byte[] header = fr.Read(..16); Console.WriteLine($"Magic bytes: {BitConverter.ToString(header)}"); } ``` -------------------------------- ### Index.TryGetFile / Index.TryFindNode Source: https://context7.com/aianlinb/libggpk3/llms.txt Look up files in the bundle index using either a hash-based lookup or by traversing the in-memory tree. ```APIDOC ## Index.TryGetFile / Index.TryFindNode ### Description `TryGetFile` is the fastest lookup (hash-based, no tree required). `TryFindNode` walks the in-memory tree built by `BuildTree()` / `Root`. ### Methods #### `TryGetFile(string path, out FileRecord? fileRecord)` Attempts to find a file by its path using a hash-based lookup. - **Parameters** - `path` (string): The path of the file to find. - `fileRecord` (out FileRecord?): When this method returns, contains the `FileRecord` if found, or `null` otherwise. - **Returns** - `bool`: `true` if the file was found, `false` otherwise. #### `TryFindNode(string path, out ITreeNode? node)` Attempts to find a node (directory or file) in the in-memory tree by its path. - **Parameters** - `path` (string): The path of the node to find. - `node` (out ITreeNode?): When this method returns, contains the `ITreeNode` if found, or `null` otherwise. - **Returns** - `bool`: `true` if the node was found, `false` otherwise. ### Example Usage ```csharp using LibBundle3; using LibBundle3.Records; using var index = new Index(@"C:\Games\PathOfExile\Bundles2\_.index.bin"); // Direct hash lookup — no tree construction needed if (index.TryGetFile("Art/2DArt/UIImages/loading.png", out FileRecord? fr)) Console.WriteLine($"Found in bundle: {fr.BundleRecord.Path}, offset: {fr.Offset}, size: {fr.Size}"); // Tree-based lookup (requires ParsePaths + BuildTree) if (index.TryFindNode("Art/2DArt/UIImages", out var node)) Console.WriteLine($"Node: {LibBundle3.Nodes.ITreeNode.GetPath(node)}"); ``` ``` -------------------------------- ### Replace File Content in GGPK Source: https://context7.com/aianlinb/libggpk3/llms.txt Demonstrates replacing a file's content within a GGPK file using `FileRecord.Write`. Handles content size changes by relocating the record and automatically recomputes SHA-256 hashes. Partial in-place writes are also supported. ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\\Games\\PathOfExile\\Content.ggpk"); if (ggpk.Root.TryFindNode("Art/UIImages/loading.png", out var node) && node is FileRecord fr) { byte[] newContent = File.ReadAllBytes("my_custom_loading.png"); // Full replace — hash is computed automatically fr.Write(newContent); // Partial in-place write (offset + length must fit within current DataLength) byte[] patch = { 0xDE, 0xAD, 0xBE, 0xEF }; fr.Write(patch, offset: 0); // replaces first 4 bytes, recomputes hash over full content } // RenewHashes() + Flush() are called automatically on Dispose ``` -------------------------------- ### Read and Write Bundle Files with Bundle Source: https://context7.com/aianlinb/libggpk3/llms.txt The Bundle class manages individual Oodle-compressed bundle files, supporting chunk-level caching for efficient data retrieval. It allows reading entire files, slices, or cached regions, and saving new content with specified compression. ```csharp using LibBundle3; // Open a bundle directly using var bundle = new Bundle(@"C:\Games\PathOfExile\Bundles2\Art\2DArt.bundle.bin"); Console.WriteLine($"Uncompressed size: {bundle.UncompressedSize} bytes"); Console.WriteLine($"Compressor: {bundle.Compressor}"); // Read entire content (no cache) byte[] raw = bundle.ReadWithoutCache(); // Read a slice (offset 0, 4096 bytes) — no cache ArraySegment slice = bundle.ReadWithoutCache(offset: 0, length: 4096); // Read with chunk-level cache (efficient for repeated access of same regions) ReadOnlyMemory cached = bundle.Read(offset: 1024, length: 256); // Save new content with Mermaid compression at Normal level byte[] newContent = File.ReadAllBytes("replacement_content.bin"); bundle.Save(newContent); // uses existing compressor by default bundle.Save(newContent, Oodle.Compressor.Mermaid, Oodle.CompressionLevel.Optimal2); ``` -------------------------------- ### Extract Bundle Files to Disk with Index.Extract Source: https://context7.com/aianlinb/libggpk3/llms.txt Extract files from bundles to disk, either sequentially or in parallel. Supports custom handlers for processing extracted content. ```csharp using LibBundle3; using LibBundle3.Nodes; using var index = new Index(@"C:\\Games\\PathOfExile\\Bundles2\\_.index.bin"); // Extract a subtree to disk (sequential, by bundle order) int count = Index.Extract(index.Root, @"C:\\Extracted", (fr, path) => { Console.WriteLine("Extracted: " + path); return false; }); Console.WriteLine($"Extracted {count} files."); // Parallel extraction (experimental, may not always be faster) int countP = Index.ExtractParallel(index.Root, @"C:\\ExtractedParallel"); Console.WriteLine($"Parallel extracted {countP} files."); // Batch extraction with a custom in-memory handler var specificFiles = index.Files.Values .Where(f => f.Path?.StartsWith("Metadata/Items") == true) .ToList(); Index.Extract(specificFiles, (fr, content) => { if (content.HasValue) File.WriteAllBytes(Path.GetFileName(fr.Path!), content.Value.ToArray()); return false; // continue }); ``` -------------------------------- ### Bundle Source: https://context7.com/aianlinb/libggpk3/llms.txt Handles a single Oodle-compressed bundle file (`*.bundle.bin`). It supports chunk-level caching for efficient repeated reads of the same data ranges, avoiding redundant decompression. ```APIDOC ## Bundle — Read and write a single `*.bundle.bin` file `Bundle` handles a single Oodle-compressed bundle file. It supports chunk-level caching so repeated reads of the same range avoid redundant decompression. ### Methods - **ReadWithoutCache(offset, length)**: Reads a specified range of bytes from the bundle without using the cache. - **Read(offset, length)**: Reads a specified range of bytes from the bundle using chunk-level caching. - **Save(newContent, compressor, compressionLevel)**: Saves new content to the bundle, optionally specifying the compressor and compression level. ``` -------------------------------- ### Replace Files in GGPK from Disk or ZIP Source: https://context7.com/aianlinb/libggpk3/llms.txt Replaces existing files within a GGPK archive with content from disk or a ZIP archive. The ZIP variant supports adding new files if `allowAdd` is true. Ensure paths and archive names are correct. ```csharp using System.IO.Compression; using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); // Replace from a folder on disk (only files that exist on disk are updated) int replaced = GGPK.Replace(ggpk.Root, @"C:\MyMod\", (fr, path) => { Console.WriteLine("Replaced: " + path); return false; }); Console.WriteLine($"Replaced {replaced} files from disk."); // Replace (and optionally add) files from a ZIP using var zip = ZipFile.OpenRead("my_mod.zip"); int patched = GGPK.Replace(ggpk.Root, zip.Entries, (fr, path, added) => { Console.WriteLine((added ? "Added: " : "Replaced: ") + path); return false; }, allowAdd: true); Console.WriteLine($"Patched {patched} files from ZIP."); ``` -------------------------------- ### Add New Files and Directories to GGPK Source: https://context7.com/aianlinb/libggpk3/llms.txt Adds new files or directories to an existing directory record within the GGPK. Returns false if a node with the same name already exists. Pre-allocating content size can improve efficiency. ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); // Find or create a directory path ggpk.Root.FindOrAddDirectory("MyMod/Textures", out DirectoryRecord texDir); // Add a new file (pre-allocate content size for efficiency) byte[] content = File.ReadAllBytes("custom_texture.dds"); bool added = texDir.AddFile("custom_texture.dds", out FileRecord newFile, content.Length); if (added) { newFile.Write(content); Console.WriteLine($"Created: {newFile.GetPath()}"); } else { Console.WriteLine("File already existed, updating..."); newFile.Write(content); } ``` -------------------------------- ### Defragment and Shrink GGPK with FastCompact Source: https://context7.com/aianlinb/libggpk3/llms.txt Defragments the GGPK file by moving existing records into free space gaps and truncating trailing free space. Supports cancellation via `CancellationToken` and progress reporting via `IProgress`. ```csharp using System.Threading; using LibGGPK3; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); var progress = new Progress(remaining => Console.Write($"\rFree records remaining: {remaining} ")); ggpk.FastCompact(cts.Token, progress); Console.WriteLine("\nCompact complete."); ``` -------------------------------- ### Extract Files Recursively with LibGGPK3 Source: https://context7.com/aianlinb/libggpk3/llms.txt Extracts files from a GGPK archive to the filesystem. An optional callback can monitor progress and cancel the operation. Ensure the GGPK file path is correct. ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); // Extract a whole directory if (ggpk.Root.TryFindNode("Metadata", out var dir)) { int count = GGPK.Extract(dir, @"C:\Extracted\Metadata", (fr, path) => { Console.WriteLine("Extracted: " + path); return false; // continue }); Console.WriteLine($"Extracted {count} files."); } // Extract a single file if (ggpk.Root.TryFindNode("Art/2DArt/loading.jpg", out var file)) { GGPK.Extract(file, @"C:\Extracted"); } ``` -------------------------------- ### PatchClient Source: https://context7.com/aianlinb/libggpk3/llms.txt Communicates with Path of Exile patch servers to retrieve CDN URLs, query directory listings, and download/apply game updates. ```APIDOC ## PatchClient ### Description `PatchClient` communicates with the Path of Exile patch servers (protocol v6) to retrieve the CDN URL, query directory listings, and download/apply live game updates to a local GGPK. ### Methods #### `GetPatchCdnUrlAsync` Retrieves the CDN URL for the patch server. - **Parameters** - `serverEndPoint` (PatchClient.ServerEndPoints): The server endpoint to connect to. - **Returns** - `Task`: A task that represents the asynchronous operation, returning the CDN URL. #### `ConnectAsync` Connects to the specified patch server endpoint. - **Parameters** - `serverEndPoint` (PatchClient.ServerEndPoints): The server endpoint to connect to. - **Returns** - `Task`: A task that represents the asynchronous operation. #### `QueryDirectoryAsync` Queries a directory on the patch server. - **Parameters** - `directoryPath` (string): The path of the directory to query. - **Returns** - `Task`: A task that represents the asynchronous operation, returning an array of entry information or null. #### `UpdateNodeAsync` Updates a subtree in the local GGPK to match the server. - **Parameters** - `node` (GGPKNode): The root node of the subtree to update. - **Returns** - `Task`: A task that represents the asynchronous operation, returning the number of updated files. #### `QueryPatchNotesUrlAsync` Gets the URL for the patch notes. - **Returns** - `Task`: A task that represents the asynchronous operation, returning the patch notes URL. ### Example Usage ```csharp // Get CDN URL without keeping a connection string cdnUrl = await PatchClient.GetPatchCdnUrlAsync(PatchClient.ServerEndPoints.US); Console.WriteLine("CDN: " + cdnUrl); // Full patching workflow using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); using var client = new PatchClient(); await client.ConnectAsync(PatchClient.ServerEndPoints.US); Console.WriteLine("CDN: " + client.CdnUrl); // Query a directory on the patch server PatchClient.EntryInfo[]? entries = await client.QueryDirectoryAsync("Art"); foreach (var e in entries!) Console.WriteLine($"{e.Name} ({e.FileSize} bytes)"); // Update a subtree in the local ggpk to match the server int updated = await client.UpdateNodeAsync(ggpk.Root["Art"]!); Console.WriteLine($"Updated {updated} files."); // Get patch notes URL string patchNotesUrl = await client.QueryPatchNotesUrlAsync(); Console.WriteLine("Patch notes: " + patchNotesUrl); ``` ``` -------------------------------- ### Patch Bundle Files with Index.Replace Source: https://context7.com/aianlinb/libggpk3/llms.txt Use Index.Replace to update bundle files from a directory or ZIP archive. Ensure to call index.Save() afterwards unless saveIndex: true is specified. ```csharp using System.IO.Compression; using LibBundle3; using var index = new Index(@"C:\Games\PathOfExile\Bundles2\_.index.bin"); // Replace from a folder on disk int n = Index.Replace(index.Root, @"C:\MyMod\", (fr, path) => { Console.WriteLine("Replaced: " + path); return false; }, saveIndex: true); // Replace from a ZIP archive using var zip = ZipFile.OpenRead("my_bundle_mod.zip"); int m = Index.Replace(index, zip.Entries, (fr, path) => { Console.WriteLine("Replaced: " + path); return false; }, saveIndex: true); // Replace a single file by path in the index (no tree needed) if (index.TryGetFile("Art/UIImages/mainmenu.png", out var fr)) { fr.Write(File.ReadAllBytes("custom_mainmenu.png"), saveIndex: false); index.Save(); // explicitly save once after all writes } ``` -------------------------------- ### Unified GGPK and Bundle Access with BundledGGPK Source: https://context7.com/aianlinb/libggpk3/llms.txt BundledGGPK provides unified access to GGPK files by automatically parsing the embedded index. It allows extraction of nodes and patching of bundled files from ZIP archives. ```csharp using LibBundledGGPK3; using LibBundle3; using LibBundle3.Nodes; // Open the standalone client GGPK (parses bundle index automatically) using var ggpk = new BundledGGPK(@"C:\Games\PathOfExile\Content.ggpk"); Console.WriteLine($"GGPK version: {ggpk.Version}"); Console.WriteLine($"Bundle files: {ggpk.Index.Bundles.Length}"); // Access the bundle index tree DirectoryNode bundleRoot = ggpk.Index.Root; // Extract a node from the bundle index if (ggpk.Index.TryFindNode("Metadata/Items", out var node)) { int count = Index.ExtractParallel(node, @"C:\Extracted\Items"); Console.WriteLine($"Extracted {count} bundled files."); } // Patch bundled files from a ZIP using var zip = System.IO.Compression.ZipFile.OpenRead("my_mod.zip"); int replaced = Index.Replace(ggpk.Index, zip.Entries, saveIndex: true); Console.WriteLine($"Replaced {replaced} bundled files."); ``` -------------------------------- ### Download Missing Bundles with ServerBundleFactory Source: https://context7.com/aianlinb/libggpk3/llms.txt Use `ServerBundleFactory` to automatically download missing game bundles from the CDN. This is useful for standalone-server or headless patching workflows. Ensure you have the correct base directory and CDN URL. ```csharp using LibBundle3; using LibBundledGGPK3; using LibGGPK3; // Get the CDN URL from the patch server string cdnUrl = await PatchClient.GetPatchCdnUrlAsync(PatchClient.ServerEndPoints.US); // Create a factory that falls back to the CDN for missing bundles using var factory = new ServerBundleFactory( baseDirectory: @"C:\\Games\\PathOfExile\\Bundles2\", patchCdnUrl: cdnUrl ); // Download the latest index file factory.DownloadIndex(); // Open the index with the server-backed factory using var index = new Index( @"C:\\Games\\PathOfExile\\Bundles2\\_.index.bin", parsePaths: true, bundleFactory: factory ); // Extract a file — if the bundle isn't local, it's downloaded automatically if (index.TryGetFile("Art/2DArt/loading.png", out var fr)) { var data = fr.Read(); File.WriteAllBytes("loading.png", data.ToArray()); } ``` -------------------------------- ### Index.Extract Source: https://context7.com/aianlinb/libggpk3/llms.txt Extracts files from bundles to disk, with options for sequential or parallel extraction, and custom handling of extracted content. ```APIDOC ## Index.Extract ### Description Extracts files grouped by bundle for optimal decompression throughput. The parallel overload processes bundles concurrently across threads. ### Methods #### `Extract(ITreeNode root, string outputPath, Func? progressCallback = null)` Extracts a subtree of files to disk in sequential order based on bundle. - **Parameters** - `root` (ITreeNode): The root node of the subtree to extract. - `outputPath` (string): The directory where extracted files will be saved. - `progressCallback` (Func?, optional): A callback function invoked for each extracted file. If it returns `true`, extraction stops. - **Returns** - `int`: The total number of files extracted. #### `ExtractParallel(ITreeNode root, string outputPath)` Extracts files in parallel across multiple threads. - **Parameters** - `root` (ITreeNode): The root node of the subtree to extract. - `outputPath` (string): The directory where extracted files will be saved. - **Returns** - `int`: The total number of files extracted. #### `Extract(IEnumerable files, Func, bool> handler)` Extracts a specific list of files and processes their content using a provided handler. - **Parameters** - `files` (IEnumerable): An enumerable collection of `FileRecord` objects to extract. - `handler` (Func, bool>): A function that handles the extracted file content. It receives the `FileRecord` and its content as `ReadOnlyMemory`. If it returns `true`, processing stops. ### Example Usage ```csharp using LibBundle3; using LibBundle3.Nodes; using var index = new Index(@"C:\Games\PathOfExile\Bundles2\_.index.bin"); // Extract a subtree to disk (sequential, by bundle order) int count = Index.Extract(index.Root, @"C:\Extracted", (fr, path) => { Console.WriteLine("Extracted: " + path); return false; }); Console.WriteLine($"Extracted {count} files."); // Parallel extraction (experimental, may not always be faster) int countP = Index.ExtractParallel(index.Root, @"C:\ExtractedParallel"); Console.WriteLine($"Parallel extracted {countP} files."); // Batch extraction with a custom in-memory handler var specificFiles = index.Files.Values .Where(f => f.Path?.StartsWith("Metadata/Items") == true) .ToList(); Index.Extract(specificFiles, (fr, content) => { if (content.HasValue) File.WriteAllBytes(Path.GetFileName(fr.Path!), content.Value.ToArray()); return false; // continue }); ``` ``` -------------------------------- ### DirectoryRecord.AddFile / AddDirectory Source: https://context7.com/aianlinb/libggpk3/llms.txt Adds new file or directory entries within an existing directory in the GGPK archive. Returns false if an entry with the same name already exists. ```APIDOC ## DirectoryRecord.AddFile / AddDirectory ### Description Adds a new `FileRecord` or `DirectoryRecord` inside an existing directory. Returns `false` if a node with the same name already exists and sets `record` to the existing one. ### Method `bool AddFile(string name, out FileRecord record, long size = 0)` `bool AddDirectory(string name, out DirectoryRecord record)` `DirectoryRecord FindOrAddDirectory(string path, out DirectoryRecord record)` ### Parameters #### Path Parameters - `name` (string) - The name of the file or directory to add. - `record` (out FileRecord | out DirectoryRecord) - Output parameter that will contain the newly created or existing record. - `size` (long, Optional) - The pre-allocated size for the new file record. - `path` (string) - The path of the directory to find or create. ### Request Example ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); // Find or create a directory path ggpk.Root.FindOrAddDirectory("MyMod/Textures", out DirectoryRecord texDir); // Add a new file (pre-allocate content size for efficiency) byte[] content = File.ReadAllBytes("custom_texture.dds"); bool added = texDir.AddFile("custom_texture.dds", out FileRecord newFile, content.Length); if (added) { newFile.Write(content); Console.WriteLine($"Created: {newFile.GetPath()}"); } else { Console.WriteLine("File already existed, updating..."); newFile.Write(content); } ``` ``` -------------------------------- ### GGPK.Root Navigation Source: https://context7.com/aianlinb/libggpk3/llms.txt Accesses the root directory of the GGPK file, allowing navigation through the directory tree. Child nodes are loaded lazily and can be accessed by name or path. ```APIDOC ## GGPK.Root — Navigate the directory tree `Root` is the top-level `DirectoryRecord`. Child nodes are loaded lazily on first access and can be indexed by name or Murmur2 name-hash. ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); DirectoryRecord root = ggpk.Root; // Access child by name (case-insensitive Murmur2 hash lookup) var metadata = root["Metadata"] as DirectoryRecord; var file = metadata?["monsters.txt"] as FileRecord; // Navigate with TryFindNode (forward-slash separated path, no leading slash) if (root.TryFindNode("Metadata/monsters.txt", out TreeNode? node) && node is FileRecord fr) Console.WriteLine($"Found: {fr.GetPath()}, size: {fr.DataLength} bytes"); // Enumerate all direct children foreach (TreeNode child in root) Console.WriteLine($"{(child is DirectoryRecord ? "DIR " : "FILE")} {child.Name}"); ``` ``` -------------------------------- ### GGPK.Extract Source: https://context7.com/aianlinb/libggpk3/llms.txt Extracts files recursively from a GGPK archive to the filesystem. It uses a dual-buffer pipelining approach for efficient disk reads and writes. An optional callback can be provided to process each extracted file and to cancel the operation. ```APIDOC ## GGPK.Extract ### Description Extracts all files under a given `TreeNode` (file or directory) to the filesystem. A dual-buffer pipelining approach overlaps disk reads and writes. The optional callback receives each extracted file and can cancel further processing by returning `true`. ### Method `GGPK.Extract(TreeNode node, string destinationPath)` `GGPK.Extract(TreeNode node, string destinationPath, Func callback)` ### Parameters #### Path Parameters - `node` (TreeNode) - The starting node (file or directory) to extract. - `destinationPath` (string) - The path on the filesystem where files will be extracted. - `callback` (Func, Optional) - A function that is called for each extracted file. It receives the `FileRecord` and the extracted `path`. Returning `true` cancels the extraction. ### Request Example ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); // Extract a whole directory if (ggpk.Root.TryFindNode("Metadata", out var dir)) { int count = GGPK.Extract(dir, @"C:\Extracted\Metadata", (fr, path) => { Console.WriteLine("Extracted: " + path); return false; // continue }); Console.WriteLine($"Extracted {count} files."); } // Extract a single file if (ggpk.Root.TryFindNode("Art/2DArt/loading.jpg", out var file)) { GGPK.Extract(file, @"C:\Extracted"); } ``` ``` -------------------------------- ### GGPK.Replace Source: https://context7.com/aianlinb/libggpk3/llms.txt Replaces files within the GGPK archive with content from the filesystem or a ZIP archive. It can also add new files if the `allowAdd` flag is set to true when using a ZIP archive. ```APIDOC ## GGPK.Replace ### Description Replaces files in the GGPK that match paths found on disk or inside a `ZipArchive`. The ZIP variant supports an `allowAdd` flag to insert brand-new files. ### Method `GGPK.Replace(TreeNode root, string sourceDirectoryPath, Func callback)` `GGPK.Replace(TreeNode root, IEnumerable entries, Func callback, bool allowAdd = false)` ### Parameters #### Path Parameters - `root` (TreeNode) - The root node of the GGPK archive. - `sourceDirectoryPath` (string) - The path to the directory on disk containing replacement files. - `entries` (IEnumerable) - An enumerable collection of entries from a ZIP archive. - `callback` (Func, Optional for disk source) - A function called for each replaced file. Receives `FileRecord` and `path`. Returning `true` cancels the operation. - `callback` (Func, Optional for ZIP source) - A function called for each replaced or added file. Receives `FileRecord`, `path`, and a boolean `added` flag. Returning `true` cancels the operation. - `allowAdd` (bool, Optional) - If true, new files from the ZIP archive can be added to the GGPK. ### Request Example ```csharp using System.IO.Compression; using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); // Replace from a folder on disk (only files that exist on disk are updated) int replaced = GGPK.Replace(ggpk.Root, @"C:\MyMod\", (fr, path) => { Console.WriteLine("Replaced: " + path); return false; }); Console.WriteLine($"Replaced {replaced} files from disk."); // Replace (and optionally add) files from a ZIP using var zip = ZipFile.OpenRead("my_mod.zip"); int patched = GGPK.Replace(ggpk.Root, zip.Entries, (fr, path, added) => { Console.WriteLine((added ? "Added: " : "Replaced: ") + path); return false; }, allowAdd: true); Console.WriteLine($"Patched {patched} files from ZIP."); ``` ``` -------------------------------- ### FileRecord.Write Source: https://context7.com/aianlinb/libggpk3/llms.txt Replaces the content of a file record within the GGPK archive. Handles content size changes by relocating the record if necessary. Automatically recomputes SHA-256 hashes. ```APIDOC ## FileRecord.Write — Replace a file's content in GGPK Replaces the binary content of a file entry. If the new content is a different size, the record is automatically relocated to a suitable free slot (or the end of the file). A SHA-256 hash is computed automatically unless supplied explicitly. ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); if (ggpk.Root.TryFindNode("Art/UIImages/loading.png", out var node) && node is FileRecord fr) { byte[] newContent = File.ReadAllBytes("my_custom_loading.png"); // Full replace — hash is computed automatically fr.Write(newContent); // Partial in-place write (offset + length must fit within current DataLength) byte[] patch = { 0xDE, 0xAD, 0xBE, 0xEF }; fr.Write(patch, offset: 0); // replaces first 4 bytes, recomputes hash over full content } // RenewHashes() + Flush() are called automatically on Dispose ``` ``` -------------------------------- ### GGPK.FastCompact Source: https://context7.com/aianlinb/libggpk3/llms.txt Defragments the GGPK file by moving existing records into free space gaps and then truncates any trailing free space. Supports cancellation and progress reporting. ```APIDOC ## GGPK.FastCompact ### Description Fills `FreeRecord` gaps by moving existing records into them, then truncates trailing free space. Supports cancellation and progress reporting. ### Method `void FastCompact(CancellationToken cancellationToken = default, IProgress progress = null)` ### Parameters #### Path Parameters - `cancellationToken` (CancellationToken, Optional) - A token to signal cancellation of the operation. - `progress` (IProgress, Optional) - An interface to report progress, typically the number of remaining free records. ### Request Example ```csharp using System.Threading; using LibGGPK3; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); var progress = new Progress(remaining => Console.Write($"\rFree records remaining: {remaining} ")); ggpk.FastCompact(cts.Token, progress); Console.WriteLine("\nCompact complete."); ``` ``` -------------------------------- ### FileRecord.Read Source: https://context7.com/aianlinb/libggpk3/llms.txt Reads the raw binary content of a file record from the GGPK archive. Supports both allocating and non-allocating reads, as well as reading specific ranges. ```APIDOC ## FileRecord.Read — Read a file's raw content from GGPK Reads the binary content of a file entry directly from the GGPK stream. Use the `Span` overload to avoid allocations when reading repeatedly. ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); if (ggpk.Root.TryFindNode("Art/2DArt/loading.jpg", out var node) && node is FileRecord fr) { // Allocating read (convenient for one-off access) byte[] data = fr.Read(); File.WriteAllBytes("loading.jpg", data); // Non-allocating read into a pre-allocated buffer Span buf = stackalloc byte[fr.DataLength]; fr.Read(buf); // Read a sub-range (e.g., first 16 bytes only) byte[] header = fr.Read(..16); Console.WriteLine($"Magic bytes: {BitConverter.ToString(header)}"); } ``` ``` -------------------------------- ### Persist Index Changes with Index.Save Source: https://context7.com/aianlinb/libggpk3/llms.txt Index.Save serializes index modifications back to the _.index.bin file. It supports saving with default or specific compression settings. ```csharp using LibBundle3; using var index = new Index(@"C:\Games\PathOfExile\Bundles2\_.index.bin"); // ... modify files via FileRecord.Write(...) ... // Save with default Mermaid compressor at Normal compression level index.Save(); // Save with a specific compressor and compression level index.Save(Oodle.Compressor.Leviathan, Oodle.CompressionLevel.Optimal2); ``` -------------------------------- ### Index.Save Source: https://context7.com/aianlinb/libggpk3/llms.txt Persists index changes to disk. This method serializes the updated bundle/file records back into the `_.index.bin` bundle and optionally cleans up empty custom bundles. ```APIDOC ## Index.Save — Persist index changes to disk Serializes the updated bundle/file records back into the `_.index.bin` bundle and optionally cleans up empty custom bundles. ### Parameters - `compressor`: The Oodle compressor to use (optional, defaults to Mermaid). - `compressionLevel`: The Oodle compression level to use (optional, defaults to Normal). ``` -------------------------------- ### Index.Replace Source: https://context7.com/aianlinb/libggpk3/llms.txt Patch bundle files from disk or ZIP archives. This method writes modified file content into a new custom bundle and updates the index. Always call index.Save() when done unless saveIndex: true is passed. ```APIDOC ## Index.Replace — Patch bundle files from disk or ZIP Writes modified file content into a new custom bundle (`LibGGPK3/0.bundle.bin`, etc.) and updates the index. Always call `index.Save()` when done unless `saveIndex: true` is passed. ### Parameters - `index`: The `Index` object to modify. - `source`: Can be a directory path (string) or an enumeration of `ZipArchiveEntry`. - `progressCallback`: A callback function invoked for each replaced file. - `saveIndex`: A boolean indicating whether to save the index immediately after replacement. ``` -------------------------------- ### GGPK.RenewHashes / EraseRootHash Source: https://context7.com/aianlinb/libggpk3/llms.txt Manages the integrity hashes of the GGPK directory tree. RenewHashes recomputes SHA-256 hashes for modified nodes, while EraseRootHash clears the root hash to force a file patch by the game launcher. ```APIDOC ## GGPK.RenewHashes / EraseRootHash ### Description `RenewHashes()` recomputes SHA-256 hashes up the directory tree for all nodes dirtied by writes (called automatically on `Dispose`). `EraseRootHash()` intentionally clears root hashes to force the game launcher to re-patch the file, undoing all modifications. ### Method `void RenewHashes(bool forceRenewRoot = false)` `void EraseRootHash()` ### Parameters #### Path Parameters - `forceRenewRoot` (bool, Optional) - If true, forces the renewal of the root hash as well. ### Request Example ```csharp using LibGGPK3; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); // ... make modifications ... // Update hashes for modified directories only (keeps Root hash untouched) ggpk.RenewHashes(forceRenewRoot: false); // default // To allow the game to self-patch and revert all changes: ggpk.EraseRootHash(); ``` ``` -------------------------------- ### TreeNode.Remove / MoveTo Source: https://context7.com/aianlinb/libggpk3/llms.txt Provides functionality to permanently remove a node (and its children) from the GGPK archive or to move a node to a different parent directory. ```APIDOC ## TreeNode.Remove / MoveTo ### Description Permanently removes a node and all its children from the GGPK (space is returned as `FreeRecord`s), or moves it to a different parent directory. ### Method `void Remove()` `void MoveTo(DirectoryRecord newParent)` ### Parameters #### Path Parameters - `newParent` (DirectoryRecord) - The target directory to move the node to. ### Request Example ```csharp using LibGGPK3; using LibGGPK3.Records; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); // Remove a file if (ggpk.Root.TryFindNode("Metadata/old_file.txt", out var toDelete)) toDelete.Remove(); // marks space as free, updates parent directory // Move a directory into another parent if (ggpk.Root.TryFindNode("OldDir", out var src) && ggpk.Root.TryFindNode("NewParent", out var dest) && dest is DirectoryRecord newParent) src.MoveTo(newParent); ``` ``` -------------------------------- ### Control GGPK Directory Integrity Hashes Source: https://context7.com/aianlinb/libggpk3/llms.txt Manages directory integrity hashes within the GGPK. `RenewHashes` recomputes hashes for modified directories (called automatically on dispose). `EraseRootHash` clears root hashes to force game launcher re-patching. ```csharp using LibGGPK3; using var ggpk = new GGPK(@"C:\Games\PathOfExile\Content.ggpk"); // ... make modifications ... // Update hashes for modified directories only (keeps Root hash untouched) ggpk.RenewHashes(forceRenewRoot: false); // default // To allow the game to self-patch and revert all changes: ggpk.EraseRootHash(); ```