FANDOM


La-Mulana stores its various data across a variety of different files, all of which (except for the various .png, .wav, and .ogg files) to use custom file formats. Fortunately none of the files seem to employ any data compression, making understanding their formats a comparatively simple project.

map##.msd format Edit

Used by data/mapdata/map00.msd through data/mapdata/map25.msd, as well as all the mapdata/map##.msd files for each of the time attack modules. There are a number of other files in the same folder with the same extension (boss##.msd, ending#.msd, opening.msd, shop.msd, title.msd), which have similar (though not quite identical) formats.

  • Nearly all properties are signed and big-endian.
  • .msd files correspond closely to Fields, but due to some differences, are known as Zones. There are three Zones in Gate of Time, one for Mausoleum of the Giants, one for Gate of Guidance, and one for Surface. Surface at night is its own Zone, and the upper and lower halves of Chamber of Birth are each one zone. Finally, Hell Temple contains two Zones in order to get around limitations on the size of the sprite sheet.
  • A "room" is a unit smaller than a zone but (potentially) larger than a single screen. For example, screens B3-B5 in the Gate of Guidance are a single room, and likewise screens D3-F4 in the Temple of the Sun. Room boundaries are discernible in-game in at least two ways: parallax scrolling only works when going from one screen in a scene to another, and more objects respawn when leaving and reentering a scene than when going from one screen in a room to another. The number of screens in a room is not explicitly stated but is instead calculated by multiplying the width (divided by 32 tiles) and height (divided by 24 tiles).
  • Additionally, most .msd files define a number of room which are smaller than a single screen. These scenes are placed inside of other scenes so that they can be switched out or moved. For example, the statue of Sakit in Mausoleum of the Giants is composed of one of these so that he can disappear after the boss fight. All rooms used this way in the base game are the size of a screen or less, but any size room can be placed within another room.
  • The function of the GraphicsFilesID byte differs slightly between main game and time attack .msd files. In the main game GraphicsFilesID references a hardcoded array, but in time attack, it is the index of the (uncommented) line in list.dat minus 2.
  • The CollisionMask bytes have several acceptable values, listed below in hexadecimal.
    • 00 is air.
    • 01, 02, 03, 04 are the left, left middle, right middle, and right tiles of a ladder.
    • 05 is water.
    • 06, 07, 08, 09 are water flowing up, right, down and left.
    • 0a, 0b, 0c, 0d are the tiles of an underwater ladder.
    • 10 is lava.
    • 11, 12, 13, 14 are lava flowing up, right, down and left. (Only left is used, in time attack)
    • 15, 16, 17, 18 are the tiles of an underlava ladder.
    • 20 is a waterfall.
    • 30 is the type of tile which Yowies can move on.
    • 7f is air, but unlike 00, is capable of replacing walls when placed on top of them. (used with trapdoors)
    • 80 is a wall or floor.
    • 81, 82 are 45° slopes, slanted in the same way as / and \, respectively.
    • 83, 84, 85, 86 are 30° slopes, the bottom and top of the / slope and the top and bottom of the \ slope.
    • 87, 88, 89, 8a are 60° slopes (which you cannot stand on), the bottom then top of the / slope, then the top and bottom of the \ slope.
    • 8c is an ice wall or floor
    • 8d, 8e are the ice 45° slopes.
    • 8f, 90, 91, 92 are the ice 30° slopes.
    • a1 is 45° sand flowing left.
    • a3 is 45° sand flowing right.
    • a6, a7 are 30° sand flowing left. This is not used anywhere. It is unknown if it works.
    • aa, ab are 30° sand flowing right. This is not used anywhere. It is unknown if it works.
    • b5, b6 are 45° ceiling slopes. Though they are frequently used, it is difficult to tell if they have any effect.
    • b7, b8 are 30° / slanted ceilings. They are only used in Inferno Cavern twice each, and it is unknown if they do anything.
    • c0, c1 are crusher tiles. When pushed into the wall, c0 crushes Lemeza vertically, and c1 crushes Lemeza horizontally.

Other tiles may exist, but are never used and are not present in data/graphics/hit_parts.png, which appears to be a debug file for displaying ColisionMask tiles.

struct MapMSD {
	struct AnimatedTiles {
		bit1 AnimateInBoss; // only used in boss06.msd (Tiamat), and is required for animations to work in boss fights.
		bit15 NumberOfFrames; // the number of images used.
		AnimatedTileID Frames[NumberOfFrames];
	} AnimatedTiles[];
	short EndOfAnimatedTileSection; //always 00 00, which would be parsed as an animated tile that is zero frames long
	byte GraphicsFilesID; //Subtract two, then reference the array below. In shops, always use 02shop.png . In ending1.msd, ending2.msd, and title.msd, always use title01.png . In time attack, subtract two, then reference list.dat
	short NumberOfRooms;
	struct Rooms {
		byte UseBossGraphics; //If 1, uses the boss graphics in this Scene. The boss graphics file used is b##.png where ## is the number of the msd file.
		byte NumberOfLayers;
		byte NumberOfPrimeLayer; //Layers before the prime layer are in the foreground, and layers after the prime layer are in the background. The size of the prime layer determines the size of the Zone. The size of the hit tile mask is always the same as the size of the prime layer.
		short HitMaskWidth; //measured in hit tiles, which are 10x10
		short HitMaskHeight; //measured in hit tiles
		byte HitMask[HitMaskWidth][HitMaskHeight];
		struct Layer {
			short LayerWidth; //measured in graphical tiles, which are 20x20
			short LayerHeight; //measured in graphical tiles
			byte NumberOfSublayers;
			struct Sublayer {
				Tile[LayerWidth * LayerHeight];
			} Sublayers[NumberOfSublayers]; //ordered so that the first is in front and the last is in back
		} Layers[NumberOfLayers]; //ordered so that the first is in front and the last is in back
	} Rooms[NumberOfRooms];
}

