Chorizite.DatReaderWriter 2.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Chorizite.DatReaderWriter --version 2.0.0
                    
NuGet\Install-Package Chorizite.DatReaderWriter -Version 2.0.0
                    
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="Chorizite.DatReaderWriter" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Chorizite.DatReaderWriter" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Chorizite.DatReaderWriter" />
                    
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 Chorizite.DatReaderWriter --version 2.0.0
                    
#r "nuget: Chorizite.DatReaderWriter, 2.0.0"
                    
#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 Chorizite.DatReaderWriter@2.0.0
                    
#: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=Chorizite.DatReaderWriter&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Chorizite.DatReaderWriter&version=2.0.0
                    
Install as a Cake Tool

DatReaderWriter

DatReaderWriter is an open-source library for reading and writing .dat files used by the game Asheron's Call. This tool allows players and developers to access and modify game data files for various purposes, such as creating mods or analyzing game content.

Table of Contents

Features

  • Read/Write Support: Full support for reading and writing AC end-of-retail .dat files (client_portal.dat, client_cell_1.dat, client_local_English.dat, client_highres.dat).
  • Data Structures: Full BTree seeking, insertion, removal, and range queries.
  • Caching: Built-in caching options (OnDemand, None, etc.) to optimize performance.
  • Async API: Full async support for IO operations.
  • Cross-Platform: Targets net8.0, netstandard2.0, and net48.

Installation

Install the Chorizite.DatReaderWriter package from NuGet:

dotnet add package Chorizite.DatReaderWriter

Core Concepts

DatCollection

The DatCollection class is the main entry point if you want to work with the standard set of AC dat files. It manages Portal, Cell, Local, and HighRes databases together, allowing for cross-reference lookups.

Basic Usage

Getting Started

using DatReaderWriter;
using DatReaderWriter.Enums;
using DatReaderWriter.Options;
using DatReaderWriter.DBObjs;

