MENU file format
Applies to:
BGEE, BG2EE, IWDEE (patch 2.0 and later)
General Description
MENU files (resource type
The engine loads exactly one MENU resource by itself:
This page describes the parser and the data structures it builds, as implemented in patches 2.6.6.0 and 2.7.3.0 of BGEE, BG2EE and IWDEE. All six of these builds were examined. Their keyword tables, grammars and parser tables are identical, and the few differences that exist are listed under Version differences. PSTEE and other patch levels were not examined.
Descriptions marked † are inferred from the engine's own field and function names; the code that consumes them was not traced in detail. Everything else on this page was checked against the executables.
0x0408, extension .menu) define the user interface of the Enhanced Editions since patch 2.0. A MENU file is plain text. It declares menus (screens and panels), the items (controls) they contain, and the Lua code that drives them.The engine loads exactly one MENU resource by itself:
UI.MENU, read once at start-up after the engine's own Lua functions have been registered. The file is lexed and parsed in a single pass. Lua code embedded in it is compiled into the same global Lua state that the rest of the user interface uses. The Lua functions available to menu code are listed on the MENU Lua interface page, and the other globals the engine defines on the Engine Lua globals page.This page describes the parser and the data structures it builds, as implemented in patches 2.6.6.0 and 2.7.3.0 of BGEE, BG2EE and IWDEE. All six of these builds were examined. Their keyword tables, grammars and parser tables are identical, and the few differences that exist are listed under Version differences. PSTEE and other patch levels were not examined.
Descriptions marked † are inferred from the engine's own field and function names; the code that consumes them was not traced in detail. Everything else on this page was checked against the executables.
The file is read as one NUL-terminated string and split into tokens as follows:
Syntax errors. When a token cannot be accepted, the engine logs
| Token | Rules |
|---|---|
| White space | Any character accepted by the C function isspace (space, tab, CR, LF, VT, FF) separates tokens. |
| Comment | -- starts a comment that runs to the end of the line. A single - is a minus sign. |
{ } |
Open and close a block. |
| String | Text between a pair of double quotes ("…") or a pair of single quotes ('…'). The string ends at the next occurrence of its opening quote character, so the other quote character can appear inside it. There are no escape sequences, and a string can span several lines. An unterminated string runs to the end of the file. |
| Number | A run of decimal digits, converted with atoi. There are no fractions or hexadecimal numbers: 1.5 is read as 1, a skipped . and 5. A negative value is written as - followed by a number, and the sign may be repeated (- -5 is 5). |
| Word | A run of ASCII letters and digits that does not start with a digit. If the word matches one of the keywords, compared case-insensitively, it is that keyword; otherwise it is treated exactly like a string. Underscores are not part of a word: unquoted My_Menu is the string My followed by the keyword menu. Always quote names. A word of 64 or more characters makes the engine abort. |
| Inline Lua | Text between two backticks (`…`) is Lua code. The lexer runs it as soon as it reaches it, under the chunk name menu, so it runs before any part of the file that follows it is parsed. It produces no token. The engine drops the last character before the closing backtick, so end every inline block with a line break or a space. A compile error or a runtime error is logged and shown in a message box, and parsing continues. |
| Anything else | Any other character (for example , ; = ( .) is silently skipped, so area 0, 0, 10, 10 is the same as area 0 0 10 10. |
Syntax errors. When a token cannot be accepted, the engine logs
Invalid token in menu: [token] on line L, character C. A Menu Error message box is shown as well when the Debug Mode option of the Program Options section is set. The line number counts the line breaks before the token, so the first line of the file is line 0, and on that line the character number is meaningless. The offending token is discarded and parsing continues. Further errors are not reported until three tokens have been accepted again.
The parser is generated by the Lemon parser generator. The grammar below was rebuilt from its tables (157 rules) and is identical in all examined builds. Quoted symbols are literal tokens.
+ means one or more and | separates alternatives. Keywords are case-insensitive.
file ::= menu+
menu ::= 'menu' '{' menu_prop+ item+ '}'
item ::= item_type '{' item_prop+ '}'
| 'list' '{' column+ item_prop+ '}'
item_type ::= 'label' | 'text' | 'button' | 'edit' | 'movie' | 'slider'
| 'handle' | 'slot' | 'map' | 'template'
column ::= 'column' '{' column_prop+ item+ '}'
column_prop ::= 'width' value
item_prop ::= one of the item properties below
| item -- a nested item
menu_prop ::= one of the menu properties below
number ::= NUMBER | '-' number
value ::= number | STRING | 'lua' STRING
color ::= value | number number number number
halign ::= 'left' | 'top' | 'right' | 'bottom' | 'center'
This grammar implies the following rules:- Every menu needs at least one menu property and at least one item, and all menu properties must come before the first item.
- Every item needs at least one property. An empty block such as
label { }is a syntax error. - In a
list, allcolumnblocks come first and the list's own properties follow them. Each column starts with one or morewidthentries and then holds one or more items. - The parser accepts every item property in every item type. The type only decides which fields are used when the item is drawn and handled.
- Alignment words are interchangeable:
leftandtopboth mean 0,rightandbottomboth mean 1, andcentermeans 2.
The keyword table has 124 entries for 122 distinct words, and it is identical in all examined builds. Keywords are matched case-insensitively; the spelling below is the engine's.
name and top each appear twice in the table with the same meaning, and transparent is the same keyword as usealpha.action, actionalt, actionbar, actiondbl, actiondrag, actionenter, actionexit, actionHold, actionScroll, actionsimpledrag, actionsimpledrop, actionUpdate, align, area, areamap, background, bam, bitmap, bottom, button, center, clickable, clunkyScroll, color, colordisplay, column, count, dynamic, edit, enabled, encumbrance, fill, font, force, frame, frameTimes, full, func, glow, greyscale, handle, hide, hidehighlight, highlight, highlightgroup, icon, ignoreEsc, ignoreEvents, label, left, list, loop, lower, lua, map, maxchars, maxlines, memorizedSpellInfo, menu, modal, mosaic, movie, name, offset, on, onclose, onopen, opacity, overlayTint, pad, palette, panel, paperdoll, placeholder, point, portrait, position, progressbar, pulse, queuedMovie, rectangle, res, respectClipping, respectConstraints, right, rowbackground, rowheight, rowwidth, scaleToClip, scrollbar, seperator, sequence, sequenceonce, settings, shadow, size, skipReset, slider, sliderBackground, slot, slotinfo, sound, spellInfo, state, style, subtitle, table, template, text, tint, toggle, tooltip, top, transparent, upper, usages, usealpha, useFontZoom, useOverlayTint, var, width, worldmap| Form | Meaning |
|---|---|
NUMBER |
A literal integer. In a value it is stored as an integer constant. |
STRING |
A literal string, used exactly as written. Where a property takes Lua code as a string (marked statement or expression in the tables), the string is compiled into a Lua function. See How a menu becomes Lua. |
lua STRING |
A Lua expression. It is compiled into a function that returns the expression, and the function is called every time the engine needs the value. |
| value | Any of the three forms above. A property that needs a number reads an integer constant directly, or calls the Lua function and takes its result: a number is truncated to an integer and a boolean becomes 0 or 1, while anything else, or a Lua error, gives 0. A property that needs a string reads a string constant directly, or calls the function and takes its result as a string. Integer and string constants are not converted into each other, so use the form the property expects. |
| color | Four numbers a b c d are packed into the 32-bit value a + b×28 + c×216 + d×224. A single number or string is a key into the global Lua table fontcolors (a number is converted to its decimal text). The table entry must be a hexadecimal string, and it is read while the file is parsed. For a number key with no matching entry the colour is 0xFFFFFFFF; a string key must exist. lua "…" gives a colour that is evaluated at run time. |
A menu is identified by its name together with its
panel and state numbers. Names are compared case-insensitively. Menus are kept sorted by these three keys and found by binary search, so if two menus share all three, which one is found is unspecified. Every menu should have a name, because a missing name is passed to the string comparison as a null pointer. The engine stores at most 256 menus and does not check this limit.| Syntax | Description |
|---|---|
name STRING | Menu name, used by Infinity_PushMenu and similar functions. |
panel number | Panel number: the second part of the menu's identity (default 0). |
state number | State number: the third part of the menu's identity (default 0). |
modalmodal value | Marks the menu as modal. Without an argument the value is 1. † |
opacity value | Menu opacity. † |
greyscale value | Draws the menu in greyscale. † |
onopen STRING | Lua statement run when the menu opens. |
onclose STRING | Lua statement run when the menu closes. |
enabled STRING | Lua expression that decides whether the menu is active. † |
offset number number | Menu offset x y. It can be read and changed with Infinity_GetOffset and Infinity_SetOffset. |
align halign halign | Horizontal and vertical alignment of the menu (0, 1 or 2; see Grammar). † |
size number number | Menu width and height. † |
ignoreEsc | Sets the menu's ignoreEsc flag, so the menu ignores the Escape key. † |
| Keyword | Engine type | Notes |
|---|---|---|
label | ITEM_NONE (0) | Static item: text, images, rectangles. |
text | ITEM_TEXT (1) | Text area. |
button | ITEM_BUTTON (2) | Clickable item. |
list | ITEM_LIST (3) | Table of rows built from column blocks. Each list also creates an empty global Lua table; see How a menu becomes Lua. |
edit | ITEM_EDIT (4) | Text input bound to a Lua variable (var). |
movie | ITEM_MOVIE (5) | Plays a WBM movie. See Infinity_PlayMovie. |
slider | ITEM_SLIDER (6) | Slider bound to a Lua variable (position). |
handle | ITEM_HANDLE (7) | Drag handle. † |
slot | ITEM_SLOT (9) | Inventory or spell slot. |
map | ITEM_MAP (10) | Area map or world map, selected with areamap or worldmap. |
template | ITEM_TEMPLATE (11) | Holds one nested item, which serves as the prototype for instances created with Infinity_InstanceAnimation. |
The engine's type list also contains
ITEM_DRAGGABLE (8), but no keyword creates it.Nested items. Any item can contain another item block. The parser builds items in one shared, static work record and only copies it into a new item when the closing
} is reached. A nested block therefore receives every property the enclosing item declared before it, and the enclosing item starts again from defaults. Only the most recent nested block is remembered (in the enclosing item's template slot). This is why a template is written with its nested item first and its own properties after it:
template
{
label
{
bam "B3TBACKG"
scaleToClip
}
name "B3Timer_Menu_TEMPLATE_Background" -- after the nested item
}
Items are kept in file order. Only top-level items are entered in nameToItem; nested items and items inside list columns are not.
The group headings name the engine sub-record the property writes to. Properties that write the same field overwrite each other, and the last one wins.
Note on
| Syntax | Description |
|---|---|
| General | |
name STRING | Item name. Top-level items with a name are entered in nameToItem, which the Infinity_* functions use to find them. |
area x y w h | Position and size. All four are numbers. |
pad number number number number | Padding, as four values. † |
align halign haligntext align halign halign | Horizontal and vertical alignment (both forms set the same fields). |
enabled STRING | Lua expression. Before it is evaluated the engine sets the global instanceId to the item's instance number. While the expression is false, the item is not drawn. |
ignoreEvents | The item ignores input events. † |
opacity value | Item opacity. † |
glow value | Glow effect. † |
pulse value | Pulse effect. † |
fill color | Fill colour. † |
rectangle number | Draws a frame rectangle of style number. The engine stores number + 1 and treats 0 as “none”. † |
rectangle opacity value | Opacity of that rectangle. † |
useOverlayTint STRING | Lua expression that switches the overlay tint on and off. † |
overlayTint number number number | Overlay tint, as three components. † |
Text (text record) | |
text STRING | Text, translated while the file is parsed by calling the global Lua function getUiString (2.7.3.0) or t (2.6.6.0) with the string. That function must already exist. The untranslated string is kept so the menus can be translated again when the language changes. |
text number | Text given as a string reference into dialog.tlk. |
text lua STRING | Lua expression that returns the text. |
text font STRING | Font resource. † |
text point number | Font size in points. † |
text useFontZoom number | Whether the font-zoom option applies. † |
text color color | Text colour. |
text upper / text lower | Upper-case or lower-case text. † |
text shadow value | Text shadow. † |
text highlight value | Text highlight. † |
text style STRING | Copies a style from the global Lua table styles[STRING], read while the file is parsed. The fields color (a key into fontcolors), font, point, useFontZoom, halign and valign are always read, so a style should define all six. halign and valign are strings: "left"/"top" = 0, "right"/"bottom" = 1, any other string = 2 (case-insensitive). If upper is present at all (even false), upper case is switched on. If pad is present, its entries 1 to 4 are read as the four padding values. The style overwrites earlier text, alignment and padding settings; later properties can override it. |
Images (bam, mosaic, bmp records) | |
bam value | BAM resource name. |
frame value | BAM frame. † |
sequence value | BAM sequence (cycle), animated. † |
sequenceonce value | BAM sequence played once. † |
scaleToClip | Scales the BAM to the item area. † |
greyscale value | Draws the BAM in greyscale. † |
usealpha valuetransparent value | Alpha blending for the BAM. transparent is the same keyword as usealpha; see the note below. |
mosaic value | MOS resource name. |
respectClipping | The mosaic respects clipping. † |
bitmap value | Bitmap resource name. † |
bitmap res STRING | Lua expression that gives the bitmap resource. † |
bitmap area x y w h | Source area within the bitmap. † |
Tooltip (tooltip record) | |
tooltip value | Tooltip text. |
tooltip force value | Forces the tooltip to show. † |
tooltip force top | Sets the separate “force top” flag. † |
tooltip position number | Tooltip position. † |
| Actions (all take a Lua string; see How a menu becomes Lua) | |
action STRING | Statement. The main action, e.g. a left click on a button. † |
actionalt STRING | Statement. Alternative action (right mouse button). † |
actiondbl STRING | Statement. Double click. † |
actiondrag STRING | Expression. Dragging †. Unlike the other actions, this one is compiled as an expression, so its return value is available to the engine. |
actionenter STRING | Statement. The mouse enters the item. † |
actionexit STRING | Statement. The mouse leaves the item. † |
actionUpdate STRING | Statement. Update while the item is captured. † |
actionsimpledrag STRINGactionsimpledrop STRING | Statement. Start and end of a simple drag-and-drop. † |
actionHold STRING | Statement. The mouse button is held down. † |
actionScroll STRING | Statement. Mouse-wheel scrolling over the item. † |
Lists (list record) | |
table STRING | Lua expression that returns the table of rows. The engine walks this table with next, so the row order is Lua's table order. While each row is processed, the global rowNumber holds the row's key and rowSelected holds the list's selected row. |
rowheight numberrowheight dynamic | Fixed row height, or rows sized to their content. † |
rowwidth number | Row width. † |
rowbackground value | Row background. † |
highlight color color | Colour of the highlighted row. † |
hidehighlight | Hides the row highlight. The highlight is shown by default. |
seperator STRING | Lua expression for row separators (the misspelling is the engine's). † |
column { width value … items } | A column of the list (see Grammar). |
Scroll bar (scrollbar record) | |
scrollbar STRING | BAM resource for the scroll bar. † |
scrollbar func STRING | Name of a scroll function (stored as text). † |
scrollbar hide value | Hides the scroll bar. † |
scrollbar respectConstraints | Flag. † |
scrollbar clunkyScroll number | Scroll step. † |
scrollbar skipReset | Keeps the scroll position when the content is reset. † |
Edit fields (edit record) | |
var STRING | Name of the global Lua variable that holds the text (Infinity_EnterEdit assigns this variable). |
maxlines number | Maximum number of lines. † |
maxchars number | Maximum number of characters. † |
placeholder STRING | Placeholder text, translated while the file is parsed, like text STRING. |
Buttons (button record) | |
clickable value | Whether the button can be clicked. This is the value Infinity_IsItemEnabled reports. |
toggle STRING | Name of a Lua variable for a toggle button (stored as text). † |
on STRING | Value of the toggle variable that means “on” (stored as text). † |
sound STRING | Click sound resource. † |
highlightgroup STRING | Highlight group name. When the item is created and no global Lua variable of that name exists yet, the engine creates one that holds the item (as light userdata), so the global refers to the group's first item. |
portrait value | Portrait. † |
paperdoll value | Paper doll. † |
encumbrance | Encumbrance display. † |
colordisplay number | Colour display. † |
frameTimes | Flag. † |
actionbar value | Action bar button. † |
Sliders (slider record) | |
position STRING | Name of the Lua variable that holds the slider position (stored as text). † |
settings value | Number of slider settings. † |
sliderBackground STRING | Background resource. † |
palette number | Palette. † |
Movies (movie record) | |
loop | Loops the movie (used by Infinity_PlayMovie). |
queuedMovie value | Next movie to play. † |
subtitle area x y w h | Subtitle area. † |
subtitle background number number number number | Subtitle background colour, packed like a four-number color. † |
Slots (slot record) | |
icon value | Icon. † |
count value | Stack count. † |
usages value | Charges. † |
highlight value | Slot highlight. † |
tint value | Slot tint. † |
slotinfo STRING | Shorthand. With T as the string, it generates:icon lua "T.item.icon", count lua "T.item.count", usages lua "T.item.usages", highlight lua "T.highlight", tint lua "T.item.tint", tooltip lua "T.tooltip", enabled "T.valid", action = expression Infinity_SwapSlot(T.id), actionalt = statement showItemDescriptionInventory(T.slotName), actiondbl = statement slotDoubleClick(T.slotName). |
spellInfo STRING | Shorthand. With T as the string, it generates:enabled "T ~= nil", icon lua "T.icon", tooltip lua "Infinity_FetchString(T.name)", greyscale lua "T.castable == 0", bam lua "'IVSLOT'", action = expression memorizeSpell(T), actionalt = statement showSpellDetails(T, false). |
memorizedSpellInfo STRING | Same as spellInfo, except that action = expression unmemorizeSpell(T) and actionalt = statement showSpellDetails(T, true). |
| Other | |
progressbar value | Progress-bar percentage. † |
progressbar color colorprogressbar full color | Bar colour, and the bar colour when full. † |
areamap / worldmap | Map type: 1 = area map, 2 = world map. |
Note on
transparent. The engine's grammar has a rule transparent number that would set a movie's transparency, but the keyword table maps the word transparent to the same token as usealpha. That rule can never be reached: transparent n sets the BAM alpha flag, and a movie's transparency (read by Infinity_PlayMovie) cannot be set from a MENU file.
Each property that takes Lua code is compiled while the file is parsed, by running a generated chunk that defines a global function. A reference to that function is then stored in the item. The global name is a fixed prefix followed by a memory address in hexadecimal (the C
The generated functions take no arguments. The code text is pasted in unchanged, so a Lua syntax error in it just leaves the function undefined; the error is not reported. Code in a property string is only compiled at load time; it runs later, so it can call functions that are defined further down in the file. In contrast, the following are evaluated during parsing and must exist before the menu that uses them: the translation function used by
The parser also creates these globals:
%p format). It is unique within one run, but it is not stable between runs, so never refer to these globals by name. Two chunk shapes are used:function <prefix><address> () <code> end -- statement form function <prefix><address> () return <code> end -- expression form
| Form | Properties (prefix of the generated global in parentheses) |
|---|---|
| Statement | onopen (onopen), onclose (onclose), action (action), actionalt (actionalt), actiondbl (actiondbl), actionenter (actionenter), actionexit (actionexit), actionUpdate (actionupdate), actionsimpledrag (actionsimpledrag), actionsimpledrop (actionsimpledrop), actionHold (actionhold), actionScroll (actionscroll) |
| Expression | menu and item enabled (enabled), actiondrag (actiondrag), table (table), bitmap res (resname), seperator (seperator), useOverlayTint (useOverlayTint), every lua STRING value (variant), and the values generated by slotinfo/spellInfo (variant_icon, variant_count, variant_usages, variant_highlight, variant_tint, variant_tooltip, variant_greyscale, variant_bam, valid, enabled, action) |
The generated functions take no arguments. The code text is pasted in unchanged, so a Lua syntax error in it just leaves the function undefined; the error is not reported. Code in a property string is only compiled at load time; it runs later, so it can call functions that are defined further down in the file. In contrast, the following are evaluated during parsing and must exist before the menu that uses them: the translation function used by
text STRING and placeholder, the styles table used by text style, and the fontcolors table used by color values. The usual way to provide them is an inline `…` block earlier in the file.The parser also creates these globals:
- One empty table per
listitem, namedlistfollowed by an address. - One global per new
highlightgroupname (see Buttons above). - After the whole file has been parsed,
nameToItem = {}is rebuilt. Then, for every top-level item with a name (menus in their sorted order, items in file order), the engine runsnameToItem['name'] = nameToItemPointer, wherenameToItemPointerholds the item as light userdata. A later item with the same name replaces an earlier one. A name that contains'breaks the generated statement and is skipped.
| Limit | Behaviour |
|---|---|
| Menus: 256 | Size of the engine's menu array. The parser does not check it, so exceeding it overwrites the menu stack that follows the array in memory. |
| Menu stack: 256 entries | Size of the stack of open menus. |
| Word length: 63 characters | A longer unquoted word aborts the game. Quoted strings are not limited. |
| Parser stack: 100 entries | On overflow the parser empties its stack. Ordinary nesting stays far below this limit. |
Keywords, grammar, parser tables and the property-to-field mapping are identical in all six examined builds (BGEE, BG2EE and IWDEE at 2.6.6.0 and 2.7.3.0). The only difference in the parser is this:
| Behaviour | 2.6.6.0 | 2.7.3.0 |
|---|---|---|
Translation function called for text STRING and placeholder STRING | t | getUiString |
`
-- inline Lua: runs when the lexer reaches it (note the line break before the closing backtick)
myMenuTitle = "Hello"
`
menu
{
name "MY_MENU" -- quoted: an unquoted name cannot contain '_'
align center center
ignoreEsc
onopen "Infinity_Log('opened')"
label
{
area 0 0 200 40
text lua "myMenuTitle" -- evaluated at run time
text align center center
}
button
{
name "MY_MENU_CLOSE"
area 0 50 200 40
bam "MYBUTTON" -- a BAM resource of your own
enabled "true"
action "Infinity_PopMenu()"
}
}