Do Not Click On The Honey Pot !

Menu Patching : allowing different Mods to mod the same menu

Everything about Modding the game, from new courts to the modifications of the player & tournament bases (and including all the bugs they may be creating ! ;) )
Forum rules
Speak English in 1st page of topics, and don't hijack topics, troll, use profanities nor insults

Menu Patching : allowing different Mods to mod the same menu

Postby manutoo » 14 Oct 2026, 11:06

TE4 menu patches: a guide for modders

A menu patch changes selected parts of a menu instead of replacing the entire file. Several mods can patch the same file: each patch sees the changes made by the preceding mods, in the game's normal mod order.

You find a recognizable piece of text, move to the right place, then add, replace or delete something. You do not need line numbers.

Note : the Modding SDK now contains "Menu/Scripts/AGENTS.md" to feed your AI agent, both to Mod a menu as a whole (ie: full API description) and how to apply patches.

1. Creating a patch file

Put the patch in MySuperMod\Menu\Scripts or MySuperMod\Menu\ScriptsTe, matching the target script's directory. Append .mod after the complete filename, including its original extension; for example:

Code: Select all
MainTe.menu  ->  MainTe.menu.mod
Menu.def     ->  Menu.def.mod


The patch contains patch commands, not a complete replacement menu. The text inside an Add or replacement block contains ordinary TE4 menu commands.

For captions, use Text followed by a language code: Back, Ok, On, or a code defined in your mod's language files. SomeTextCode below is a placeholder; replace it with the actual code from your menu. The Default font is implicit, so the examples only specify Font when selecting a different style.

2. The basic idea

The patch has an invisible cursor: the position where it searches or inserts text. It starts at the beginning of the target file.

  • After finds text and places the cursor after it.
  • Before finds text and places the cursor before it.
  • Add ... EndAdd inserts a block at the cursor.
  • Replace ... With ... EndReplace replaces a piece of text.
  • Delete removes text, lines or items.
  • GoTo moves to a file boundary, an item boundary or a script label.

Searches ignore capitalization, but otherwise match literal text. Spaces between words, punctuation and quotation marks matter. The first match is used. There are no wildcards or regular expressions.

Preprocessing: menus and patches undergo the same comment removal before patching. Comments, blank lines and leading indentation are removed, including inside text blocks. Write neatly indented examples for readability, but do not use blank lines or comments as search anchors. Text blocks are literal after this preprocessing; command-looking lines inside them are not executed as patch commands.

Comments in a patch start with // , or use : /* some comment */ .

3. Three first examples

The following examples use this small, fictional Example.menu. Its names are for the tutorial; substitute anchors from the real file you want to modify.

Code: Select all
item ExampleTitle
{
    Text SomeTextCode
    Pos 640 120 Center Center
}

item ExampleBack
{
    Text Back
    Pos 640 620 Center Center
    DefaultBack
}


Each example below can be tried independently against this starting file. The patch filename is Example.menu.mod. Each independent patch starts with its cursor at the beginning, so these examples do not need FromStart. When combining several changes into one patch, searches continue from the cursor left by previous commands. Use FromStart only when the anchor you need may be before that position; it restarts the search at the beginning of the chosen scope.

Example 1: add content at the end of a file

Use GoTo End to move to the end of the file, then Add to append the new content:

Code: Select all
GoTo End

Add
item MyModFooter
{
    Text Ok
    Pos 640 680 Center Center
}
EndAdd


This appends a new text item without needing an anchor or knowing how the original file ends. If its last line has no line break, the next Add supplies one before the inserted content

Example 2: add an item after another item

Code: Select all
After Item
item ExampleTitle

Add
item MyModSubtitle
{
    Text On
    Pos 640 160 Center Center
}
EndAdd


After finds the title's declaration. Item tells it to move past that item's complete closing-brace line, including its line break when present. Add then inserts the subtitle between ExampleTitle and ExampleBack.

This works regardless of how many commands or actions the title contains. Its name is the anchor, so you do not need to count its lines.

The insertion changes the order in the file. The item's Pos command determines where it appears on screen.

This also works after the very last item. If its closing-brace line has no final line break, the next Add supplies one automatically. After Item alone does not change the file.

