Writing scripts to do the work for you

First, read SCRIPTING. Since an example is more worth than a thousand words, here is a quick introduction in the form of a script that handles input files from ARGV, does nothing with them, and then saves them back to the same file from which they came from.

#!/usr/bin/env nwn-dsl

ARGV.each_with_index {|file, index|
  # The script will abort hard if file is not an ARE.
  # This looks up the data_type, which are usually the
  # first four bytes of a file.
  gff = need file, :are

  # This will save gff back to 'file'.
  save gff

  # This will prefix all lines printed with log with
  # a percentage. It's usage is optional, and the sole
  # reason for using each_with_index instead of each above.
  progress index
}

To run this example, type the following in a directory containing some .are files, with script.rb being the name you saved the above script in.

nwn-dsl path/to/script.rb *.are

The shebang (#!/..) is for unixoid systems and Cygwin, and as such optional.

For all available commands to nwn-dsl scripts, see NWN::Gff::Scripting.

All code snippets shown here assume that you put include NWN in your application. It is not needed for nwn-dsl, which imports that namespace for you.

Accessing GFF paths

You can access GFF paths within structs by using the overloaded division operator.

#!/usr/bin/env nwn-dsl

ARGV.each_with_index {|file, index|
  gff = need file, :are

  log (gff / 'Name/0')

  progress index
}

For a full reference of the path syntax, see NWN::Gff::Struct#by_path.

You cannot use paths to assign new values to structs - you can only modify existing values:

(gff / 'Name').v[0] = "New Localized Name with Language ID 0"

Wrong, will raise error:

(gff / 'Name/0') = "New .."

Please note that using the [] method will NOT evaluate paths, just access labels in the CURRENT struct (which is actually a hash).

Creating new GFF Structs and Elements

You can add new fields to existing structs via NWN::Gff::Struct#add_field:

gff.add_field 'LocalVersion', :int, 1

# This will print "1"
log (gff / 'LocalVersion$')

You can also the dynamic methods to save some typing:

gff.add_int 'LocalVersion', 1

You can just as well create whole GFF structures on the fly:

Gff::Struct.new do |s|
  s.add_byte 'ImaByte', :int, 1
  list = s.add_list 'ImaList', [] do |l|
    l.add_struct(1) do |ss|
      ss.add_byte 'ImaByteToo', 2
    end
  end
end

Further reading: NWN::Gff::Struct#add_field, NWN::Gff::List#add_struct.

Working with .2da files

Working with TwoDA files is easy and painless:

#!/usr/bin/env nwn-dsl

data = IO.read('path/to/baseitems.2da')
table = TwoDA::Table.parse(data)

# This will print "twobladedsword" with NWN1 1.69
log table[12].Label

# You can re-format (and save) any valid table:
File.open("/tmp/out", "wb") {|f|
  f.write(table.to_2da)
  # OR
  table.write_to(f)
}

For more documentation, see NWN::TwoDA::Table and NWN::TwoDA::Row.

You can also set up a default location for 2da files, after which you can simply use the following to read 2da files:

table = TwoDA.get('baseitems')

You can set up the TwoDA::Cache by either setting the environment variable (recommended, see SETTINGS), or like this (see NWN::TwoDA::Cache.setup):

TwoDA::Cache.setup("path_a:path_b/blah:path_c")

Accessing .tlk data

You can access individual .tlk files, or use a TlkSet, which will emulate the way NWN1/2 reads it’s .tlk files.

Read a simple .tlk file:

io = File.open("/path/to/dialog.tlk", "rb")
tlk = NWN::Tlk::Tlk.new(io)

Note that Tlk::Tlk seeks and reads from io as needed, so if you close the file handle, any further accesses will fail.

# Retrieve strref 12
tlk[12][:text]

# Retrieve the attached sound resref, if any:
tlk[12][:sound]

# prints the highest strref used
log tlk.highest_id

# Add a new strrref.
new_strref = tlk.add 'New text'

# And save the new TLK somewhere else.
File.open("/tmp/new.tlk", "wb") {|another_io|
  tlk.write_to(another_io)
}

Now read-only access with a TlkSet:

# The arguments are dialog.tlk, dialogf.tlk,
# custom.tlk and a customf.tlk each wrapped in
# a Tlk::Tlk as shown above.
set = Tlk::TlkSet.new(tlk, tlkf, custom, customf)

# Retrieve str_ref 12 as the female variant (dialogf.tlk)
# if present, :male otherwise.
log set[12, :female]

You cannot use TlkSet to write out .tlk files or modify existing entries - it is merely a wrapper.

Accessing .key index files

A key file is an index into all shipped game resources.

key = Key::Key.new(File.new("/path/to/chitin.key", "r"), "/path/to/")

This will lookup all indexed bif files and the resources contained within.

Resource Manager

The resource manager can be used to simulate a NWN-style resource manager to read files from .key/bifs, override, haks, and similar sources. Files are located in the reverse order that their containers are added to the Manager.

Example detailing the usual NWN lookup procedure:

nwn_path = "/path/to/nwn/"
mgr = Resources::Manager.new

# First, all the base data files.
for key in %w{chitin.key xp1.key xp1patch.key xp2.key xp2patch.key xp3.key}
  mgr.add_container(Key::Key.new(File.new(nwn_path + key, "r"), nwn_path))
end

# Override
mgr.add_container(Resources::DirectoryContainer.new(nwn_path + "override"))

# All custom haks
for hak in %w{a.hak b.hak c.hak}
  mgr.add_container(Erf::Erf.new(File.new(nwn_path + "hak/" + hak, "r")))
end

# Now you can retrieve any indexed file:
puts mgr.get("actions.2da")

Note that initialising the whole Resource Manager this way takes a few seconds depending on IO and CPU speed.