Dink's file formats v0.9

After reading WC's guide and putting it into practice only to become terribly disappointed with its gross inaccuracy, I decided to make my own. Finally after many long years and several false starts, here it is before I forget to write it down. The following chapters contain a fairly comprehensive analysis of most of the file formats found within Dink Smallwood's game data describing their layout and size. Hopefully by the end you will feel inspired to write a JSON exporter, or something similar.

This guide is by no means complete; the accuracy varies, and the block diagrams are occasionally plain wrong to the point where I hope that in the near future, someone reading this is suitably inspired or disgruntled enough to produce their own much better guide.

As Dink Smallwood was a game originally designed for Windows 95 running on at least a 486, all datatypes should be assumed to be 32-bit with little endian byte order. See the Wikpedia page for more info. This reference assumes you have some programming knowledge, or are at very least capable of looking up unknown terminology as you encounter it.



Map Data

Dink.dat

classDiagram
    class map_info {
        char name[20];
        int loc[769];
        int music[769];
        int indoor[769];
        int v[40];
        char s[80];
        char buffer[2000];
    }
classDiagram
    class map_info {
        char name[20];
        int loc[769];
        int music[769];
        int indoor[769];
        int v[40];
        char s[80];
        char buffer[2000];
    }

The smallest and simplest of Dink Smallwood's custom data files is Dink.dat. Often referred to as the screen index file, Dink.dat primarily stores information about map screens such as their order, their indoor status, and their music. Most of this will be apparent upon opening DinkEdit, as the grid that is displayed immediately after loading is a visual depiction of its contents. Like the rest of Dink's map data files, it is a C struct which is saved and loaded from disk. Open Dinkvar.h from the original 2003 source release in your text editor and scroll to line 613 to view the struct itself:

struct map_info
{
    char name[20];
    int loc[769];
    int music[769];
    int indoor[769];
    int v[40];
    char s[80];
    char buffer[2000];
    
};
struct map_info
{
    char name[20];
    int loc[769];
    int music[769];
    int indoor[769];
    int v[40];
    char s[80];
    char buffer[2000];
    
};

Just by glancing at that struct you can probably figure out what some of it does. Of these, only loc, music, and indoor actually receive any sort of use but let's go through them all anyway:

One thing that may be confusing at this point is that several of these arrays are 769 items in length rather than 768 as per the amount of map screens. If you look at the struct for the save files you'll see a comment that reads:

//added one to these, because I don't like referring to a 0 item

This is common throughout much of Dink's data formats resulting in wasted space in the data files along with great confusion for those attempting to understand the engine. In the Dink 1.09/1.10 source code written almost 15 years later, Seth rebukes himself in another comment:

//1 index based, man I was dumb(er) back then

Hard.dat

classDiagram
    hardness <|-- ts_block
    ts_block <|-- block_y
    class hardness {
    ts_block tile[800]
    int index[8000]
    }
    
    class ts_block {
    block_y x[51];
    bool used;
    int hold;
    }
    
    class block_y {
    byte y[51];
    }

classDiagram
    hardness <|-- ts_block
    ts_block <|-- block_y
    class hardness {
    ts_block tile[800]
    int index[8000]
    }
    
    class ts_block {
    block_y x[51];
    bool used;
    int hold;
    }
    
    class block_y {
    byte y[51];
    }

It would make more sense to go straight to MAP.DAT from Dink.dat but rarely does anything in the world of Dink make sense. Open up Dinkvar.h again and go to line 577 to find the struct named hardness. This struct contains the tile-based collision data for the engine and is presented like so:

struct hardness
{
    ts_block tile[800];
    int index[8000];
    
};
struct hardness
{
    ts_block tile[800];
    int index[8000];
    
};

The first section is a series of 800 graphical tiles that are 51x51 pixels in size with one line from each axis going unused for the total of 50x50 as per Dink's BMP tile size. In accordance with Seth's oudenophobia, the top row and first column are never referred to, and therefore should be skipped over, or accounted for some other way when reading or writing to them.