Insert before or after an item using text located before it

Suppose the global line def ExampleAnchor 1 appears before the target item. After the search, the following item is current. You can insert after it directly:

Code: Select all
After
def ExampleAnchor 1

Add AfterCurrentItem
item MyModAfter
{
    Text Ok
}
EndAdd


Or insert before that same item with this independent patch:

Code: Select all
After
def ExampleAnchor 1

Add BeforeCurrentItem
item MyModBefore
{
    Text On
}
EndAdd


These options also work from inside the item, including inside its action blocks. After Item requires its matched text to belong to an item; Add AfterCurrentItem uses the current-item convention and therefore also works after a global search anchor.

A real example: an inactive Ok button on the main menu

Save this as MainTe.menu.mod under your mod's Menu\Scripts or Menu\ScriptsTe, matching the main menu being loaded:

Code: Select all
After Item
Text TutoDetail

Add
add   x   ButtonW
item MyMod1stTry
{
   Image MenuBB/ButtonBig2 ButtonW ButtonH
   Text Ok
   Pos x y
   Font BgF
}
EndAdd


This adds an inactive Ok button at the bottom right of the main menu. After Item finds the item displaying TutoDetail and moves after its closing-brace line. The inserted add x ButtonW advances the layout by one button width. Font BgF selects the existing button style. The button has no OnAction, so it performs no action.

Example 3: add a command inside an item

Code: Select all
After
item ExampleTitle

GoTo End InCurrentItem

Add
    Color 0xFFFFD060
EndAdd


After finds the item. GoTo End InCurrentItem moves to the beginning of its closing-brace line. Add inserts the color command inside the item, after its existing commands and before its closing brace.

Use GoTo Start InCurrentItem instead to insert immediately after the opening-brace line.

4. A few more useful recipes

Change one item's text

Code: Select all
After
item ExampleTitle

Replace InCurrentItem
Text SomeTextCode
With
Text Ok
EndReplace


InCurrentItem restricts the replacement to this item, starting at the cursor just after its declaration. It cannot accidentally replace the same text in another item. Add FromStart only if you also need to search an earlier part of the current item.

Remove a complete item

Code: Select all
After
item ExampleBack

Delete CurrentItem


After moves just past the matched text, item ExampleBack, before its opening brace. ExampleBack is therefore still the current item. Delete CurrentItem removes its declaration, braces and contents, including nested action blocks.

To remove the following item instead, use Delete NextItem. After Item would first move beyond ExampleBack's closing-brace line, making the following item current, if one exists.

Skip an optional change when an anchor is missing

Code: Select all
After OnFail=Done
item ExampleTitle

GoTo End InCurrentItem

Add
    Color 0xFFFFD060
EndAdd

:Done
Dump FinalFile


If the title exists, the patch changes it. If the search finds nothing, execution jumps forward to :Done. In both cases, Dump FinalFile requests the final result for inspection.

5. Complete command reference

In the syntax below, angle brackets mean a required value and square brackets mean an optional argument. Do not type those brackets. Optional counts are positive integers and default to 1. Commands, options, searches and label names are case-insensitive. Options on search commands can be supplied in any order; do not repeat an option.

Search and positioning

Code: Select all
After [FromStart] [InCurrentItem] [Item] [OnFail=<LabelName>]
<search text>

Before [FromStart] [InCurrentItem] [OnFail=<LabelName>]
<search text>


The search text occupies the very next line. Do not put a comment or another instruction between the command and its search text. Do not add quotes around the search unless those quotes are part of the text you want to find. Backslashes have no special escape meaning.

  • After: place the cursor immediately after the match.
  • Before: place it immediately before the match.
  • After ... Item: place it after the closing-brace line of the item containing the match. The match must belong to an item. If this is the end of the file and the closing-brace line has no line break, the next Add supplies one. Item is available only on After.

The same search options are available on text Delete and Replace:

  • No scope option: search from the cursor to the end of the file.
  • FromStart: search from the beginning of the file.
  • InCurrentItem: search from the cursor to the end of the current item. If the cursor is in a gap before that item, start at its declaration.
  • FromStart InCurrentItem: search from the current item's declaration to its end.

