Skip to content

pull-request-perhaps/cl-nbt

 
 

Repository files navigation

cl-nbt

This is a pure Common Lisp library for editing binary files generated by Minecraft. It is a work in progress, so expect minimal error-checking, few optimizations, and so on. Back up your Minecraft save files before use.

Once the low-level stuff is completely done, the idea is to create a higher-level DSL for manipulating Minecraft worlds straight from the REPL. Think NBTExplorer and MCEdit filters rolled into one, but with the flexibility of Common Lisp.

I have already used this library to implement something akin to the Minecraft Land Generator (which I will share soon) and there are naturally many more potential uses.

As yet, both compressed and uncompressed .dat files, whose contents consist entirely of a single Named Binary Tag structure - see here - are supported. Support for region (.mca) files is partially implemented, but there are some technical challenges posed by this that I have yet to overcome.

Installation

With Quicklisp it's trivial:

$  cd ~/quicklisp/local-projects/
$  git clone https://github.com/pnoom/cl-nbt.git

Start up SLIME and run:

CL-USER> (ql:quickload :cl-nbt)

Usage

The API is not set in stone (hence the lack of exported symbols - just use (in-package :cl-nbt) for now), but the functions should be pretty self-explanatory. Read a .dat file from your Minecraft world's save directory like so:

CL-USER> (defparameter level-dat
           (read-dat-file "~/.minecraft/saves/my-world/level.dat"))
==> LEVEL-DAT

Access a nested tag by providing a list of names/indexes of the tags that make up its "path", eg:

CL-USER> (get-tag level-dat "Data" "RandomSeed")
 ==> #<TAG-LONG "RandomSeed">
CL-USER> (payload *)
 ==> -981234193418740174
CL-USER> (setf (payload (get-tag level-dat
                          "Data" "GameRules" "naturalRegeneration"))
	       "false")
 ==> "false"
CL-USER> (write-dat-file "~/.minecraft/saves/my-world/level.dat" level-dat)
 ==> T

The above first returns the value of the world's seed number, then disables player health regeneration, then writes the changes back out in-place.

Contributing

Any feedback or contributions would be very welcome. Beyond docstrings and the odd comment there's no documentation yet, but the code is quite simple, and the NBT spec will clear up any confusion. Some things to note:

  • I adapted a couple of functions from the binary-types library, but wrote the rest of the binary I/O stuff from scratch, because at the time, I couldn't actually figure out how to use the library. My code should be pretty easy to follow though.

  • AFAIK the compression ratio of data compressed by the salza2 library (see here) is lower than that of the data compressed by Minecraft, so the files written by cl-nbt may be slightly larger than the originals. There's no way to change the ratio at the moment, unfortunately. For .dat files this is no problem, but it may prove problematic for .mca files.

  • I haven't yet specialized the arrays that form the payload of tag-byte-array and tag-int-array, so reading a file with many such tags uses a lot more memory than it should. To make matters worse, .mca files can (so far) only be read and written all at once (as opposed to being able to read chunks from the file individually) so if they're over a couple of MB in size, your Lisp may keel over(!) You could always assign more dynamic space to your Lisp on startup, but that shouldn't be necessary once the code has basic space optimizations.

  • Comparing the data structure produced by read-mca-file and NBTExplorer for the same .mca file, the order of the chunk entries seems to differ: in one, the chunks are ordered by increasing (x,z), while in the other it's (z,x). Writing it out with write-dat-file and reading it in again changes the order once more, so one or both of those functions does not preserve the order of the entries, right? But I can't see how I've deviated from the spec...

Happy hacking, mining and crafting! :)

Releases

No releases published

Packages

No packages published

Languages

  • Common Lisp 100.0%