NyxAssets 0.2.8

dotnet add package NyxAssets --version 0.2.8
                    
NuGet\Install-Package NyxAssets -Version 0.2.8
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NyxAssets" Version="0.2.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NyxAssets" Version="0.2.8" />
                    
Directory.Packages.props
<PackageReference Include="NyxAssets" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NyxAssets --version 0.2.8
                    
#r "nuget: NyxAssets, 0.2.8"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NyxAssets@0.2.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NyxAssets&version=0.2.8
                    
Install as a Cake Addin
#tool nuget:?package=NyxAssets&version=0.2.8
                    
Install as a Cake Tool

NyxAssets

.NET 10 library for reading and writing Nyx-style client data: Nyx.dat (object definitions) and Nyx.spr / Nyx.assets (sprites). On-disk layouts match Asset Editor.

Install

dotnet add package NyxAssets

Requires .NET 10. You need paired client files (*.dat + *.spr or *.assets) at runtime.


Namespaces & entry points

Namespace Start here Role
NyxAssets.Client ClientAssetBundle Load .dat + sprites together; decode & export
NyxAssets.Things ThingCatalog, ThingType Items, outfits, effects, missiles from .dat / JSON
NyxAssets.Things.Exchange ThingDocument, ObdThingCodec Single-thing JSON + Object Builder .obd
NyxAssets.Things.Frames ThingFrameResolver Direction, walk phase, stack count → sprite ids
NyxAssets.Sprites SpriteArchive, ISpriteSource .spr / .assets random-access decode
NyxAssets.Utils SpriteImageExporter Decoded pixels → PNG / JPEG / BMP

Typical flow: ClientAssetBundleGetItem / GetOutfitThingFrameResolverDecodeSpriteById → optional SpriteImageExporter.

Full API reference: docs/API.md in this package, or on GitHub:
https://github.com/Tofame/NyxFramework/blob/main/NyxAssets/docs/API.md


Quick start

using NyxAssets.Client;
using NyxAssets.Things;
using NyxAssets.Sprites;

var options = new ClientDataReadOptions
{
    ClientVersion = new ClientDataVersion(1098),
    TransparentSprites = true,
};

// Opens .dat (memory) + .spr (memory-mapped). Dispose when done.
using ClientAssetBundle bundle = ClientAssetBundle.OpenFromFiles("Nyx.dat", "Nyx.spr", options);

// Metadata
Console.WriteLine($"Items up to id {bundle.Things.ItemCount}, sprites: {bundle.Sprites.SpriteCount}");

// Look up a thing definition
ThingType coin = bundle.GetItem(2148);

// Decode one sprite by id (ids are 1-based, from ThingFrameGroup.SpriteIds)
byte[] rgba = bundle.DecodeSpriteById(100);   // allocates byte[4096]
// same as: bundle.GetSpriteRgba(100)  or  bundle.Sprites.DecodeSpriteById(100)

// Or decode without allocating (preferred in loops):
Span<byte> scratch = stackalloc byte[SpritePixelCodec.RgbaBufferLength];
bundle.TryDecodeSpriteById(100, scratch);

DecodeSpriteById lives on ClientAssetBundle, SpriteArchive, AssetArchive, and the ISpriteSource interface (bundle.Sprites). Returns 32×32 RGBA (4096 bytes, R,G,B,A per pixel). See Sprite decoding below.

Use LoadFromFiles instead of OpenFromFiles if you want the whole .spr in a byte[] (no dispose needed for the map).


Common examples

1 — Load only .dat or only .spr

using NyxAssets.Things;
using NyxAssets.Sprites;

var options = new ClientDataReadOptions { ClientVersion = new ClientDataVersion(1098), TransparentSprites = true };

ThingCatalog catalog = ThingCatalog.Load(File.ReadAllBytes("Nyx.dat"), options);
ThingType outfit = catalog.GetOutfit(128);