Item searches include the declaration and structural braces, but cannot extend outside the item. Matches must fit within a single line. Searches never wrap around, and empty searches are errors. A failed search leaves the document and cursor unchanged.

What is the current item?

It is the first item whose closing brace ends strictly after the cursor. If the cursor is inside an item, that item is current. In a gap between items, the following item is current. Immediately after an item's closing brace, the following item is current. After the last item there is no current item.

Item-aware commands recognize complete item blocks, including nested actions. Structural opening and closing braces must be alone on their lines, apart from whitespace. Inline braces in menu expressions are not structural braces. Items may be unnamed or share names; text searches still stop at the first match, so prefer distinctive anchors.

Text navigation

Code: Select all
GoTo Start [InCurrentItem]
GoTo End [InCurrentItem]

GoTo NextItem [NumberOfItems]
GoTo PreviousItem [NumberOfItems]


  • GoTo Start / End: move to the beginning / end of the file. GoTo End also prepares the next Add to supply a missing final line break if it appends at the end; navigation itself does not edit the file.
  • GoTo Start InCurrentItem: move to the first content line after the item's opening-brace line.
  • GoTo End InCurrentItem: move to the beginning of its closing-brace line, ready to insert inside it.
  • GoTo NextItem / PreviousItem: move to the declaration of the next / previous item relative to the current item. A count moves that many items.

Relative navigation must reach a different item. In a gap, the following item is already current, so GoTo NextItem skips to the one after it. A missing current item or destination is an error.

Insertion

Code: Select all
Add [AfterCurrentItem|BeforeCurrentItem]
<one or more content lines>
EndAdd


Insert the block exactly at the cursor, then move the cursor after the inserted text. Consecutive Add operations therefore keep their script order. Add includes a line break after its last content line, using the target's line-ending convention; it normally does not add one before the first line. After GoTo End or After ... Item, the next Add supplies a leading line break only if it appends to a nonempty file whose last line has none. This does not apply to GoTo End InCurrentItem.

With no option, Add inserts at the cursor. Add AfterCurrentItem inserts after the current item's closing-brace line; Add BeforeCurrentItem inserts at the beginning of its declaration line. The cursor may be in the gap before that item or inside it. Both forms leave the cursor after the inserted text. Add AfterCurrentItem supplies a missing line break at the file's end. If there is no current item or its structure is invalid, the command fails without moving the cursor or modifying the file.

Choose at most one positioning option. These forms accept no count, search options or OnFail. A qualified Add resolves the current item again each time; to continue directly after inserted content, use ordinary Add. In the signature above, | means alternatives and is not typed.

EndAdd must be alone on its line, ignoring surrounding whitespace and capitalization. It is not inserted. Other lines are content, even if they look like patch commands. Remember that comment removal happens before block parsing. An empty Add block is an error.

Replacement

Code: Select all
Replace [FromStart] [InCurrentItem] [OnFail=<LabelName>]
<search text>
With
<replacement content>
EndReplace


Replace only the first matching text, keeping all surrounding characters. The replacement can have several lines, or be empty to remove the match. With must immediately follow the search line. With and EndReplace must be alone on their lines, ignoring surrounding whitespace and capitalization.

The replacement does not add an implicit final line break. The existing text after the match, including any original line ending, is retained. The cursor ends immediately after the replacement. On search failure, neither text nor cursor changes.

Delete matching text

Code: Select all
Delete [FromStart] [InCurrentItem] [OnFail=<LabelName>]
<search text>


Delete only the matching characters, keeping the rest of the line and its line ending. The cursor moves to the position where the match began. To remove a whole line or item, use the forms below.

Delete complete lines

Code: Select all
Delete PreviousLine [NumberOfLines]
Delete CurrentLine [NumberOfLines]
Delete NextLine [NumberOfLines]


  • PreviousLine: remove lines before the line containing the cursor.
  • CurrentLine: remove that line and, for a count greater than 1, the following lines.
  • NextLine: remove lines after the line containing the cursor.

At the beginning of a line, that line is current. PreviousLine and NextLine do not delete the current line. Whole lines include their line endings; a real final line without a line ending also counts. There is no extra empty line after a final line break.

