VGFM Level Format
Format type | Map/level |
---|---|
Map type | 2D tile-based |
Layer count | 2 |
Tile size (pixels) | 16×16 |
Viewport (pixels) | 320×159 |
Games |
In VGFM there are two types of levels: regular levels (*.M) and "Map" levels (*.MAP) - a world map where you travel between levels. They both use the same file format and structure, but some data fields are used for different purpose in Map levels.
Signature
The file contains no signature. Identification can only be done by parsing the file and checking that valid (in-range) data is at the expected locations.
File format
Data type | Name | Description |
---|---|---|
UINT16LE | height | Height of level, in tiles |
UINT16LE | width | Width of level, in tiles |
UINT16LE[width * height] | background | Background layer, width × height × 2 bytes long |
UINT8[width * height] | foreground | Foreground layer, width × height bytes long |
UINT16LE | platformTiles | Number of tiles from the start of the tileset that can be stood upon (and can be passed through from left, right and bottom direction). |
UINT16LE | solidTiles | Number of tiles from the start of the tileset that are impassable. The value is always less or equal to platformTiles, meaning that solid tiles are located always in the beginning of tileset, followed by 0 or more platform tiles. |
TAnimationEntry[15] | tileAnimations | Animated tile information |
UINT16LE | numTileAnimations | Number of tile animations that are used |
UINT16LE | noAnimationTiles | Number of tiles from the start of the tileset that should not be animated. That means animated tiles are always in the end of tileset. Lowering the value makes all tiles with index >= value animate, and eventually stay animated within the first defined animation. |
UINT8[500] | safeTiles | 0=tile hurts player, 1=tile safe, one byte for each tile in the tileset. In Map level this means whether a tile is passable or not: 0 = impassable, 1 = passable. |
UINT16LE | capsuleCount | Number of capsules (red orbs) in level (displayed on stats screen after level exit) |
UINT16LE | tileFirstItem | Tile index of first item tile. These tiles can be picked up - when touched by player they disappear and a twinkle animation is shown. |
UINT16LE | tileLastItem | Tile index of last item tile |
UINT16LE | tileFirstClimb | Tile index of first climbable tile (vine, chain, stick) |
UINT16LE | tileLastClimb | Tile index of last climbable tile |
UINT16LE | tileFirstLadder | Tile index of first ladder tile |
UINT16LE | tileLastLadder | Tile index of last ladder tile |
UINT16LE | tileFirstRail | Tile index of first overhead climbing rail tile |
UINT16LE | tileLastRail | Tile index of last overhead climbing rail tile |
UINT16LE | scrollBorderLeft | X-tile-coordinate which camera will never exceed when scrolling in left direction. The level will scroll normally as the player moves to the right, but it will only scroll back until tiles at this X-coordinate are visible. No tiles where X is less than this value will appear. If the player continues to walk off the screen to the left, graphical glitches will occur. |
UINT16LE | scrollBorderTop | Y-tile-coordinate which camera should never exceed when scrolling in up direction. Not working - probably not or wrongly implemented in the game. |
UINT16LE | scrollBorderRight | X-tile-coordinate which camera will never exceed when scrolling in right direction. As the player moves right, the level will only scroll this far. The tile at this X coordinate will never be seen, only the one at X-1. Normally this is set close to the width of the level, so that it is not possible to scroll past the edge of the level. If the player is not blocked and continues to walk off the screen to the right, graphical glitches will occur. |
UINT16LE | scrollBorderBottom | Y-tile-coordinate which camera will never exceed when scrolling in down direction. As the player moves down/looks down, the level will only scroll this far. The tile at this Y coordinate will never be seen, only the one at Y-1. Normally this is set to the height of the level of the level, so that it is not possible to scroll past the edge of the level. If the player is not blocked and can move past this point, graphical glitches will occur. |
TDoorEntry[10] | doors | Definitions of walk-through doors, allowing the player move from one place in a map to another. One door allows one-way movement, that means always 2 doors are used for two-way movement. So there can be only 5 two-way doors in a level. |
TSwitchEntry[25] | switches | Definitions of switches, which can be activated by the player to perform some change in a level (activate platform, remove blocking tiles). |
TLockEntry[15] | locks | Definitions of locks (keyhole tiles), which are unlocked by the player by touching them while holding the respective key. Usually blocking tiles are removed when unlocking a lock. In Map level locks are used to define entrance to a regular level. |
UINT16LE | numDoors | Number of doors used in level |
UINT16LE | numSwitches | Number of switches used in level |
UINT16LE | numLocks | Number of locks used in level |
UINT16LE | tileFirstTransparent | Tile index of first transparent tile rendered in front of sprites, partially covering them. This is used on barred windows, trees and some other decorations. Other tiles are rendered behind sprites. |
UINT16LE | tileLastTransparent | Tile index of last transparent tile rendered in front of sprites |
BYTE[26] | * | Unused, contains garbage/leftover data. Editing did not change anything in the level. |
CHAR[13] | tilesetFilename | Filename of tileset to use in this level, NULL terminated |
CHAR[13] | paletteFilename | Filename of palette for this level, NULL terminated |
UINT16LE | tileFirstBoundary | Tile index of first solid (impassable) tile which is used as a boundary of hidden passages. These tiles are always inside tileFirstFront - tileLastFront range, so they cover all sprites (specifically splashes and explosions). |
UINT16LE | tileLastBoundary | Tile index of last solid (impassable) tile which is used as a boundary of hidden passages. |
BYTE[1140] | * | Unused, contains garbage/leftover data. Editing did not change anything in the level. |
UINT16LE | mapFinishPixelX | X-coordinate in pixels (1 tile = 16 pixels) where you appear on Map level after you finish the level. This is for example used to move the player to different place after finishing level 4 in the first episode. |
UINT16LE | mapFinishPixelY | Y-coordinate in pixels (1 tile = 16 pixels) where you appear on Map level after you finish the level. |
UINT16LE | * | Unused, contains garbage/leftover data. Editing did not change anything in the level. It appears to store a number of used structures in the preceding unused data fields. There is place for 25 48-byte structures which seem to be related to locks and switches as they store coordinates to tiles which are removed by unlocking the lock. |
TAnimationEntry[5] | itemAnimations | Defines animations of item tiles - capsule, 100 points, 250 points, 500 points and 1000 points (never used). |
UINT16LE | tileFirstItemAnim | Tile index of first item tile which should be animated |
UINT16LE | tileLastItemAnim | Tile index of last item tile which should be animated |
TPaletteAnimationEntry[5] | paletteAnimations | Defines palette animations - palette continuously "rotates" within this range. This is used to animate falling/flowing water in several levels (most notably egyptian-themed levels in episode 3) as well as blue-circle-shaped level markers in Map level. |
UINT16LE | numPaletteAnimations | Number of palette animations that are used |
INT8[5] | tileFirstItemType | Tile index of first item tile for particular item type - capsule, 100 points, 250 points, 500 points and 1000 points (never used). |
INT8[5] | tileLastItemType | Tile index of last item tile for particular item type |
UINT16LE | tileFirstFront | Tile index of first opaque tile rendered in front of sprites, fully covering them. This is used to make hidden (secret) passages. |
UINT16LE | tileLastFront | Tile index of last opaque tile rendered in front of sprites, fully covering them. |
TSpriteDefinition[30] | usedSprites | Definitions of sprites ("object types") used in the level |
TObjectEntry[150] | objects | Definitions of objects in the level (monsters, weapons, healing potions, keys, moving platforms etc...) |
UINT16LE | numTransformationBlocks | Number of transformation blocks used in level |
TTransformationBlock [numTransformationBlocks] |
transformationBlocks | Definitions of blocks, which replace a block in a map after unlocking a lock or pressing a switch. This is almost always used to remove blocking tiles or open a door, but can be used for generally anything, like inserting a platform (as it is in E2L7). |
TAnimationEntry structure
Data type | Name | Description |
---|---|---|
UINT16LE | * | Unknown. Mostly, but not always it is 0. |
UINT16LE | lastTile | Tile index of last tile in animation sequence |
UINT16LE | firstTile | Tile index of first tile in animation sequence |
TPaletteAnimationEntry structure
Data type | Name | Description |
---|---|---|
UINT16LE | firstIndex | Index of first color in animation sequence |
UINT16LE | lastIndex | Index of last color in animation sequence |
UINT16LE | startIndex | Should be set to same value as firstIndex. Setting to a value out of range causes strange graphical effects. |
UINT16LE | * | Unknown, always 0. Editing did not change anything. |
TDoorEntry structure
Data type | Name | Description |
---|---|---|
UINT16LE | minX | Defines the door entrance area |
UINT16LE | maxX | |
UINT16LE | minY | |
UINT16LE | maxY | |
UINT16LE | destPixelX | Destination coordinates (where player's sprite appears after entering the door) in pixels (1 tile = 16 pixels) |
UINT16LE | destPixelY | |
UINT16LE | scrollBorderLeft | Behave in same way as scrollBorder values defined in level data, but these values apply in the area reached by entering the door. That means, once player enters any door, the values defined in level data are never valid again (only after player's death) |
UINT16LE | scrollBorderRight | |
UINT16LE | scrollBorderTop | |
UINT16LE | scrollBorderBottom |
TSwitchEntry structure
Data type | Name | Description |
---|---|---|
UINT16LE | posX | Coordinates of the switch in tiles |
UINT16LE | posY | |
UINT16LE | initialState | 0 = Down, 1 = Up. The switch action is performed only when it is flipped from Up state to Down state, so that it is always (and should be) set to 1. |
UINT16LE | switchType | Type of switch action: 1 = Activate object, 3 = Transform tiles, 4 = Remove tiles from foreground layer, 5 = Remove objects. |
UINT16LE | var1 | Various meaning, depends on the switch type. |
UINT16LE | var2 | |
UINT16LE | var3 | |
UINT16LE | var4 | |
UINT16LE[5] | * | Unknown, editing did not change anything. |
For type 1 (Activate object) var1 is the object number to activate. Note: Technically a switch of this type sets the "var1" property on an object to "1". For most object/behavior types this value means whether the object is active. But a vertical platform uses "var2" property to determine whether it is active, that's why vertical platform unfortunately cannot be activated with a switch.
For type 3 (Transform tiles) var1 is the number of transformation block which is applied.
For type 4 (Remove tiles from foreground layer) var1 and var2 are X/Y coordinates of the target area and var3/var4 are width/height of the target area (in tiles). Note: At most 24 tiles can be removed this way.
For type 5 (Remove objects) var1 and var2 are first/last object numbers to remove. The numbers are not absolute, but are counted from 0 through only valid objects, so if there is for example free space of 5 objects in the object array, the following objects have number less than 5 than their index. Note: The removed objects must be out of the viewport. If you remove objects which are on-screen they won't disappear completely but become flickering. They will eventually disappear after leaving the viewport.
The switch action is irreversible once the switch is activated. When the switch state is changed, the tile in background layer is changed to the respective adjacent tile in the tileset.
TLockEntry structure
Data type | Name | Description |
---|---|---|
UINT16LE | keyX | Coordinates of the corresponding key in tiles. This is not used by the game (editing these values does not change anything) because keys are defined as objects. |
UINT16LE | keyY | |
UINT16LE | posX | Coordinates of the lock (keyhole tile) in tiles |
UINT16LE | posY | |
UINT16LE | * | Unknown, editing did not change anything. |
UINT16LE | transblockNumber | Number of transformation block which is applied when unlocking the lock. |
INT16LE | messageNumber | Number of message which is displayed when touching the lock without the corresponding key. 0 = "You need a gold key", 1 = red, 2 = green, 3 = blue. Any other value (including negative values) will display some other message from the game's list. |
UINT16LE | showMessage | 0 = Message is shown, nonzero value = Message is not shown |
UINT16LE | * | Unknown, editing did not change anything. |
UINT16LE | * | Unknown, editing did not change anything. |
In Map level locks define entrances to levels. In this case keyX/keyY/posX/posY define coordinates of the level entrance area (always posX = keyX + 1, posY = keyY + 1).
TSpriteDefinition structure
Data type | Name | Description |
---|---|---|
CHAR[16] | name | File name of the sprite (always including .cmp extension), null-terminated. |
UINT16LE | width | Sprite width |
UINT16LE | height | Sprite height |
INT8 | behavior | This is the same as behavior value defined for each object separately but this "global" value is applied when the object is spawned during gameplay (a projectile is shot). The value means how the sprite moves and how animates. |
INT8 | kind | Defines the type of object - whether hurts player or not, whether can be stood upon etc: 2 = monster, 3,4 = hazard/projectile (hurts), 5 = pick-up item, 6 = exit, 10 = decoration, 11 = can be stood upon |
INT8 | numSprites | Number of sprites in the cmp file |
INT8 | itemType | Type of the item: 0 = weapon, 1 = healing, 3 = key, 5 = checkpoint |
INT8 | fullHealth | 1 = if healing item, restores full health |
INT8 | * | Unknown, always 0 |
UINT16LE | firstSpriteIndex | It is sum of numSprites value of all preceding sprite definitions |
UINT16LE | childSprite | Sprite (sprite definition number) which is spawned by this sprite (fired projectlile, pick-up animation) |
BYTE[92] | * | ! Not known yet, seems to be more details about behavior, sprite offsets etc. |
The sprite definition with index 0 has always empty name and it is the player sprite (used as starting point)
TObjectEntry structure
Data type | Name | Description |
---|---|---|
INT16LE | objType | Object type - index of sprite definition. -1 = object is not used. |
UINT16LE | pixelX | X-coordinate in pixels (1 tile = 16 pixels) |
UINT16LE | pixelY | Y-coordinate in pixels (1 tile = 16 pixels) |
INT8 | var1 | Various meaning, depending on object type and behavior (whether monster or platform is active, weapon type, item animaiton speed) |
INT8 | var2 | Various meaning, depending on object type and behavior (key number) |
UINT16LE | * | Unknown, always 0 |
UINT16LE | phase | Used on in-out platforms in E2L2, also works on spikes |
BYTE[6] | * | Unknown, always 0 |
UINT16LE | var3 | Various meaning, depending on object type and behavior (platform speed, swing/circling radius) |
UINT16LE | * | Unknown |
BYTE[20] | * | Unknown, always 0 |
UINT16LE | behavior | Means how the sprite moves and how animates: 1, 2 = Move horizontally (moving platform), 4 = Walking and shooting (monster), 8 = Move vertically (moving platform), 12 = Continuous animation (item), 16 = Jump (jumping spike ball), 23 = Swing, 38 = Move in circle, and many more values |
The object with index 0 is always the starting point (and is of type 0).
TTransformationBlock structure
Data type | Name | Description |
---|---|---|
UINT16LE | posX | Target coordinates of the block in tiles |
UINT16LE | posY | |
UINT16LE | width | Block size |
UINT16LE | height | |
UINT16LE | numBytes | Size of tiles array in bytes, equal to width×height×2 |
UINT16LE | * | Unknown, editing did not change anything |
UINT16LE | * | Unknown, editing did not change anything |
UINT16LE[numBytes/2] | tiles | Tiles (tile indexes) in the block |
Note: Only up to 25 tiles can be transformed at once, so this is a limitation of transformation block size. If a transformation block has more tiles, the tiles above 25 (counted up-to-down, left-to-right) will not be visually redrawn, but still will be internally changed and redrawn respectively if going off-screen and then on-screen. This limit includes also the switch tile so you can use a block of at most 24 tiles if it is triggered with a switch.
Tile mapping
Each tile code in both the foreground and background layer is an index into the tileset. Since the foreground layer only uses one byte for each tile, it can only store tiles up to index 255. The background layer's maximum of 65,535 is well within the 500 tiles in the tileset.
In the foreground layer, a code of 0 is used for an empty tile which is not drawn. In the background layer there is no value reserved for an empty tile, so 0 is valid tile index there.
The background layer can only consist of opaque tiles (tiles not having any transparent pixels) and the foreground layer can only contain transparent tiles (tiles which have transparent pixels). Both types of tiles are stored in different format in the tileset and if a tile is used in the background layer the game treats it as if it was stored in the opaque tile format and vice versa. Using tiles improperly results in wrongly rendered tiles.
Unclear information
- It is not known how the game determines the player's position on the Map level when saving game inside a regular level. When you restore this saved game you appear on the Map level standing just before the marker of the level you saved in, and the position is part of the saved game. The position is neither stored in the regular level nor the Map level (editing all unknown/unused data didn't change it). It is possible that this information is stored in a different file or is hard-coded in the executable.
- It is not clearly known how the "locked doors" work (the walk-through doors you cannot pass until unlocked, they appear only in E3L1). It was discovered that the game checks for tiles which are in the door entrance area and for some particular tiles it won't let the player pass (namely the tiles which contain iron bars). But it is not known how these tiles are determined - it is possible that the game checks the pixels in the tiles and for some particular colors it treats the tile as "locking" the doors.
Credits
This file format was reverse engineered by Malvineous and Hisymak. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)