using SpriteArchive spr = SpriteArchive.OpenReadOnlyFile("Nyx.spr", options);
uint count = spr.SpriteCount;
byte[] pixels = spr.DecodeSpriteById(1);

2 — .dat + .assets (binary catalog + modern sprites)

using var bundle = ClientAssetBundle.OpenAssetsFromFiles("Nyx.dat", "Nyx.assets", options);
// or auto by extension on the sprite path:
using var bundle = ClientAssetBundle.OpenFromFilesAuto("Nyx.dat", "Nyx.assets", options);

OpenFromFilesAuto only auto-detects .spr vs .assets — the catalog path must still be .dat.

3 — things.json + .assets (no .dat, no .spr)

Use this when you ship a JSON thing catalog and a ZSTD sprite archive only. There is no single helper yet — load each side, then construct the bundle:

using NyxAssets.Client;
using NyxAssets.Sprites;
using NyxAssets.Things;

var options = new ClientDataReadOptions
{
    ClientVersion = new ClientDataVersion(1098),
    TransparentSprites = true,
};

ThingCatalog catalog = ThingCatalog.LoadJson("things.json", options);
AssetArchive sprites = AssetArchive.OpenReadOnlyFile("Nyx.assets");

using ClientAssetBundle bundle = new ClientAssetBundle(catalog, sprites, disposeSprites: true);

ThingType coin = bundle.GetItem(2148);
byte[] rgba = bundle.DecodeSpriteById(100);
  • LoadJson — metadata + sprite id layouts from JSON.
  • OpenReadOnlyFile — memory-maps .assets; disposeSprites: true so Dispose() releases the map.
  • Pass preloadPages: true to OpenReadOnlyFile if you want all ZSTD pages decompressed up front.

4 — Load client data with .otfi configuration

If your assets directory contains a Tibia.otfi (OTClient format configuration), you can parse it to automatically configure transparency, extended sprite IDs, and other features:

using NyxAssets.Client;

// Load and parse the OTFI file
var otfi = OtfiFile.Load("Tibia.otfi");

// Generate ClientDataReadOptions (version will be inferred if not specified)
var options = otfi.ToReadOptions(); // or otfi.ToReadOptions(1098) to explicitly set version

// Use the resolved options to load assets
using var bundle = ClientAssetBundle.OpenFromFiles(
    otfi.MetadataFile ?? "Tibia.dat",
    otfi.SpritesFile ?? "Tibia.spr",
    options);

In-memory variant (no file map to dispose):

ThingCatalog catalog = ThingCatalog.LoadJson(jsonBytes.AsMemory(), options);
AssetArchive sprites = AssetArchive.Load(assetsBytes.AsMemory());
using var bundle = new ClientAssetBundle(catalog, sprites, disposeSprites: false);

4 — Item with stack count → correct sprite

using NyxAssets.Things.Frames;

ThingType coin = bundle.GetItem(2148);
ThingFrameSelection frame = ThingFrameResolver.GetItemFrame(coin, new ItemFrameRequest { StackCount = 37 });

foreach (ThingFrameSelection.SpriteSlot slot in frame.EnumerateSpriteSlots())
{
    bundle.TryDecodeSpriteById(slot.SpriteId, scratch);
}

5 — Outfit facing + walking animation

ThingType player = bundle.GetOutfit(128);
var request = new OutfitFrameRequest
{
    Direction = (int)Direction4.South,
    WalkPhase = 2,
    AddonMask = 0xFF,
    Mounted = false,
};
ThingFrameSelection frame = ThingFrameResolver.GetOutfitFrame(player, request);
uint[] spriteIds = frame.GetSpriteIds();

6 — Export sprite or spritesheet to PNG

using NyxAssets.Utils;

// Single sprite
bundle.TryExportSpritePng(spriteId: 100, filePath: "sprite.png");

// Whole thing (all frame groups, Asset Editor layout)
ThingType item = bundle.GetItem(2148);
bundle.TryExportThingSpriteSheetPng(item, "item_sheet.png");

