UntitledRpg.LibraryFile API Guide
The UntitledRpg.LibraryFile library provides a high-level API for reading, streaming, validating, and generating .urpglib container files. The container bundles metadata (PackageManifest) and compressed .tar payloads containing game definitions and configurations.
Core Types
UrpglibFile: Static facade providing primary entry points for inspection, extraction, packaging, and validation.UrpglibPackage: An open package instance providing access to header metadata, manifest information, and payload contents.UrpglibPackageEntry: A single file entry within the payload stream, offering zero-allocation stream access, UTF-8 string parsing, or raw byte loading.UrpglibPackageBuilder: Fluent builder for creating new.urpglibarchives from directories, files, or in-memory byte arrays.UrpglibValidationResult: Diagnostic report containing status flags, schema versions, manifest data, and error details.
Common Workflows
1. Fast Metadata Peeking
Read package metadata without decompressing or allocating the payload:
using UntitledRpg.LibraryFile;
// Inspect metadata from a file path
PackageManifest manifest = await UrpglibFile.ReadManifestAsync("CoreGameplay.urpglib");
Console.WriteLine($"Package: {manifest.Name} v{manifest.Version}");
Console.WriteLine($"Author: {manifest.AuthorName} ({manifest.AuthorId})");
Console.WriteLine($"Dependencies: {manifest.Dependencies.Count}");
// Inspect metadata directly from an open Stream
await using var networkStream = GetPackageStream();
PackageManifest remoteManifest = await UrpglibFile.ReadManifestAsync(networkStream);
2. Streaming Payload Entries (Database Ingestion)
Stream entries sequentially to parse files directly into records without holding the entire archive in memory:
using UntitledRpg.LibraryFile;
await using var package = await UrpglibFile.OpenReadAsync("CoreGameplay.urpglib");
// Iterate over entries directly from the decompressed Tar stream
await foreach (UrpglibPackageEntry entry in package.ReadEntriesAsync())
{
if (entry.Name.EndsWith(".toml", StringComparison.OrdinalIgnoreCase))
{
// Read directly as UTF-8 text for TOML/JSON parsers
string tomlText = await entry.ReadAsStringAsync();
GameDefinition definition = TomlParser.Parse(tomlText);
await database.UpsertDefinitionAsync(definition);
}
else if (entry.Name.EndsWith(".bin", StringComparison.OrdinalIgnoreCase))
{
// Or stream raw bytes
byte[] rawBytes = await entry.ReadAsBytesAsync();
await ProcessBinaryAssetAsync(entry.Name, rawBytes);
}
}
3. Extracting an Archive to Disk
Unpack an archive back into its directory structure:
using UntitledRpg.LibraryFile;
// One-liner extraction
await UrpglibFile.ExtractToDirectoryAsync(
packagePath: "CoreGameplay.urpglib",
destinationDirectory: "./GameData/Extracted",
overwrite: true);
// Or extract from an already opened package
await using var package = await UrpglibFile.OpenReadAsync("CoreGameplay.urpglib");
await package.ExtractToDirectoryAsync("./GameData/Extracted", overwrite: true);
4. Validating Archive Integrity
Verify binary magic bytes, schema versions, manifest JSON deserialization, and payload decompression without throwing exceptions:
using UntitledRpg.LibraryFile;
UrpglibValidationResult result = await UrpglibFile.ValidateAsync("ModPackage.urpglib", validatePayload: true);
if (!result.IsValid)
{
Console.WriteLine("Package is invalid or corrupt:");
foreach (string error in result.Errors)
{
Console.WriteLine($" - {error}");
}
return;
}
Console.WriteLine($"Valid package! Found {result.EntryCount} payload entries.");
Console.WriteLine($"Payload compression: {result.Header?.PayloadCompression}");
Console.WriteLine($"Manifest Schema: {result.Header?.ManifestSchemaVersion}");
5. Packaging and Creating Archives
Create archives using either the one-liner helper or the fluent builder.
Quick Directory Packaging:
using UntitledRpg.LibraryFile;
var manifest = new PackageManifest
{
Name = "Base Items Mod",
AuthorName = "Dev Studio",
Version = new Version(1, 0, 0),
Description = "Base equipment and item configurations"
};
await UrpglibFile.CreateFromDirectoryAsync(
sourceDirectory: "./RawContent/Items",
destinationPackagePath: "./Output/Items.urpglib",
manifest: manifest,
compression: PayloadCompressionType.Gzip);
Advanced Package Assembly:
using System.Text;
using UntitledRpg.LibraryFile;
var manifest = new PackageManifest
{
Name = "Custom Encounter",
AuthorName = "Scenario Designer",
Version = new Version(2, 1, 0)
};
byte[] generatedConfig = Encoding.UTF8.GetBytes("difficulty = 5\nenemy_count = 12");
await new UrpglibPackageBuilder()
.WithManifest(manifest)
.WithCompression(PayloadCompressionType.Gzip)
.AddDirectory("./Content/Monsters", entryPrefix: "monsters")
.AddFile("./Shared/LootTables.toml", entryName: "tables/loot.toml")
.AddBytes(generatedConfig, entryName: "rules/generated_encounter.toml")
.BuildAsync("./Output/Encounter.urpglib");
6. In-Memory Random Access
For modding tools or editors that need random access by file path rather than batch-streaming through all entries:
using UntitledRpg.LibraryFile;
await using var package = await UrpglibFile.OpenReadAsync("CoreGameplay.urpglib");
// Unpacks all files into memory at once
IReadOnlyDictionary<string, byte[]> fileMap = await package.ReadAllToMemoryAsync();
if (fileMap.TryGetValue("items/weapons/sword.toml", out byte[]? swordData))
{
string swordToml = Encoding.UTF8.GetString(swordData);
Console.WriteLine(swordToml);
}
Best Practices
Use
ReadEntriesAsync()for Batch Processing: Streaming avoids buffering entire archives into memory when pushing records into databases or search indexes.Use
ReadManifestAsync()for Fast Scans: Use this in mod loaders or main menus to build metadata catalogs without paying decompression overhead.Handle Disposal Cleanly:
UrpglibPackageimplements bothIDisposableandIAsyncDisposable. When using asynchronous flows, preferawait using var package = await UrpglibFile.OpenReadAsync(...)to ensure internal streams and file locks release cleanly.age.