The tiles in the hardness struct are defined by a substruct called ts block, which also refers to another substruct called block_y which is used to define the tile's size for both its axes so as to create a two-dimensional array. These pixel co-ordinates start from the top left corner rather than the bottom corner as is common in most graphical libraries or raster graphics editing programs.

The values that make up y and x are numbered 1, 2, 3, or 0. The first is the typical impassable sort such as walls and buildings, with 2 being for things that fireballs may pass over such as small rocks, and 3 unused. It is commonly assumed that 3 was designed for fire and spike tiles that would have caused harm if walked onto. A value of zero indicates no collision.

struct ts_block
{
    block_y x[51];
    bool used;
    int hold;
};

struct block_y
{
    byte y[51];
};
struct ts_block
{
    block_y x[51];
    bool used;
    int hold;
};

struct block_y
{
    byte y[51];
};

Following the tile screen block's tile data are two others values consisting of used and hold which, ironically, are unused and may be skipped.

The second part of the hardness struct, and thus hard.dat overall, indexes which BMP tiles are overlayed with the aforementioned Hard.dat tiles so that placing the BMP tile in the editor may cause it to appear immediately with the corresponding Hard.dat tile. The array holding these values suggests that there are 8,000 slots despite the fact the engine in theory only supports 5,248 tiles with all 41 tile screen BMPs filled with 128 tiles each (or 5216 according to Beuc, or 5375 according to Magicman). The DAT file that ships with the original game contains 5,248 indices, however Redink1's rewrite has all 8,000 present. It is worth noting at this point that unlike many of Dink's other data types, the first tile of TS01.bmp in the top left corner is actually tile zero.

Hard.dat tiles may also be applied manually in the map editor to an individual screen, with this data stored in Map.dat. On top of this, collision data for sprites is also present in Dink.ini through the "hardbox" feature and its SET_SPRITE_INFO lines in case you weren't already confused.

Map.dat

classDiagram
    small_map <|-- tile
    small_map <|-- sprite_placement
    class small_map {
        char name[20];
    tile t[97];
    int v[40];
    char s[80];
    sprite_placement sprite[101];
    
    char script[13];
    char random[13];
    char load[13];
    char buffer[1000];
    }
    
    class tile {
        int num, property, althard, more2;
    byte  more3,more4;
    
    int buff[15];
    }
    
    class sprite_placement {
        int x,y,seq,frame, type,size;
    bool active;
    int rotation, special,brain;
    
    
    char script[13];
    char hit[13];
    char die[13];
    char talk[13];
    int speed, base_walk,base_idle,base_attack,base_hit,timer,que;
    int hard;
    RECT alt;
    int prop;
    int warp_map;
    int warp_x;
    int warp_y;
    int parm_seq;
    
    int base_die, gold, hitpoints, strength, defense,exp, sound, vision, nohit, touch_damage;
    int buff[5];
    }
classDiagram
    small_map <|-- tile
    small_map <|-- sprite_placement
    class small_map {
        char name[20];
    tile t[97];
    int v[40];
    char s[80];
    sprite_placement sprite[101];
    
    char script[13];
    char random[13];
    char load[13];
    char buffer[1000];
    }
    
    class tile {
        int num, property, althard, more2;
    byte  more3,more4;
    
    int buff[15];
    }
    
    class sprite_placement {
        int x,y,seq,frame, type,size;
    bool active;
    int rotation, special,brain;
    
    
    char script[13];
    char hit[13];
    char die[13];
    char talk[13];
    int speed, base_walk,base_idle,base_attack,base_hit,timer,que;
    int hard;
    RECT alt;
    int prop;
    int warp_map;
    int warp_x;
    int warp_y;
    int parm_seq;
    
    int base_die, gold, hitpoints, strength, defense,exp, sound, vision, nohit, touch_damage;
    int buff[5];
    }

The biggest of Dink's DAT files is of course MAP.DAT. It stores the various sprite, tile, and script data values that make their way onto the screen. As mentioned above, MAP.DAT simply stores every screen next to each other, with any relative placement information provided by Dink.dat. To find out the layout of each screen's data, we must again look in Dinkvar.h at line 657 for the relevant struct.