7 — .dat ↔ JSON

var options = new ClientDataReadOptions { ClientVersion = new ClientDataVersion(1098), TransparentSprites = true };

// Export
ThingCatalog catalog = ThingCatalog.Load(File.ReadAllBytes("Nyx.dat"), options);
catalog.ExportJson("things.json", options);

// Import
ThingCatalog fromJson = ThingCatalog.LoadJson("things.json", options);

8 — Build or convert sprite archives

using NyxAssets.Sprites;

// .spr → .assets
AssetArchiveWriter.ConvertSprToAssets("Nyx.spr", "Nyx.assets", extendedSpriteIds: true, transparentPixels: true);

// Write .spr from RGBA buffers (index 0 unused; index 1 = sprite id 1)
SpriteSheetCompiler.WriteToStream(outputStream, sprSignature: 0x12345678, extendedSpriteIds: true, transparentPixels: true, rgbaPerSpriteIdOneBased);

9 — Add or remove sprites and things in memory

using NyxAssets.Client;
using NyxAssets.Sprites;
using NyxAssets.Things;

var options = new ClientDataReadOptions { ClientVersion = new ClientDataVersion(1098), TransparentSprites = true };
using var bundle = ClientAssetBundle.OpenFromFiles("Nyx.dat", "Nyx.spr", options);

// Add or replace a sprite by id (1-based) and write the updated archive back out.
bundle.PutSprite(42, rgbaBuffer);

// Remove an existing sprite slot (returns false when the slot did not exist)
bundle.RemoveSprite(7);

// Add or replace a thing definition. New ids must be contiguous with the section bounds.
var newItem = new ThingType { Id = bundle.Things.ItemCount + 1, Kind = ThingKind.Item };
newItem.FrameGroups.Add(new ThingFrameGroup { SpriteIds = new uint[] { 42 }, Width = 1, Height = 1, ExactSize = 32, Layers = 1, PatternX = 1, PatternY = 1, PatternZ = 1, Frames = 1 });
bundle.PutItem(newItem);

// Remove an existing definition by id.
bundle.RemoveItem(100);

PutSprite / RemoveSprite are available on ClientAssetBundle, SpriteArchive, AssetArchive, and ISpriteSource implementations that support mutation. RemoveItem / RemoveOutfit / RemoveEffect / RemoveMissile are available on ThingCatalog and ClientAssetBundle.


Sprite decoding

Method Type Description
byte[] DecodeSpriteById(uint spriteId) ClientAssetBundle, ISpriteSource Allocates byte[4096]. Throws if invalid.
byte[] GetSpriteRgba(uint spriteId) ClientAssetBundle Alias for DecodeSpriteById.
byte[] GetSpriteRgbaPixels(uint spriteId) SpriteArchive Alias for DecodeSpriteById.
bool TryDecodeSpriteById(uint spriteId, Span<byte> rgbaDestination) all above Writes into your buffer (≥ 4096 bytes). Returns false if invalid.
bool IsEmptySprite(uint spriteId) ISpriteSource Slot has no pixel data.
uint SpriteCount { get; } ISpriteSource Valid ids: 1 … SpriteCount.

Constants: SpritePixelCodec.RgbaBufferLength = 4096, SpritePixelCodec.SpriteEdgeLength = 32.


API at a glance

Method signatures for the types you use most. Parameter names reflect the real API.

ClientAssetBundle (NyxAssets.Client)

// Construction / load
ClientAssetBundle(ThingCatalog things, ISpriteSource sprites, bool disposeSprites = false)
static ClientAssetBundle Load(ReadOnlyMemory<byte> dat, ReadOnlyMemory<byte> spr, ClientDataReadOptions options)
static ClientAssetBundle LoadFromFiles(string datPath, string sprPath, ClientDataReadOptions options)
static ClientAssetBundle OpenFromFiles(string datPath, string sprPath, ClientDataReadOptions options)
static ClientAssetBundle OpenAssetsFromFiles(string datPath, string assetsPath, ClientDataReadOptions options, bool preloadPages = false)
static ClientAssetBundle OpenFromFilesAuto(string datPath, string spritePath, ClientDataReadOptions options, bool preloadSprites = false)

