Graal Online is a late 20th century graphical MMORPG. Unfortunately, archive.org does not seem to have the earliest snapshots of the game's website anymore. Going off of my own memory, the game was originally a java game called "Zelda Online", which was written by Stefan Knorr in the late 90s. The original game was made from graphics and level data extracted from The Legend of Zelda: A Link to the Past.
After the obvious and inevitable Cease and Desist order from Nintendo, the game was rebranded as "Graal Online". The infringing graphics were redrawn in a simple style that was easy for new creators to learn, and new levels were created to replace the infringing ones. The roots as a data mining project would still be reflected in the engine design and the sprite atlas layouts for some time.
The game's engine evolved quite a bit, starting as a simple multiplayer game to adding a robust programming language for game scripting, a flexible container format for highly customizable sprite animation, the ability to create custom tile atlases, and various attempts at 3D. The vast majority of the game content over the years was made via player contributions, though much of it is lost now.
The game still exists, though it's original creator has long since left the company and started Avalonia Online as a successor to the project.
Graal holds a special place in my heart, for being something that very directly helped me develop an interest in programming and video-game tech, for being an escape from social isolation in my teen years and safeish place to explore self expression. It is also a connection to a deceased friend who remained involved in the game long after myself and others in our group stopped playing.
Out of nostalgia and as a fun project to help overcome burnout, I have at various points opened up old game data files in a text editor to see what is in them and make sense of how the game used them. What follows below is what I have figured out about the file formats so far.
Graal levels are 64 by 64 tiles. The tiles are stored in a tightly packed atlas. Depending on the game version, the atlas is 116x32 tiles or 128x32. The individual tiles in the atlas are 16x16 pixels. The collision type of the tiles is most likely either hard coded in the game itself, or is in the blob "arrays.dat".
Graal levels also can contain definitons for several other object types: Level links, baddies, signs, treasure boxes, and programmable NPCs.
Level links are regions of tiles that when the player touches the link, they will be transported to another level. A level link is defined with the coordinates of the top left corner and the width and height. The coordinates and dimensions must be integer values. The destination Can be expressed as a decimal value, or use "playerx" and "playery" to indicate that the player's current coordinates should be used for one or both axes. A level link also names the destination level. The ordering of the parameters in all file formats is: LevelName Width Height X Y NewX NewY.
Baddies have three integer parameters: the type, and their X/Y coordinates. They also have three optional string parameters: what they say when they notice the player, what they say when they hit the player, and what they say when hurt.
Signs are strings of text stored in the level. These are used to present an on-screen box of text which the player can flip through. These are used to represent the contents of signs, but are also commonly used to represent character speech and other things. The sign strings are accessed by index, which is simply the order they are stored in the file.
Treasure boxes have a coordinate in the level, an item, and an optional sign text. A treasure box is 2x2 tiles in size, and the player can open it by touching the front of the box. The contained item is one of the built-in items, such as bombs, arrows, swords, shields, money, and so on. Treasure boxes cannot contain NPCs, however NPCs can easily be made to mimic treasure boxes.
NPCs are little self contained programs that can either be given an image file for its appearance, be made to render like a player character, or be left invisible. NPCs also can add themselves to the player's inventory, in which case they can be activated by the player. Inventory NPCs are called "NPC weapons", though they often have non-violent purposes. "NPC weapons" persist with the player from level to level. NPCs are objects in the sense that they represent some in-game entity, but also that their execution context is isolated from the other NPCs in the level. NPCs can read the game state, modify the game state (including player characters), and communicate with eachother via a simple actor model. The programming model is remarkably accessible to novice programmers.
A lot more could be said about NPC programming, but as far as the level data is concerned, an NPC is just a few simple parameters: a coordinate pair, an optional image file name, and a source code string.
The original level format for the game is a compact binary encoding. Tile data is run length encoded. There are several revisions to the file format which mostly correspond to new engine features being added to the game. At the end of this section is a tabel of all of the header strings for which I could find examples of.
The .graal and .zelda files are organized like so:
Starting at the 9th byte is the tile data. The tile data is encoded as a series of successive packets. The size of the packets depends on the format version. Regardless of the number of bits in the packet, the Most Significant Bit is the "repeat bit". Counting from the Least Significant Bit, the 9th bit is the "double bit", and the bottom 8 bits are the "count bits", which represent an unsigned integer. Tile ackets are interpreted like so:
Read a tile packet. If the repeat bit is not set, then the packet represent one tile. All of the remaining bits in the packet represent the index of the tile in the atlas. Simply add the tile to your board and advance to the next packet.
If the repeat bit is is, then the packet describes the repeat mode. There are two repeat modes: single repeat and double repeat. If the double bit is set, then we are in double repeat mode. Read the next one or two packets (depending on the mode) as tiles, and add them to the board for the number of times described in the count bits.
Stop after you've added the 4096th tile to the board.
There is no tile count, and the number of packets depends on the complexity of the tile arrangement in the level, so the only way to determine where to stop is to extract the tiles.
Once you have your array of tiles, the conversion from atlas index to atlas
x/y coordinates is:
atlas x = floor(index / 512) * 16 + index % 16
atlas y = floor(index / 16) % 32
Everything after the tile block will be byte-aligned. There may be some bits left over after the last packet - just ignore them.
Following the tile block are level links. The parameters are ascii encoded and delimited by spaces. Each link ends with the newline character. The parameters are Destination Level, Link Width, Link Height, Link X, Link Y, Destination X, and Destination Y. The level link section is terminated with the "#" character followed by a newline character, even if there are no links in the level.
Following the level links section is the baddies section. For each baddy, there are three bytes representing unsigned integers. These correspond to the baddy's X coordinate, Y coordinate, and type. After that are three ascii encoded strings, which are separated by a "\"" character, followed by a new line.
The baddies section is terminated with three bytes who's value is 0xFF.
After the baddies section are the NPCs. Each NPC starts with two bytes for the X/Y coordinate as unsigned integer values. Following that is an optional ascii encoded file name for an image. After that is a "#" character, followed by the source code of the NPC. The source code is ascii encoded, except that the line end is represented by a byte with the value 0xA7 instead of a newline character. The NPC definition is concluded with a newline character.
The NPCs section is terminated with a "#" character followed by a newline character.
After the NPCs section are the treasure boxes. Each treasure box is represented by four bytes and a newline character. The bytes are unsigned integers representing the X coordinate, the Y coordinate, the treasure type, and the sign index.
The sign index parameter is really weird. A value of "31" indicates that no sign is
to be displayed for the chest. However the chest can be associated to one of 255 signs.
The formula to decode the sign index is: index = (param + 224) % 256.
The formula to encode the sign index is: param = (index + 32) % 256.
In some file versions, the treasure box will conclude with a null treasure box who's parameters are 0xFF, 0xFF, 0xFF, and 0x00. This may be in addition to the block being terminated by a "#" followed by a newline character.
Finally, the last section in the file is the sign text. For each sign definition, the first two bytes are the coordinates of sign + 32 (so, subtract 32 to get the correct value). Following the coordinates are a byte per character in the sign, and then the sign entry ends with a newline character. The sign text is not ascii encoded. Each character byte is the index of the character in the sign sprite sheet + 32.
Here is a handy reference for the character encoding:
That's all, folks!
Whew, there is a lot going on there! To make matters worse, there are lots of old levels that appear to have corrupted sections, but the game will happily accept. This seems to be a bug in the first few versions where NPCs were first supported, where the NPC text would sometimes be overwritten with garbage data. This will sometimes look like extra baddy definitions or just nonsense data. At a guess, the editor was probably allocating the memory without it being cleared first, and then not writing anything to it. I don't have any recommendations on how to handle these levels, but it is worth noting.
To conclude this section on the binary level formats, here is a table of file headers I've encountered, and notes on the pecularities of each version.
The .nw format originates from Graal's ill-fated New World project, and is one of the few things from said project to be incorporated into the game.
The .nw format does away with the old binary file encoding, and insted encodes everything as plain text. This also does away with the run-length encoding of tile data. So a .graal file would run the range of 226 bytes to ~7 kb depending mainly on the complexity of the tile arrangement, a .nw file is always going to be at minimum 9.3 kb. On the surface this sounds wasteful for a game that was streamed over the internet at a time period where dialup connections were still very common. However, the encoding lends itself well to lz or gzip compression, so it is possible that the over-the-wire file size was about the same.
A .nw file starts with an eight byte file header reading "GLEVNW01", followed by a new line character. Simple definitions start with a command word (like "BOARD" or "LINK") followed by a parameter list separated by spaces, and the definition ends with a newline character. Definitions containing text blocks start with a command word with the parameters on the same line, separated by spaces as before. The text body follows on subsequent lines. The block ends with the command word + "END" and a new line. So, NPCs start with "NPC", and end with "NPCEND" etc.
The object definitions are all pretty self explanitory, and so I'm not going to explain them here. The tile definitions are not obvious from looking at example files, so here is how that works:
There will be 64 lines representing each horizontal row of the tile board, that
take this form:
"BOARD xoffset yoffset width layer data".
Because the game only ever supported 64x64 levels and layers were never implemented, there will always be 64 lines for the BOARD entries, the xoffset and layer parameters will always be 0, the width will alway be 64, and the yoffset param will always be a number from 0 through 63.
The data parameter will be 128 characters. Each pair of characters
is a base64 encoded number. This number is an index into the tile atlas, and can
be translated into x/y coordinates with the same equation as the .graal parser uses:
atlas x = floor(index / 512) * 16 + index % 16
atlas y = floor(index / 16) % 32