struct small_map
{
    char name[20];
    tile t[97];
    int v[40];
    char s[80];
    sprite_placement sprite[101];
    
    char script[13];
    char random[13];
    char load[13];
    char buffer[1000];
};
struct small_map
{
    char name[20];
    tile t[97];
    int v[40];
    char s[80];
    sprite_placement sprite[101];
    
    char script[13];
    char random[13];
    char load[13];
    char buffer[1000];
};

Once again we can deduce a few obvious bits of data immediately.

Similarly to Hard.dat, there are also a few substructs here that define certain data types. Here are the tiles:

struct tile
{
    int num, property, althard, more2;
    byte  more3,more4;
    
    int buff[15];
};
struct tile
{
    int num, property, althard, more2;
    byte  more3,more4;
    
    int buff[15];
};

Of these, only num and althard actually receive any use as the tile index and manually-placed hard.dat tiles respectively, with the rest having no apparent use in the engine. Let's look at how sprite data is stored now.

struct sprite_placement
{
    int x,y,seq,frame, type,size;
    bool active;
    int rotation, special,brain;
    
    
    char script[13];
    char hit[13];
    char die[13];
    char talk[13];
    int speed, base_walk,base_idle,base_attack,base_hit,timer,que;
    int hard;
    RECT alt;
    int prop;
    int warp_map;
    int warp_x;
    int warp_y;
    int parm_seq;
    
    int base_die, gold, hitpoints, strength, defense,exp, sound, vision, nohit, touch_damage;
    int buff[5];
};
struct sprite_placement
{
    int x,y,seq,frame, type,size;
    bool active;
    int rotation, special,brain;
    
    
    char script[13];
    char hit[13];
    char die[13];
    char talk[13];
    int speed, base_walk,base_idle,base_attack,base_hit,timer,que;
    int hard;
    RECT alt;
    int prop;
    int warp_map;
    int warp_x;
    int warp_y;
    int parm_seq;
    
    int base_die, gold, hitpoints, strength, defense,exp, sound, vision, nohit, touch_damage;
    int buff[5];
};

To any D-modder, just about all of these will be very familiar and are covered in greater depth in the DinkC Reference. Some of these that do stand out are:


Graphics Data

BMP

By default, all of the Dink engine's graphics are 8-bit paletted Windows BMPs. The palette is loaded from the first tile screen (TS01.bmp) upon start, and afterwards may be replaced using DinkC commands, thereby affecting all sprites and tiles drawn to screen. In newer versions of the Dink engine including Freedink, if 24-bit mode is selected, each BMP is drawn to screen as per its own palette and pixel array.

The newest official versions of the engine (Dink "HD") can load 32-bit BMPs with an alpha channel, and also lack 8-bit display mode meaning that paletting techniques will not work at all. More recent versions of Freedink allow for 8-bit compatibility through Beuc's programming magic, and not natively through SDL2, meaning that sometimes the colours will look somewhat unusual in D-mods such as "Dink Goes Boating" when compared to 1.08 or earlier.

There is generally no compression applied to BMP files that the engine uses, although RLE support is present and is used in the splash screen for DinkEdit. Dink 1.10 supports the display of PNG files, as does Freedink with a file-rename workaround that allows for the use of anything recognised by the SDL Image library.

FastFiles

classDiagram
class FastFile {
    int filecount;
    int offset;
    char filename[13];
    int offset;
    char filename[13];
    etc...
    chunk filedata
}
classDiagram
class FastFile {
    int filecount;
    int offset;
    char filename[13];
    int offset;
    char filename[13];
    etc...
    chunk filedata
}

Most of the early Dink graphics are obfuscated as "dir.ff" files. Within each file there are multiple BMP files concatenated together as per however many files are indicated in the first four bytes. The FastFile format was not developed by RTSoft and instead was included in the DirectX SDK by Microsoft as a means to speed up file loading operations by loading the majority of game data from a large fastfile so as to minimise file handle operations. In Dink's data, however, just about every sequence has its own dir.ff file meaning that any performance benefits nowadays are minimal at best, with the format's main use by RTSoft being as a means to ward off graphics thieves. This was not always the case, however, and in the early days of Dink it was preferable to use fastfiles to slightly improve graphics loading performance, as the engine would bypass various steps applied to standard BMPs and load the FastFile's contents directly into VRAM.