ThingCatalog Things { get; }
ISpriteSource Sprites { get; }

// Decode
bool TryDecodeSpriteById(uint spriteId, Span<byte> rgbaDestination)
byte[] DecodeSpriteById(uint spriteId)
byte[] GetSpriteRgba(uint spriteId)

// Catalog shortcuts (throw KeyNotFoundException)
ThingType GetItem(uint id)
ThingType GetOutfit(uint id)
ThingType GetEffect(uint id)
ThingType GetMissile(uint id)

// Export
bool TryExportSpritePng(uint spriteId, string filePath)
bool TryExportSpriteJpeg(uint spriteId, string filePath, int quality = 90)
bool TryExportSpriteBmp(uint spriteId, string filePath)
bool TryExportFrameGroupSpriteSheetPng(ThingFrameGroup group, string filePath)
bool TryExportThingSpriteSheetPng(ThingType thing, string filePath)
// ... Jpeg/Bmp variants on same types

void Dispose()

ThingCatalog (NyxAssets.Things)

static ThingCatalog Load(ReadOnlyMemory<byte> datFile, ClientDataReadOptions options)
static ThingCatalog LoadJson(ReadOnlyMemory<byte> jsonData, ClientDataReadOptions options)
static ThingCatalog LoadJson(string filePath, ClientDataReadOptions options)

ThingType? TryGetItem(uint id) / ThingType GetItem(uint id)
ThingType? TryGetOutfit(uint id) / ThingType GetOutfit(uint id)
ThingType? TryGetEffect(uint id) / ThingType GetEffect(uint id)
ThingType? TryGetMissile(uint id) / ThingType GetMissile(uint id)

IEnumerable<ThingType> EnumerateItems()
IEnumerable<ThingType> EnumerateOutfits()
IEnumerable<ThingType> EnumerateEffects()
IEnumerable<ThingType> EnumerateMissiles()

void PutItem(ThingType thing, bool rebuildArrays = true)   // + PutOutfit, PutEffect, PutMissile
void WriteDatTo(Stream output, ClientDataReadOptions formatOptions, uint? datSignatureOverride = null)
void ExportJson(string filePath, ClientDataReadOptions options, uint? signatureOverride = null, string? itemsXmlPath = null)
void LoadItemsXml(string filePath)

uint DatSignature { get; set; }
uint ItemCount { get; }   // inclusive last id, not count of defined items
uint OutfitCount { get; }
uint EffectCount { get; }
uint MissileCount { get; }
DatThingFormat DatFormat { get; set; }

ThingType / ThingFrameGroup

// ThingType — flags (IsGround, Stackable, Rotatable, …), FrameGroups, ExtraProperties
ThingFrameGroup? GetFrameGroup(int index)
uint[] GetSpriteIdsForOutfit(uint? innerWidth = null, …, int frameGroupIndex = 0)

// ThingFrameGroup — layout + sprite id list
uint GetSpriteId(uint layer, uint patternX, uint patternY, uint patternZ, uint frame)
bool TryGetSpriteId(uint layer, uint patternX, uint patternY, uint patternZ, uint frame, out uint spriteId)
uint[] GetSpriteIds(uint? innerWidth = null, …)
uint[] SpriteIds { get; set; }
uint Width, Height, Layers, PatternX, PatternY, PatternZ, Frames { get; set; }

ThingFrameResolver (NyxAssets.Things.Frames)

ThingFrameSelection GetOutfitFrame(ThingType outfit, OutfitFrameRequest request = default)
ThingFrameSelection GetItemFrame(ThingType item, ItemFrameRequest request = default)
ThingFrameSelection GetEffectFrame(ThingType effect, EffectFrameRequest request = default)
ThingFrameSelection GetMissileFrame(ThingType missile, MissileFrameRequest request = default)
IEnumerable<ThingFrameSelection> EnumerateOutfitAddonFrames(ThingType outfit, OutfitFrameRequest request = default)