struct Tile {
	bit11 TileCoords; //each row in the map##_1.png files is 50 tiles wide, so TileCoords=49 would be tile 49,0 and TileCoords=50 would be tile 0,1. It doesn't matter whether the .png is the full 1024 pixels tall. Shops are hardcoded to use 51 tile width instead.
	bit2 TileType; //0: empty. 1: standard. 2: color addition. 3: color multiplication.
	bit1 FlippedHorizontally; //boolean
	bit1 Rotated90Degrees; //boolean
	bit1 Rotated180Degrees; //boolean
}
struct AnimatedTileID {
	bit5 FramesWait // Multiplied by two. For the first frame (not on repeat), only use the first four bits and multiply by four.
	bit11 TileCoords // As in Tile
};

char * GraphicsFiles[] = { // This array is used in a Room when UseBossGraphics is 0.
     "map00_1.png",
     "map01_1.png",
     "map02_1.png",
     "map03_1.png",
     "map04_1.png",
     "map05_1.png",
     "map06_1.png",
     "map07_1.png",
     "map08_1.png",
     "map09_1.png",
     "map10_1.png",
     "map11_1.png",
     "map12_1.png",
     "map13_1.png",
     "map14_1.png",
     "map15_1.png",
     "map16_1.png",
     "map17_1.png",
     "",
     "",
     "",
     "",
     "",
     "",
     "",
     "",
     "",
     "",
     "",
     "map18_1.png",
     "map19_1.png",
     "map19_1.png"
};

.rcd format Edit

Also known as the Ruins Code. Used by data/mapdata/script.rcd, as well as mapdata/script.rcd for each of the time attack modules.

  • Note that it is impossible to parse an entire .rcd file without simultaneously parsing all the corresponding .msd files, because the .rcd format does not include any explicit markers of how many zones, rooms, or screens there are.
  • Each "zone" corresponds to a .msd file, not necessarily a field as is presented to the game player.
  • Again, all properties are to be signed and big-endian.
  • There are 203 different Objects which can be referenced directly via rcd, although 4 of them never appear in-game, each of which is capable of accepting 0-4 tests, 0-4 updates, and up to 32 arguments. Not specifying an argument that is read results in the object not being initialized properly, and using the values that were already present in the internal object table. For example, a Wall Seal, Object 52, always has exactly two parameters: one for specifying which key seal is needed to open it (values 0-3), and one for specifying whether it is one of the giant seals in the True Shrine of the Mother (value 1) or only a regular-sized seal (value 0). A full list of these ObjectIDs, as well as the purposes of their numerous parameters, is currently being worked on.
  • Objects either always have a position or optionally have a position. Giving an Object a position when it doesn't usually have one has no effect. An example of an Object with a position would be a Pot; an example of an Object with no position would be specifying that Chonchons should be continually generated in a room.
struct ScriptDotRCD {
	short Unknown; //purpose unknown, skipped internally; possibly intended as a version number? always zero?
	struct Field {
		byte LengthOfInternalZoneName;
		short ZoneObjectsCount;
		byte ZoneName[LengthOfInternalZoneName]; //uses UTF-16 encoding; does nothing. 
		Object ZoneObjects[ZoneObjectCount];
		struct Room {
			short RoomObjectCount;
			Object RoomObjects[RoomObjectCount];
			struct Screen {
				byte InternalNameLength;
				short NumberOfObjects;
				byte NumberWithoutPosition;
				Object ObjectsWithoutPosition[NumberWithoutPosition];
				Object Objects[NumberOfObjects - NumberWithoutPosition];
				byte InternalScreenmName[InternalNameLength]; //name of zone in UTF-16 encoding. Does nothing
				struct Exit {
					byte ZoneID;
					byte RoomID;
					byte ScreenID;
				} Exits[4]; //order is top, right, bottom, left
			} Screens[];
		} Rooms[];
	} Zones[];
}