Thankfully, there are several programs that can unpack dir.ff files and reveal their hidden treasures. For GNU/Linux users there is an extractor in the Freedink source, and on Windows there is the commercial product called Game Extractor, as well as WinDinkEdit2. The file data is stored in the dir.ff file exactly as it would have been as a BMP meaning that there is no quality loss, or much drive space gained by using the format.

Be warned, some dir.ff files are packed improperly and contain more than just BMP data. In the case of Mystery Island, its fastfile is 7MB with only two megabytes of that being graphics. The bulk of the file consists of the actual original dir.ff file within it, plus the graphics alongside it (again), plus a leftover build script. If reading from a dir.ff file, it is best to skip anything that isn't a BMP when it comes to extracting its files.

RTTEX

Engine presence: >=1.09 File type: ProtonSDK RTFile Nominal size: Unknown Number of files: Varies Block diagram:

classDiagram
class RTFile {
    char fileTypeID[6];	
    byte version;
    byte reserved[1];
    int compressedSize;
    int decompressedSize;
    bool decompression_type;
    char reserved[15];
    chunk texture_data;
}
classDiagram
class RTFile {
    char fileTypeID[6];	
    byte version;
    byte reserved[1];
    int compressedSize;
    int decompressedSize;
    bool decompression_type;
    char reserved[15];
    chunk texture_data;
}

The newest official version of the Dink engine (known as Dink "HD", iPhone Dink, or RTDink) is built on top of RTSoft's cross-platform Proton SDK. Internally, Proton uses the RTTEX texture format for the sake of device interoperability, as certain devices won't load certain texture formats. In RTDink, the only apparent use of RTTEX files is for the interface, and those hand-holding pop-ups at the start. These files may be converted back to PNG by using RTPackConverter.

To convert an image to an RTTEX file, one may check out the Proton SDK themselves from GitHub and run rtpack.exe with their input image file. For more information on RTSoft's Proton SDK, you may want to have a look at the documentation.


Text files

DinkC .c Files

Interaction with the Dink engine is mostly performed through the use of text files with the ".c" extension, written in RTSoft's custom programming language known as DinkC. These scripts may be run by attaching them to sprites or map screens in the map editor, or by being called from another script. If there is both a .c file and a compressed .d file with the same name in the same script path, the latter will take precedence for execution by the engine. One may also sort scripts into subdirectories as well, although only Beuc seems to have ever used the feature in the case of Dink Mines as part of the "One-Screen Dmod". If you'd like to learn more about DinkC, check out the web reference.

.d Files

classDiagram
class BPE {
    short paircount + 128;
    int pairs[paircount];
    chunk text
}
classDiagram
class BPE {
    short paircount + 128;
    int pairs[paircount];
    chunk text
}

In another attempt to obfuscate the game's data, RTSoft implemented a form of compression known as "Byte Pair Encoding" for DinkC text files. Originally invented by Philip Gage and published in the C User's Journal in February of 1994, the compression works by combining common pairs of characters and substituting them multiple times. Due to RTSoft's marketing spin, including naming the compressor to "compile.exe", they are often erroneously referred to as "compiled scripts" despite the fact that no compilation to binary takes place, and not even the comments are stripped from the input file.

After scanning through the text and looking for common pairs of letters, the algorithm substitutes them with a single character with an ordinal value of 128 added that is moved to a table of pairs at the top of the file. The encoder does this multiple times, including to already substituted pairs before writing an integer to the first two bytes of the file that contains the amount of substituted pairs. To get back the original characters, one must work in reverse by looking for characters with ordinals above 127 and then subtracting and replacing them and then removing them from the table until no characters have ordinals above 127 and the table is empty.

