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 enough to Fields that the two terms can be used basically interchangeably, but there is not quite a 1:1 correspondence. For example, the two halves of the Chamber of Birth are divided across map15.msd and map16.msd.
  • A "scene" is a unit smaller than a field but (potentially) larger than a single screen. For example, screens B3-B5 in the Gate of Guidance are a single scene, and likewise screens D3-F4 in the Temple of the Sun. Scene 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 scene to another. The number of screens in a scene 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 fields define a number of scenes 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 scenes used this way are less than the size of a screen, but any size scene can be placed within another scene.
  • 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.
  • See data/graphics/hit_parts.png for clues about the various possible values of the CollisionMask bytes. The most common are 0x00 (unmasked, can be walked through) and 0x80 (orange, solid wall). The green ladder-looking-structures are ladders. The light blue backdrops are water (and the arrows are water currents), followed by purple lava. The vertical blue lines are waterfalls. The brown checkerboard is an area that Yowies can crawl on. The blue walls are ice/walls that the Grapple Claw doesn't work on. The orange slopes are slippery and require Hermes' Boots to walk up. The dark green arrow tiles are walls that you can die from being smashed into by moving blocks.
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 NumberOfScenes;
	struct Scene {
		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
	} Scenes[NumberOfScenes];
}

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: animated tile. 1: standard tile. 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 Scene 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 seem to include any explicit markers of how many fields, zones, or rooms there are.
  • Each "field" 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 EventIDs, although 4 of them never appear in-game, each of which has the same number of parameters every time it appears, despite the .rcd format stating that number explicitly. For example, a Wall Seal, EventID 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 EventIDs, as well as the purposes of their numerous parameters, is currently being worked on.
  • Events are divided into two types, Objects and Effects, the difference being that Objects are placed in specific positions in specific rooms, whereas Effects don't have specific positions, and may apparently be bound to rooms, zones, or entire fields. An example of an Object would be a Pot; an example of an Effect would be specifying that Chonchons should be continually generated in a room. Any given EventID is either always an Object or always an Effect.
struct ScriptDotRCD {
	short Unknown; //purpose unknown, skipped internally; possibly intended as a version number? always zero?
	struct Field {
		byte LengthOfInternalFieldName;
		short FieldEffectCount; 
		byte FieldName[LengthOfInternalFieldName]; //uses UTF-16 encoding; does nothing. 
		Effect FieldEffects[FieldEffectCount];
		struct Scene {
			short SceneEffectCount;
			Effect SceneEffects[SceneEffectCount];
			struct Room {
				byte InternalNameLength;
				short NumberOfObjects;
				byte NumberOfEffects;
				Effect Effects[NumberOfEffects];
				Object Objects[NumberOfObjects - NumberOfEffects];
				byte InternalRoomName[InternalNameLength]; //name of zone in UTF-16 encoding. Does nothing
				struct Exit {
					byte FieldID;
					byte SceneID;
					byte ScreenID;
				} Exits[4]; //order is top, right, bottom, left
			} Screens[];
		} Scenes[];
	} Fields[];
}

struct Object {
	short EventID;
	bit4 NumberOfTestFlags;  //NumberOfTestFlags and NumberOfWriteFlags together make up a single byte
	bit4 NumberOfWriteFlags; 
	byte NumberOfParameters;
	short PositionX;
	short PositionY;
	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 Effect { //exactly the same as Object, but with no Position properties
	short EventID;
	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 Field
byte Scene
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