Delete complete items

Code: Select all
Delete PreviousItem [NumberOfItems]
Delete CurrentItem [NumberOfItems]
Delete NextItem [NumberOfItems]


These work relative to the current item. CurrentItem removes it and the following items when the count exceeds 1. PreviousItem and NextItem exclude the current item. Each removed item includes its declaration, braces, contents and closing-line ending when present. Global commands between removed items are retained.

All relative deletions require the full requested count. If too few lines or items exist, nothing is partially deleted. The cursor keeps its logical position, adjusted for content removed before it; if its position is deleted, it moves to the beginning of that deleted range.

Labels and conditional execution

Code: Select all
:LabelName

GoTo Label LabelName


A label names a position in the patch script. GoTo Label changes which instruction runs next, without moving the text cursor. Names must be nonempty, contain no whitespace and be unique within the patch. Jumps are forward only; loops and backward jumps are errors.

Code: Select all
After OnFail=Missing
item ExampleTitle

// Instructions for the case where the item exists go here.
GoTo Label Done

:Missing
// Instructions for the missing-anchor case go here.

:Done


OnFail=LabelName is available on After, Before, text Delete and Replace. When their search finds no match, it jumps to that forward label without changing text or cursor and without triggering the global error policy.

OnFail handles a missing text match. Invalid syntax, a missing current item, an invalid item structure or an invalid jump still triggers the global error policy. Relative Delete forms do not accept OnFail.

Error handling

Code: Select all
OnError Rollback
OnError Keep


Every patch starts with Rollback. The selected policy applies to subsequent unhandled errors:

  • Rollback: stop this patch and restore the text as it was before this patch began. Changes from earlier patches remain.
  • Keep: keep successful changes, log the error and continue with the next instruction. A failed edit does not leave a partial deletion or replacement.

Errors include missing anchors without OnFail, unavailable items or lines, unknown commands, invalid arguments or labels, backward jumps and missing block delimiters. Parsing and execution happen as the script is read: an error later in the script can occur after earlier edits have succeeded. A missing EndAdd or EndReplace can consume the rest of the script, leaving no next instruction for Keep to continue with.

Errors are logged with the target, mod, patch, command and reason. Reported script line numbers refer to the processed patch, after comments and blank lines have been removed. An error in one patch does not prevent later mods from being attempted.

Debug dumps

Code: Select all
Dump CurrentFile
Dump FinalFile
Dump CurrentFile FinalFile


  • CurrentFile: save the patched text at this exact point. The snapshot remains available even if this patch later rolls back.
  • FinalFile: request the final text after all applicable patches and mods have finished. Multiple requests for the same target produce only one final dump, identified by the first requester. A request remains pending even if its patch later rolls back.

Dumps are saved in Settings -> Profile -> Show Data Folder -> MenuPatchDumps folder. Names contain the target, patch, snapshot type, a session sequence number and the mod name. Existing files with the same names are overwritten; dumps no longer receive timestamps or a new name just because a file already exists. Dump does not move the cursor or change the menu. Dump requests are ignored in console mode. A dump-writing failure is logged separately.

6. Making patches easier to combine

  • Prefer named items and distinctive text over relative line counts. Searching for item ExampleTitle also matches a longer name beginning with ExampleTitle, so check that your anchor is unique.
  • Use InCurrentItem when changing an item's contents.
  • Make the smallest change needed. Replacing a whole item can erase another mod's additions to it.
  • Use OnFail for expected differences between menu versions. Keep Rollback for changes that should succeed together; use Keep when independent later changes should still be attempted.
  • Inspect a FinalFile dump and test the result in the game. A successful patch means the text operations worked; the inserted TE4 menu commands must also be valid.

Patches can cooperate as long as their anchors and required item structure remain available. They cannot guarantee compatibility when another mod removes or replaces the exact content they depend on.
ManuTOO
== Mana Games ==

>> I don't answer Private Message, except if it's really a _Private_ topic <<
User avatar
manutoo
Game Author
 
Messages: 21447
Gaming Since: 24 Jan 2004, 15:38
Location: France

Return to Modding - TE4

Who is online

Users browsing this forum: ClaudeBot and 7 guests