Due to how BPE is implemented, any character ordinal in the plaintext above 127 such as accented letters will cause the encoder to fail, as those values are used for substitution. At least two D-mod developers have released such mods which are completely broken due to the needless use of this superfluous feature. Apart from a desire to avoid people easily reading through the scripts for the purposes of cheating, or for some sort of paranoia regarding script theft, there is no worthwhile reason for its use, as disk space saved by using BPE-compressed scripts is minor at best.

There are numerous BPE decompressors around including Dink Smallwood HD Text Tool, and d2c.c in the Freedink source. Ducklord also posted the source for an easily compilable one on the Dink Network forum in 2008.

Dink.ini

The Dink engine's graphics are not automatically loaded by magic(man), and instead must be specified with their various parameters in an ini file called Dink.ini. Not only are graphics loaded through the various lines in the file, Dink.ini is also capable of taking parameters such as specifying aliases for frames, and their delay interval. It is also possible to specify certain commands such as PLAYMIDI for the purpose of playing music over the loading screen, as well as a couple of archaic ones that determine game starting co-ordinates leftover from when DinkC was far less comprehensive feature-wise.

The archaic commands are as follows: STARTING_DINK_X, STARTING_DINK_Y, STARTING_DINK_MAP All of which are self-explanatory.

The specifics of the other commands such as SET_FRAME_FRAME are covered in much greater detail elsewhere and thus I will spare you the torment.

Dmod.diz

These are text files with a few lines in them to provide information to the user as to what they might be about to launch when loading something in a front-end. The first line is typically the title of the file, with the others reserved for contacting the author or copyright etc. Unlike warez or BBS FILE_ID.DIZ files, Dmod.diz will be most commonly viewed through a Dink front-end and will tend not to feature ASCII/ANSI art.


Miscellaneous

Here are some file types that don't really fit in anywhere else but need to be covered anyway.

.DMOD files

Back in the early days of Dink, most D-mods were packaged either in Zip files, RAR archives, or sometimes a self-extracting archive. This required the player to decompress the archive to the proper location and then run the game with a front-end or otherwise manually invoke dink.exe with the -game parameter on the command line. A few years later, Merlin produced a program called DFarc, short for "DMOD file archiver", which was designed to extract and compress renamed .tar.bz2 files so they could be launched from a front-end. With the release of Dink 1.08, Dfarc2 was developed, and integrated a full-featured front-end providing for a seamless experience.

As they are Bzip2 files, you can open DMOD archives and produce them yourself in any halfway competent compression program such as 7-zip or GNU Tar. Unfortunately, DMOD archives that were compressed using the older versions of DFarc prior to 2 will not open in a standard decompressor, as those versions did not use a proper library for the Tar file underneath. This means they miss the 1024 bytes of padding at the end causing the decompressor to fail. DFarc 2 and 3 are both able to read these improper Tar files without the user ever knowing. RTDink is also capable of loading DMOD files directly, with Seth writing his own Tar file handler to work around this issue.

If one is interested in extracting these broken Merlin Tarballs themselves, one may consider writing their own Tar decompressor, with the file format covered in detail elsewhere. The alternative would be to simply add 1024 zero bytes to the end of the file and then proceed as above with a compliant decompressor, such as using the truncate command on a *nix system.

As we have seen in the previous sections, the amount of wasted space in Dink's data is obscene, and bzip2 turned out to be an excellent choice for cutting it down. In testing its compression I found that it was able to compress the MAP.DAT of Friends Beyond 3 from 24.1MB down to just 70KB, outperforming both 7z and Zstd which were around 100KB. In some cases .tar.xz will provide slightly better compression ratios, but overall will be marginal. In an ideal build of the Dink engine, DMOD files would not need to be decompressed at all, with the engine mounting the archive read-only instead and loading its contents into RAM.

Dink.pak

In making Dink "HD", Seth decided to pack all the original game's data except for the audio into a single archive named "dink.pak". This is a simple Zip file that will open in any decompression program that supports them. Interestingly, it contains all the graphics in standard BMP format, including those not in the official BMP pack such as those for Seth and King Daniel. From browsing the RTDink source, in particular App.cpp, it seems that "dink.pak" is only ever loaded on Android systems, and on other platforms, the data is provided alongside it again in a conventional directory tree.