struct Object {
	short ObjectID;
	bit4 NumberOfTestFlags;  //NumberOfTestFlags and NumberOfWriteFlags together make up a single byte
	bit4 NumberOfWriteFlags; 
	byte NumberOfParameters;
	short PositionX; // Value is not in rcd for objects without a position.
	short PositionY; // Value is not in rcd for objects without a position.
	struct TestByteOperations {
		short Flag; // In the save file, flags are found with an offset of 0x11. Flags 0-50 are reset every Screen. Flags 51-100 are reset every Room.
		byte Value;
		TestByteOp Operation; // see below
	} TestByteOperations[NumberOfTestFlags];
	struct WriteByteOperations {
		short Flag; // In the save file, flags are found with an offset of 0x11. Flags 0-50 are reset every Screen. Flags 51-100 are reset every Scene.
		byte Value;
		WriteByteOp Operation; // see below
	} WriteByteOperations[NumberOfWriteFlags]
	short Parameters[NumberOfParameters];
}
struct ObjectWithoutPosition { //Stored by the game exactly the same way as objects with a position; they're the same except for not providing a position in the Ruins Code.
	short Object;
	bit4 NumberOfTestFlags;
	bit4 NumberOfWriteFlags;
	byte NumberOfParameters;
	TestByteOperations  TestByteOperations[NumberOfTestFlags];
       WriteByteOperations WriteByteOperations[NumberOfWriteFlags];
	short Parameters[NumberOfParameters];
}

// The Byte Operations use switch statements to refer to an array of all byte operations that the game uses, though not all are accessible from the Test and Write operations.

enum TestByteOperations {
	EQUALITY_OPERATOR		= 0x00,
	LESS_THAN_OR_EQUAL_OPERATOR	= 0x01,
	GREATER_THAN_OR_EQUAL_OPERATOR	= 0x02,
	BITWISE_AND_IS_NONZERO_OPERATOR	= 0x03, // True if the result of a bitwise AND operation is non-zero
	BITWISE_OR_IS_NONZERO_OPERATOR	= 0x04, // True if the result of a bitwise OR operation is non-zero
	BITWISE_XOR_IS_NONZERO_OPERATOR	= 0x05, // True if the result of a bitwise XOR operation is non-zero
	FLAG_IS_ZERO_OPERATOR 		= 0x06,	// bugged and always true.
	NOT_EQUAL_OPERATOR		= 0x40,
	GREATER_THAN_OPERATOR		= 0x41,
	LESS_THAN_OPERATOR		= 0x42,
	BITWISE_AND_IS_ZERO_OPERATOR	= 0x43, // True if the result of a bitwise AND operation is zero
	BITWISE_OR_IS_ZERO_OPERATOR	= 0x44, // True if the result of a bitwise OR operation is zero
	BITWISE_XOR_IS_ZERO_OPERATOR	= 0x45, // bugged and always true.
	FLAG_IS_NONZERO_OPERATOR	= 0x46
} TestByteOperations;
	
enum WriteByteOperations {
	ASSIGNMENT_OPERATOR		= 0,
	ADDITION_ASSIGNMENT_OPERATOR	= 1,
	SUBTRACTION_ASSIGNMENT_OPERATOR	= 2,
	MULTIPLICATION_ASSIGNMENT_OPERATOR = 3,
	DIVISION_ASSIGNMENT_OPERATOR	= 4,
	BITWISE_AND_ASSIGNMENT_OPERATOR	= 5,
	BITWISE_OR_ASSIGNMENT_OPERATOR	= 6,
	BITWISE_XOR_ASSIGNMENT_OPERATOR	= 7
} WriteByteOperations;

Save Format Edit

All values are big-endian.

byte Valid //save unrecognized if 0. Alays 1
dword GameTime // milliseconds
byte Zone
byte Room
byte Screen
short Xposition
short Yposition
byte MaxHP // MaxHP / 32
short CurrentHP
short CurrentExp
byte Flags[4096] // flags 0-99 are saved but zeroed on load.
short Inventory[255] // inventory words
byte HeldMainWeapon // inventory number. -1 for empty
byte HeldSubWeapon // inventory number. -1 for empty
byte HeldUseItem // inventory number. -1 for empty
byte HeldMainWeaponSlot // menu slot number.
byte HeldSubWeaponSlot // menu slot number.
byte HeldUseItemSlot // menu slot number.
short TotalEmails // from game data
short ReceivedEmails // count of emails received
struct Email {
	short ScreenplayCard
	dword GameTimeReceived // milliseconds
	short MailNumber // as displayed in Xelpud Mailer
} Email [TotalEmails];
byte EquippedSoftware [20] // inventory number in order of equipping
short RosettasRead [RosettaCount] // stores the screenplay card number of any rosetta read, in order of reading. 
//Rosetta count is the contents of the data at screenplay card 4, record 51. 
// If the rosetta count is greater than 32, use 3.
struct BunemonRecord {
	byte SlotNumber // -1 means not recorded. Only this value is changed on deletion.
	short FieldMapCard // in Screenplay
	short FieldMapRecord // In languages other than Japanese, the game adds 1 to the record number.
	short LocationCard // Shopkeeper name or Scene name.
	short LocationRecord // No math is performed.
	short TextCard
	short TextRecord
	byte IsTablet // if 1, display the slate described in record 2 and 3 of the TextCard (if one is present)
} BunemonRecord [20];
byte MantraLearned [10] // boolean. Same order as the mantra menu
dword MapsOwnedBitArray // LSB is field 0. Set based on the field that the map was received in, not the field the map works in. All maps in the base game would be 0011 0111 1111 1111
// Zero fill