uint GetEffectFrameIndex(ThingType effect, float elapsedMs, int ticksPerFrame = 75)
uint GetCyclicFrameIndex(ThingType thing, float elapsedMs, int ticksPerFrame = 333)

Request structs: OutfitFrameRequest (Direction, WalkPhase, AddonMask, Mounted), ItemFrameRequest (StackCount, PatternX, PatternY, Frame), EffectFrameRequest (Frame, TileX, TileY), MissileFrameRequest (Direction, TileDeltaX, TileDeltaY).

ISpriteSource / SpriteArchive / AssetArchive (NyxAssets.Sprites)

// ISpriteSource
uint SpriteCount { get; }
bool TryDecodeSpriteById(uint spriteId, Span<byte> rgbaDestination)
byte[] DecodeSpriteById(uint spriteId)
bool IsEmptySprite(uint spriteId)
void Dispose()

// SpriteArchive
static SpriteArchive Load(ReadOnlyMemory<byte> sprFile, ClientDataReadOptions options, bool preloadSprites = false)
static SpriteArchive OpenReadOnlyFile(string sprPath, ClientDataReadOptions options, bool preloadSprites = false)

// AssetArchive
static AssetArchive Load(ReadOnlyMemory<byte> fileData, bool preloadPages = false)
static AssetArchive OpenReadOnlyFile(string filePath, bool preloadPages = false)
void SetMaxCachedPages(int count)

ClientDataReadOptions

required ClientDataVersion ClientVersion { get; init; }
bool TransparentSprites { get; init; }
bool? ExtendedSpriteIds { get; init; }
bool? ImprovedAnimations { get; init; }
bool? OutfitFrameGroups { get; init; }
DatThingFormat? DatThingFormatOverride { get; init; }

What you get (types)

Type Purpose
ClientAssetBundle .dat + sprite source in one handle
ThingCatalog All items, outfits, effects, missiles
ThingType / ThingFrameGroup One definition + sprite layout
ThingFrameResolver Game-style frame → sprite id resolution
SpriteArchive Legacy .spr (RLE, lookup table)
AssetArchive Modern .assets (ZSTD pages)
SpriteSheetCompiler / AssetArchiveWriter Write / convert sprite files
SpriteImageExporter / ThingSpriteSheetExporter PNG/JPEG/BMP export
ThingDocument / ObdThingCodec Single-thing JSON + .obd import/export

Single-thing exchange

Import one Object Builder export or share one definition between projects:

using NyxAssets.Things.Exchange;

var doc = ObdThingCodec.Read("item_test.obd");
doc.ImportInto(catalog, assignId: 35000);

ThingDocumentJsonCodec.Write("item.json", doc);

See docs/development/thing-exchange.md.


Documentation

Doc Description
docs/API.md Every public type and method (detailed)
docs/guides/usage.md Longer usage guide
docs/guides/supported-clients.md Client version / .dat tiers
docs/development/frame-resolver.md Frame resolver examples
docs/development/thing-exchange.md Single-thing JSON + OBD import/export

On NuGet: open docs/API.md from the package folder in your IDE, or browse on GitHub:
https://github.com/Tofame/NyxFramework/tree/main/NyxAssets/docs


Build from source

dotnet build NyxAssets/NyxAssets.csproj

Part of NyxFramework.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.8 167 8/1/2026
0.2.7 99 7/30/2026
0.2.6 116 7/25/2026
0.2.5 102 7/14/2026
0.2.4 108 7/12/2026
0.2.3 108 7/12/2026
0.2.2 115 7/6/2026
0.2.1 111 7/5/2026
0.2.0 103 7/5/2026
0.1.0.1 114 7/3/2026
0.1.0 115 7/3/2026