Save.dat files

classDiagram
player_info <|-- mydata
player_info <|-- varman
player_info <|-- item_struct
class player_info {
    int version;
    char gameinfo[196];
    int minutes;
    int x,y,die, size, defense, dir, pframe, pseq, seq, frame, strength, base_walk, base_idle,
        
        
        base_hit,que;
    
    item_struct	mitem[9];
    item_struct item[17];
    
    int curitem, unused;
    int counter;
    bool idle;
    mydata spmap[769];
    int button[10];
    varman var[max_vars];
    
    
    bool push_active;
    int push_dir;
    DWORD push_timer;
    int last_talk;
    int mouse;
    bool item_magic;
    int last_map;
    int crap;
    int buff[95];
    DWORD dbuff[20];
    
    long lbuff[10];
    
    char  cbuff[6000];
}

class mydata {
    unsigned char type[100];
    unsigned short seq[100];
    unsigned char frame[100];
    int last_time;
}

class varman {
    int var;
    char name[20];
    int scope;
    bool active;
}

class item_struct {
    bool active;
    char name[10];
    int seq;
    int frame;
}
classDiagram
player_info <|-- mydata
player_info <|-- varman
player_info <|-- item_struct
class player_info {
    int version;
    char gameinfo[196];
    int minutes;
    int x,y,die, size, defense, dir, pframe, pseq, seq, frame, strength, base_walk, base_idle,
        
        
        base_hit,que;
    
    item_struct	mitem[9];
    item_struct item[17];
    
    int curitem, unused;
    int counter;
    bool idle;
    mydata spmap[769];
    int button[10];
    varman var[max_vars];
    
    
    bool push_active;
    int push_dir;
    DWORD push_timer;
    int last_talk;
    int mouse;
    bool item_magic;
    int last_map;
    int crap;
    int buff[95];
    DWORD dbuff[20];
    
    long lbuff[10];
    
    char  cbuff[6000];
}

class mydata {
    unsigned char type[100];
    unsigned short seq[100];
    unsigned char frame[100];
    int last_time;
}

class varman {
    int var;
    char name[20];
    int scope;
    bool active;
}

class item_struct {
    bool active;
    char name[10];
    int seq;
    int frame;
}

The strange machine that doesn't belong here refuses to die, and instead will record whatever you've done to disk. Let's go through that block diagram above and see what it writes to those SAVE.DAT files:

The rest are buffers that never got filled, thereby leading to a ton of wasted disk space if you lack filesystem compression. As these empty buffers are at the end of the file, they can safely be ignored when reading or writing savefiles, such as in the case of Undink which manages to trim them down to 313KB. Do be aware that in 1.08 and later, the engine stores data to this previously empty space such as the location of the other DAT files, tiles, palette, and game time. Look at the corresponding header file in the source for these versions to determine the specifics.

If you would like to just edit save files, have a look at Redink1's "Save Game Editor of Justice". Also note that some D-mods do not use save files in a conventional manner, and instead save them into high-numbered slots (into the hundreds) so as to implement a boolean variable that will exist independently of the engine's state. By checking for the existence of these bogus save files, one may implement a "New Game Plus" mode or similar.

That's it for the file formats. There's a few that haven't been covered such as MIDI and WAV but I don't care enough to write about them due to being fairly standard in nature, with no packing performed on them. If this guide hasn't been enough of an explanation, I suggest you download and have a look at the unDink source, as it contains offsets from the start of the file for just about all of these values, and is written in a way that skips the unused data while providing helpful variable names.


Appendix

Using a hex editor

If you're looking to casually edit any of Dink's data files, a hex editor is a necessity. I suggest downloading one of the ones in this list, and highly recommend 010 Editor due to its templating and scripting system, and because of the following instructional example. The 30-day trial should be long enough to get you started. This is not an ad, I promise.

Changing values in Dink.dat

This example uses 010 Editor to edit values in Dink.dat and demonstrate its templating system. Once the file has loaded, open a new template and paste the contents of the map_info struct into the template window. Hit the play button and out will come the template results at the bottom of the window which will show you the individual values and highlight the length of each field.

