Files with WWD extension (Wap World Document) are designed to store level data in games based on the WAP32 engine. Depending on the actual game in order to edit these files you’d use: Gruntz Level Editor (GLE) in the game Gruntz or Wap World in the case of the game Claw. Implementation-wise these editors don’t differ much, only GLE incorporates a few additional new features. For this very reason this article will be dedicated to WWD files produced by GLE, which are technically a functional superset of WWD files produced by the Wap World editor.
Below is the formal WWD file specification, taking into account all the subtle differences between Gruntz and Claw level editors.
A few basic definitions are required to get things started:
- plane – every level is comprised of planes. One of them is required to be marked as “main” – that’s where all the action takes place. Other planes are purely optional and are for decorational purposes only.
- tiles – every plane is a rectangular board comprised of tiles.
- tile properties – every tile in the main plane may have a different functionality. Tile properties describe how the tile interacts with other objects in its vicinity and what it’s responsible for.
1. Data types
There are the following different data types throughout this specification:
- null-terminated string and fixed-length string – typical strings, really. The former is a series of character bytes terminated with the NULL character, while the latter has a fixed length.
- null-terminated, fixed-length string – it’s an interesting hybrid of the two, which in my opinion is a result of a bug in the level editor implementation. Most of the strings in the editor correspond to a fixed number of bytes in a WWD file. For example the “Author” field has 64 bytes reserved. They’re not typical fixed-length strings however – if none of those 64 bytes is a NULL character, the editor will keep going and interpret the following bytes as a part of the same string. In practice then for the editor to interpret the string correctly, the “Author” field may contain only up to 63 characters while the last 64th byte should always be set to a NULL character.
- uint32_t and int32_t – unsigned and signed four byte integer type. All integer values in the specification are little endian, i.e. the least significant bits are stored in the first byte of the number. An important remark: whether a number is signed or not was determined solely based on the way the editor interprets and renders the data. The game itself may theoretically interpret the data differently – keep that in mind.
In a few places there’s also a data type named rect, which is defined as follows:
struct rect { int32_t left; int32_t top; int32_t right; int32_t bottom; };
2. WWD header and the main block
The first 1524 bytes of data is the WWD file header. The rest of the file from now on will be referred to as the main block. Depending on the Compress flag in the World Properties of the editor, the main block will have a different structure:
- if the flag is not set, the main block doesn’t require any preprocessing as is analyzed as-is.
- if the flag is set, the entire main block is compressed with the deflate algorithm. In order to load the WWD file the main block has first to be decompressed. After that you treat the entire file as if it was never compressed to begin with.
All offsets in this specification have values relative to the very beginning of the file assuming the compression was not used.
The most important fields in the WWD header include:
- the offset to the definition of planes
- the offset to the tile properties
- the size of the decompressed main block (only applicable when the Compress flag is set. Otherwise the field is zero)
- the checksum, which is calculated based on the main block data. If it’s value is incorrect the editor and the game will reject the level and not attempt to load it.
All the subsequent sections of the file (plane headers, tiles, objects etc.) may be placed in the file in any order, since only the offset values are required to locate each section. Nevertheless the sections will be explained in this specification in the order they occur in WWD files generated by the editor.
struct wwd_header { // 0x0 (0) - (?) WWD signature (and/or header size) uint32_t signature; // 0x000005F4 // 0x4 (4) uint32_t unknown1; // 0x00000000 // 0x8 (8) - flags // 0x1 - use z coords // 0x2 - compress uint32_t flags; // 0xC (12) uint32_t unknown2; // 0x00000000 // 0x10 (16) - name // null terminated, fixed length string char name[64]; // 0x50 (80) - author // null terminated, fixed length string char author[64]; // 0x90 (144) - birth // null terminated, fixed length string char birth[64]; // 0xD0 (208) - rez file path // null terminated, fixed length string char rez_file[256]; // 0x1D0 (464) - image dir // null terminated, fixed length string char image_dir[128]; // 0x250 (592) - pal rez // null terminated, fixed length string char pal_rez[128]; // 0x2D0 (720) - start x int32_t start_x; // 0x2D4 (724) - start y int32_t start_y; // 0x2D8 (728) uint32_t unknown3; // 0x00000000 // 0x2DC (732) - number of planes uint32_t num_planes; // 0x2E0 (736) - offset to the definition of planes uint32_t offset_planes; // 0x2E4 (740) - offset to the properties of tiles uint32_t offset_tile_properties; // 0x2E8 (744) - size of the decompressed main block // 0 for decompressed files uint32_t decompressed_mainblock_size; // 0x2EC (748) - checksum uint32_t checksum; // 0x2F0 (752) uint32_t unknown4; // 0x00000000 // 0x2F4 (756) - launch app // null terminated, fixed length string char launch_app[128]; // 0x374 (884) - image set 1 // null terminated, fixed length string char image_set1[128]; // 0x3F4 (1012) - image set 2 // null terminated, fixed length string char image_set2[128]; // 0x474 (1140) - image set 3 // null terminated, fixed length string char image_set3[128]; // 0x4F4 (1268) - image set 4 // null terminated, fixed length string char image_set4[128]; // 0x574 (1396) - prefix 1 // null terminated, fixed length string char prefix1[32]; // 0x594 (1428) - prefix 2 // null terminated, fixed length string char prefix2[32]; // 0x5B4 (1460) - prefix 3 // null terminated, fixed length string char prefix3[32]; // 0x5D4 (1492) - prefix 4 // null terminated, fixed length string char prefix4[32]; }
The exact algorithm for calculating the checksum value is rather … unconventional. Below is its definition written in pseudo-C:
// the main block data in its final form as it is supposed to be saved to the file // either compressed (when the flag Compress is set) or not unsigned char* mainBlockData; // the size of the mainBlockData buffer unsigned int mainBlockSize; // the calculated checksum unsigned int checksum = -mainBlockSize; for(unsigned int offset = 1; offset < mainBlockSize; ++offset) checksum += mainBlockData[offset] - offset; // if the main block data is compressed if( isCompressed ) { // decompressed main block data unsigned char* decompressedMainBlockData = ... ; checksum += decompressedMainBlockData[mainBlockSize]; }
The gist of the algorithm is that for every byte of the main block (in the exact shape that it’s written to the file, be it compressed or not) its value is added to the checksum while its offset (relative to the beginning of the main block) is subtracted from the checksum. There’s one caveat though: the calculation process starts with the second byte of the main block while the first byte is completely ignored. Further more, if the main block is compressed you also have to add in the value of the next byte of the uncompressed main block. It’s so complicated for a “reason” but I’ll reserve that topic for another article. A savvy programmer might already suspect what’s going on. All I will say at this point is that I believe that this algorithm is a result of a quite pesky programming error in the editor implementation.
In my tests I’ve discovered that the value of the checksum doesn’t have to be exactly the same as the editor calculates in order to be loaded properly. I haven’t yet reversed the exact algorithm for verifying the checksum, though once I do I will update this article. As far as I can tell you can safely omit the last part of the algorithm where you have to meddle with the uncompressed main block data and neither the editor nor the game will have any complaints.
3. Plane headers
➦ wwd_header.offset_planes
At the very beginning of the main block GLE saves a sequence of headers of all available planes. The number of planes is saved in the WWD header (wwd_header.num_planes). Each header has 160 bytes.
The most important fields in a plane header are:
- offset to the plane tiles
- offset to the list of the plane image sets
- offset to the list of objects on the plane (GLE saves the objects for the main plane only – for any other planes the offset is equal to 0)
struct plane_header { // 0x0 (0) - (?) block size uint32_t block_size; // 0x000000A0 // 0x4 (4) uint32_t unknown1; // 0x00000000 // 0x8 (8) - flags // 0x01 - main plane // 0x02 - no draw // 0x04 - x wrapping // 0x08 - y wrapping // 0x10 - auto tile size uint32_t flags; // 0xC (12) uint32_t unknown2; // 0x00000000 // 0x10 (16) - name // null terminated, fixed length string char name[64]; // 0x50 (80) - width of the plane in px (tiles wide * tiles width in px) int32_t width_px; // 0x54 (84) - height of the plane in px (tiles high * tiles height in px) int32_t height_px; // 0x58 (88) - tiles width in px int32_t tiles_width; // 0x5C (92) - tiles height in px int32_t tiles_height; // 0x60 (96) - dimensions - tiles wide int32_t tiles_wide; // 0x64 (100) - dimensions - tiles high int32_t tiles_high; // 0x68 (104) uint32_t unknown3; // 0x00000000 // 0x6C (108) uint32_t unknown4; // 0x00000000 // 0x70 (112) - movement - x percent int32_t movement_x_percent; // 0x74 (116) - movement - y percent int32_t movement_y_percent; // 0x78 (120) - fill color int32_t fill_color; // 0x7C (124) - number of image sets uint32_t num_image_sets; // 0x80 (128) - number of objects uint32_t num_objects; // 0x84 (132) - offset to the tiles uint32_t offset_tiles; // 0x88 (136) - offset to the names of image sets uint32_t offset_image_sets; // 0x8C (140) - offset to objects uint32_t offset_objects; // 0x90 (144) - z coord int32_t z_coord; // 0x94 (148) uint32_t unknown5; // 0x00000000 // 0x98 (152) uint32_t unknown6; // 0x00000000 // 0x9C (156) uint32_t unknown7; // 0x00000000 }
4. Plane tiles
➦ plane_header.offset_tiles
This section contains the list of tiles of which the plane is comprised. Each tile is represented by a 4-byte ID number (uint32_t). The number of tiles is: plane_header.tiles_wide * plane_header.tiles_high.
The tiles are indexed from the top-left to the bottom-right corner of the plane, row by row.
Instead of an ID number there may be one of two predefined values, which mean the following:
- 0xFFFFFFFF – the tile is invisible.
- 0xEEEEEEEE – the tile is filled.
5. Plane image sets
➦ plane_header.offset_image_sets
In this section there is the list of image sets from which all available tiles are supplied. It’s a sequence of plane_header.num_image_sets null-terminated strings.
GLE for some reason ignores all image sets but the first one and so all tiles available in the editor are supplied only from the first image set on the list.
6. Plane objects
➦ plane_header.offset_objects
This is a sequence of all objects on the plane. There is plane_header.num_objects of them and they are saved in order one after another.
One has to pay attention to the flag-based fields object_type and flags_hit_type. The flags user1 and user2 should not be used because of a nasty implementation bug in the editor. If you look closely at their bit masks in the listing below you will see that the mask of the flag user3 shares the bits with the masks of the user1 and user2 flags. The field object_type does not use any bit operations and uses mask values directly so using this field is relatively safe. The flags_hit_type field does however and because of that the flags user1 and user2 are not reliable and should not be used at all, or at least not in conjunction with the editor. I plan to make a blog article on that subject explaining it in more detail.
struct object { // 0x0 (0) - id int32_t id; // 0x4 (4) - number of characters in the name string uint32_t size_name; // 0x8 (8) - number of characters in the logic string uint32_t size_logic; // 0xC (12) - number of characters in the image set string uint32_t size_image_set; // 0x10 (16) - number of characters in the animation string uint32_t size_animation; // 0x14 (20) - location x int32_t location_x; // 0x18 (24) - location y int32_t location_y; // 0x1C (28) - location z int32_t location_z; // 0x20 (32) - location_i int32_t location_i; // 0x24 (36) - flags - add flags // 0x01 - difficult // 0x02 - eye candy // 0x04 - high detail // 0x08 - multiplayer // 0x10 - extra memory // 0x20 - fast cpu uint32_t flags_add; // 0x28 (40) - flags - dynamic flags // 0x1 - no hit // 0x2 - always active // 0x4 - safe // 0x8 - auto hit damage uint32_t flags_dynamic; // 0x2C (44) - flags - draw flags // 0x1 - no draw // 0x2 - mirror // 0x4 - invert // 0x8 - flash uint32_t flags_draw; // 0x30 (48) - flags - user flags // 0x001 - flag 1 // 0x002 - flag 2 // 0x004 - flag 3 // 0x008 - flag 4 // 0x010 - flag 5 // 0x020 - flag 6 // 0x040 - flag 7 // 0x080 - flag 8 // 0x100 - flag 9 // 0x200 - flag 10 // 0x400 - flag 11 // 0x800 - flag 12 uint32_t flags_user; // 0x34 (52) - score int32_t score; // 0x38 (56) - points int32_t points; // 0x3C (60) - powerup int32_t powerup; // 0x40 (64) - damage int32_t damage; // 0x44 (68) - smarts int32_t smarts; // 0x48 (72) - health int32_t health; // 0x4C (76) - rect move rect rect_move; // 0x5C (92) - rect hit rect rect_hit; // 0x6C (108) - rect attack rect rect_attack; // 0x7C (124) - rect clip rect rect_clip; // 0x8C (140) - rect user1 rect rect_user1; // 0x9C (156) - rect user2 rect rect_user2; // 0xAC (172) - user1 int32_t user1; // 0xB0 (176) - user2 int32_t user2; // 0xB4 (180) - user3 int32_t user3; // 0xB8 (184) - user4 int32_t user4; // 0xBC (188) - user5 int32_t user5; // 0xC0 (192) - user6 int32_t user6; // 0xC4 (196) - user7 int32_t user7; // 0xC8 (200) - user8 int32_t user8; // 0xCC (204) - min x int32_t min_x; // 0xD0 (208) - min y int32_t min_y; // 0xD4 (212) - max x int32_t max_x; // 0xD8 (216) - max y int32_t max_y; // 0xDC (220) - speed x int32_t speed_x; // 0xE0 (224) - speed y int32_t speed_y; // 0xE4 (228) - tweak x int32_t tweak_x; // 0xE8 (232) - tweak y int32_t tweak_y; // 0xEC (236) - counter int32_t counter; // 0xF0 (240) - speed int32_t speed; // 0xF4 (244) - width int32_t width; // 0xF8 (248) - height int32_t height; // 0xFC (252) - direction int32_t direction; // 0x100 (256) - face dir int32_t face_dir; // 0x104 (260) - time delay int32_t time_delay; // 0x108 (264) - frame delay int32_t frame_delay; // 0x10C (268) - hits - object type (single value) // 0x001 - generic // 0x002 - player // 0x004 - enemy // 0x008 - powerup // 0x010 - shot // 0x020 - p-shot // 0x040 - e-shot // 0x080 - special // 0x100 - user1 (usage not recommended) // 0x200 - user2 (usage not recommended) // 0x300 - user3 (yes - the value is not a mistake) // 0x400 - user4 (yes - the value is not a mistake) uint32_t object_type; // 0x110 (272) - hits - hit type flags // 0x001 - generic // 0x002 - player // 0x004 - enemy // 0x008 - powerup // 0x010 - shot // 0x020 - p-shot // 0x040 - e-shot // 0x080 - special // 0x100 - user1 (should be deprecated) // 0x200 - user2 (should be deprecated) // 0x300 - user3 (yes - the value is not a mistake) // 0x400 - user4 (yes - the value is not a mistake) // 0xFFFFFFFF - all (separate, special value overriding all other flags) uint32_t flags_hit_type; // 0x114 (276) - move res x uint32_t move_res_x; // 0x118 (280) - move res y uint32_t move_res_y; // 0x11C (284) - name string // fixed length, object.size_name characters char* name; // (variable) - logic string // fixed length, object.size_logic characters char* logic; // (variable) - image set string // fixed length, object.size_image_set characters char* image_set; // (variable) - animation string // fixed length, object.size_animation characters char* animation; }
7. Tile properties
➦ wwd_header.offset_tile_properties
The first 32 bytes is the header of the section:
struct tile_properties_header { // 0x0 (0) uint32_t unknown1; // 0x00000020 // 0x4 (4) uint32_t unknown2; // 0x00000000 // 0x8 (8) - the number of the tile properties uint32_t num_tile_properties; // 0xC (12) uint32_t unknown3; // 0x00000000 // 0x10 (16) uint32_t unknown4; // 0x00000000 // 0x14 (20) uint32_t unknown5; // 0x00000000 // 0x18 (24) uint32_t unknown6; // 0x00000000 // 0x1C (28) uint32_t unknown7; // 0x00000000 }
After the header there is a contiguous list of all tile properties (there is tile_properties_header.num_tile_properties of them). The tile ID a property refers to depends on its index in the list. The indices are zero-based and as such the first property in the list refers to the tile with ID 0, the second one is tile ID 1 and so on. It might be quite a surprise for some since all the tiles defined in Gruntz (and Claw to my knowledge) have IDs starting with 1 and not 0. It’s also worth noting that since the list is contiguous there may be properties referring to non-existing tiles – they simply serve as data fillers and are not used by the editor for any purpose.
Each tile property starts with the following 16 bytes:
struct tile_property_base { // 0x0 (0) - tile type // 0x1 - single // 0x2 - double // 0x3 - mask uint32_t tile_type; // 0x4 (4) uint32_t unknown1; // 0x00000000 // 0x8 (8) - tile width uint32_t width; // 0xC (12) - tile height uint32_t height; }
The following bytes depend on the value of the field tile_property_base.tile_type, which may take one of three values: 1 (single), 2 (double) or 3 (mask).
- single (1) – the most basic type of a tile – it makes the tile accept a single attribute. All the tiles in Gruntz have this type.
- double (2) – the tile has a rectangular area defined which accepts one attribute (an “inside” attribute). The remainder of the area may have a different attribute (an “outside” attribute). This tile type is used in Claw and makes the playable character be able to interact with different parts of tiles in a different way (for example make only a part of a tile solid).
- mask (3) – each pixel of the tile may have a separate attribute, which are saved as a mask. Each pixel value of the mask represent a single attribute of a corresponding pixel of the tile. This tile type is not used by any of the games. Although GLE has support for mask tile types it may act funky and in the worst case even make the editor crash.
A few words on the attributes – an attribute is a numeric value which represents the way the tile (or its part) is interacting with the rest of the game. WapWorld and GLE define five main attributes: Clear, Solid, Ground, Climb and Death. Gruntz and GLE expand this list further to almost 60 values – each attribute other than the first five main ones is treated as a User Attribute.
Here are the complete definitions of each type of tile properties and the values for the main attributes:
// Standard attribute values in WapWorld and GLE: // 0x00 (0) - clear // 0x01 (1) - solid // 0x02 (2) - ground // 0x03 (3) - climb // 0x04 (4) - death struct tile_property_single { tile_property_base base; // 0x10 (16) - tile attribute int32_t attribute; } struct tile_property_double { tile_property_base base; // 0x10 (16) - outside tile attribute int32_t attribute_outside; // 0x14 (20) - inside tile attribute int32_t attribute_inside; // 0x18 (24) - tile rect rect rect; } struct tile_property_mask { tile_property_base base; // 0x10 (16) - mask data // tile_property_base.width * tile_property_base.height bytes char* mask; }
hello, i appreciate your hard work, you give us a very good information. after i read your post, i’ve got excited and i started to convert wwd files to tilemap json files, and it works very well for now. i had never done this kind of convertion before. it became like a hoby project for me so, thanks to you. i’m kind of stuck at converting pid files to pcx or png files. i couldn’t figure out the compression algorithm to extract image data, yet. i believe image data starts at 0x20. is there a guide or something for pid file specification like what you did in this post or could you point me a direction to look at? thanks again.
Hi there! Sorry for the late response. You can see the PID file specification over here at GooRoo’s Gruntz:
https://gooroosgruntz.proboards.com/thread/2046/games-file-format-specifications
I’ve never really bothered making any articles about it or researching the subject since it was already quite well covered right there and as such it was not a high priority for me.
However if you have any more questions regarding PID files you may ask them in that very thread at GooRoo’s. I’ll be there to answer them right away 🙂 Cheers.