// Open a set of dat file for reading. This will open all the eor dat files as a single collection.
using var dats = new DatCollection(@"C:\Turbine\Asheron's Call\", DatAccessType.Read);

// Read a file explicitly from the Portal database
// We use the specific database (Portal) here for clarity and reliability
Region? region = dats.Portal.Get<Region>(0x13000000u);

// this works as well, directly from the collection
region = dats.Get<Region>(0x13000000u);

if (region != null) {
    Console.WriteLine($"Region Name: {region.RegionName}");
}

// Check iteration of portal dat
Console.WriteLine($"Portal Iteration: {dats.Portal.Iteration.CurrentIteration}");

// Determine type from a file id (using the specific database)
var type = dats.Portal.TypeFromId(0x13000000u);
// Returns DBObjType.Region

// Some types will include QualifiedDataIds<TDBObj> that reference other files
// You can call QualifiedDataId.Get(datCollection) to get the actual file object
if (!dat.TryGet<GfxObj>(0x010005E8, out var gfxObj)) {
    throw new Exception($"Failed to read GfxObj: 0x010005E8");
}
var surface = gfxObj.Surfaces.First().Get(dat);

Update spell names and descriptions

var dats = new DatCollection(@"C:\Turbine\Asheron's Call\", DatAccessType.ReadWrite);

// Access the SpellTable directly via the property on PortalDatabase
var spellTable = dats.SpellTable ?? throw new Exception("Failed to read spell table");

// Update spell name / description
// (Changes are in memory until validly written back)
if (spellTable.Spells.ContainsKey(1)) {
    spellTable.Spells[1].Name = "Strength Other I (updated)";
    spellTable.Spells[1].Description = "Increases the target's Strength by 10 points. (updated)";

    // Write the updated spell table back to the dat
    if (!dats.TryWriteFile(spellTable)) {
        throw new Exception("Failed to write spell table");
    }
}
dats.Dispose();

Rewrite all MotionTables to be 100x speed

var dats = new DatCollection(@"C:\Turbine\Asheron's Call\", DatAccessType.ReadWrite);

// Get all MotionTable IDs
// (This scans the database for files matching the MotionTable type ID range)
var motionTableIds = dats.GetAllIdsOfType<MotionTable>();

foreach (var id in motionTableIds) {
    // Read the file
    if (portalDat.TryGet<MotionTable>(id, out var mTable)) {
        // Update framerates in cycles
        foreach (var cycle in mTable.Cycles.Values) {
            foreach (var anim in cycle.Anims) {
                anim.Framerate *= 100f;
            }
        }
        
        // Update framerates in modifiers
        foreach (var modifier in mTable.Modifiers.Values) {
            foreach (var anim in modifier.Anims) {
                anim.Framerate *= 100f;
            }
        }

        // Write the updated MotionTable back
        dats.TryWriteFile(mTable);
    }
}

Add a new title

static void Main(string[] args)
{
    // new title info
    var enumId = "ID_CharacterTitle_MyNewTitle";
    var titleString = "My New Title";
    
    // open the dat collection in write mode
    var dats = new DatCollection(@"C:\Turbine\Asheron's Call\", DatAccessType.ReadWrite);

    // load the relevant string table and enum mapper
    if (!dats.TryGet<StringTable>(0x2300000E, out var stringTableTitles))
    {
        throw new Exception($"Failed to get titles StringTable 0x2300000E");
    }

    if (!dats.TryGet<EnumMapper>(0x22000041, out var enumTitles))
    {
        throw new  Exception($"Failed to get titles enum mapper 0x22000041");
    }
    
    // check if the enum already exists
    if (enumTitles.IdToStringMap.ContainsValue(enumId)) 
    {
        var existingId = enumTitles.IdToStringMap.First(kv => kv.Value == enumId).Key;
        Console.WriteLine($"Enum ID '{enumId}' already exists with key {existingId}.");
        return;
    }
    
    // first we add a new enum mapper for the new title, at the next available ID
    var newEnumId = enumTitles.IdToStringMap.Keys.Max() + 1;
    enumTitles.IdToStringMap[newEnumId] = enumId;
    
    // now we compute the hash based on the enum string, and add it to the string table
    var newEnumHash = ComputeHash(enumId);
    stringTableTitles.StringTableData[newEnumHash] = new StringTableData()
    {
        Strings = [titleString]
    };
    
    // save the changes back to the dats (no iteration increase, just overwrite)
    if (!dats.Portal.TryWriteFile(enumTitles) || !dats.Local.TryWriteFile(stringTableTitles))
    {
        Console.WriteLine("Failed to write updates back to dat.");
        return;
    }
    
    dats.Dispose();
    
    Console.WriteLine($"Added new title enum {newEnumId} with hash {newEnumHash:X8} and string '{titleString}'");
}

public static uint ComputeHash(string strToHash)
{
    long result = 0;

    if (strToHash.Length > 0)
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        byte[] str = Encoding.GetEncoding(1252).GetBytes(strToHash);

        foreach (sbyte c in str)                
        {
            result = c + (result << 4);

            if ((result & 0xF0000000) != 0)
                result = (result ^ ((result & 0xF0000000) >> 24)) & 0x0FFFFFFF;
        }
    }

    return (uint)result;
}

Known Issues

  • RenderMaterial files are not yet supported.
  • LayoutDesc files are supported, but the structure will need to be cleaned up in future versions.

Contributing

We welcome contributions from the community! If you would like to contribute to DatReaderWriter, please follow these steps:

  1. Fork the repository.
  2. Create a new branch (git checkout -b feature-branch).
  3. Make your changes.
  4. Commit your changes (git commit -am 'Add some feature').
  5. Push to the branch (git push origin feature-branch).
  6. Create a new Pull Request.

Thanks

In no particular order, thanks to ACE team, GDLE team, gmriggs, OptimShi, paradox, and Yonneh. Used lots of projects as a reference for different parts.

License

This project is licensed under the MIT License. See the LICENSE.txt file for details.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 is compatible.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Chorizite.DatReaderWriter:

Package Downloads
Chorizite.Core

Chorizite core library

Chorizite.DatReaderWriter.Extensions

Extensions for Chorizite.DatReaderWriter for common tasks / helpers.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.2 140 2/15/2026
2.1.1 93 2/14/2026
2.1.0 150 2/3/2026
2.0.0 140 1/31/2026
1.0.1 248 10/31/2025
1.0.0 498 5/14/2025

## What's Changed
* Source Gen / String types / HashTables / QualifiedDataIds / Async api by @trevis in https://github.com/Chorizite/DatReaderWriter/pull/56
**Full Changelog**: https://github.com/Chorizite/DatReaderWriter/compare/release/1.0.1...release/2.0.0