Screen Shot 2022-03-27 at 5.56.27 pm.png

From here you can flip through the individual values and edit them to your heart's content. Perhaps attempt setting values in loc to identical ones and see what happens in the engine upon visiting the screen.

Rebuilding Dink.dat

If you've used WinDinkEdit or any of its descendants, you've probably found yourself in a situation in which Dink.dat mysteriously disappears, thereby destroying everything you've toiled on for the last few months. Due to map data being spread across two files, as long as your corresponding MAP.DAT is okay, you should be able to do something about it. The first thing to do is to get the size of your MAP.DAT in bytes and then divide it by 31,280 with the result being your number of screens. Create a new D-mod and load its empty Dink.dat file into the hex editor as above with the template. You'll now have access to the individual values of loc allowing you to fill them in in order as per the amount of map screens you have. Incidentally, MapNuke doesn't properly delete screens, it hides them by setting the value in loc to zero. For older D-mods, there are probably all sorts of hidden screens just waiting to be discovered.

Writing your own data parser

Don't be limited to the programs that other's have made. It's fairly straightforward to read and write Dink's data files in a programming language of your choice. In this case, we'll be using Python and Lua, but any programming language with file access support will suffice.

Using Lua

Lua is a Brazilian programming language often embedded into game engines. It has been used in Baldur's Gate, Roblox, Love2D, CoronaSDK, and Novashell. Get a Lua interpreter set up alongside your Dink data and then type this into the interactive shell:

dinkdat = io.open("dink.dat", "r")
dinkdat:read(20)
dinkdat = io.open("dink.dat", "r")
dinkdat:read(20)

This opens a Dink.dat file in read-only mode and then reads the first 20 bytes and should spit out the name value which of course is "Smallwood". This is only a very basic example, but Lua is actually quite powerful at reading C structs due to its string handling functions. Check out the documentation for more info, and pay particular attention to io and string.unpack. Using these, you could implement a Dink map file loader in Love2D or another Lua-based engine without using any sort of external library.

Using Python

Python is a popular programming language that has a lot of functionality built into it such as a GUI library (Tkinter), as well as the ability to read from C structs! This is implemented in both the struct and array modules in the standard library. Get a Python 3 interpreter set up and reopen your text editor if you closed it and type this in and then save and run it:

import struct
import array

dinkdat = open("dink.dat", "rb")
namedata = dinkdat.read(20)
name = struct.unpack("20s", namedata)
print(name)
dinkdat.close()
import struct
import array

dinkdat = open("dink.dat", "rb")
namedata = dinkdat.read(20)
name = struct.unpack("20s", namedata)
print(name)
dinkdat.close()

This should show something similar to the Lua program above. What it does is open the Dink.dat file in read-only mode and then reads the first 20 bytes before using the struct module to unpack it into somewhat legible text which is then displayed on the screen. The "20s" bit is important as it tells the module that it should decode exactly 20 bytes of data. Let's read some more of the file by adding some more to our program. Make sure to remove the last line that closes the file though.

# 32-bit integers are four bytes
locdata = dinkdat.read(769 * 4)
locarray = array.array('i')
locarray.frombytes(locdata)
print(locarray)
dinkdat.close()
# 32-bit integers are four bytes
locdata = dinkdat.read(769 * 4)
locarray = array.array('i')
locarray.frombytes(locdata)
print(locarray)
dinkdat.close()

What this does is read the location array from the file and then convert it to an array in Python which decodes the values from the input data as unsigned integers. You could then use locarray.tolist() to convert it to a standard list and then do whatever you like with it. You can repeat this process twice more to get the music and indoor values too, hopefully in different variables, as the file reading process continues from where it left off.


Credits

A big thank you to Magicman and Shevek for helping me out on the Dink Network forums when I first started looking at Dink's data in 2013, to Redink1 for keeping the Dink Network forums alive, and to Beuc for his invaluable documentation in the Freedink source. Also, many thanks to Cocomonkey for pointing out issues in certain D-mods for me to investigate, and for being larger than life.

License

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.