Jump to content

In-depth Look at the CFG Syntax


trigger_segfault
 Share

Recommended Posts

trigger_segfault

I'm looking through the LegoRR.exe assembly for how exactly it reads Lego.cfg, and other cfg-like files (.ol Object Lists, .ptl Mission AI, etc.). The basic syntax already makes sense, but knowing the boundaries helps a lot with weird edge cases. There's already been some fun finds.

 

Base CFG Syntax:

 

Back to Extended Syntax Basics

 

  1. The most interesting aspect is that there seems to be almost no concept of lines. All whitespace characters (tabs, spaces, line feeds, carriage returns, and ';' comments) are treated the same, they all separate tokens (i.e. keys, values, block names, and block braces {}). There is one exception, and that is the newline character '\n', which is required to end ';' line comments.
  2. All properties (whether the beginning of a property block {}, or a property key value pair) consume exactly 2 tokens from the config file. A property block's "value" is actually the '{' character, while the '}' character is a special case that's consumed but not assigned anywhere. Because every property consumes 2 tokens, missing a property value/key/etc., or adding one too many can throw off the entire file.
  3. Double-slash `//` comments are NOT supported... yet they still appear in Lego.cfg if you search for `// ROCK FOG`. And look! This creates 3 tokens which would throw off the file... except it doesn't, because they appear in pairs every time when defining `FogColourRGB` and `HighFogColourRGB`. This essentially removes the `HighFogColourRGB` property, because the parser ends up treating it as a property value and not a key like so:
    { FogColourRGB = 110:110:155, // = ROCK, FOG = HighFogColourRGB, 155:155:110 = //, ROCKFOG }

 

Special Characters

 

';' - Comments, you see them everywhere. Not much more to add to this, other than the fact that these do not need to be separated by real whitespace. If a value has a ';' in it, it will immediately end (although this is never done in Lego.cfg).  As an example, this would be 100% valid: `CreditsTextFile  Credits.txt;comment`.

 

'{' '}' - On the other hand, block open/close braces must be separated by whitespace, otherwise they will be considered part of the block name, property key, or property value. e.g. `BlockName{` will not count as an opening brace. A token can start with, end with, or have '{' '}' characters anywhere in the middle, as long as the token is at least 2 characters long, it will be treated like a name, and not as a block brace.

 

'*' - The asterisk is used as a wildcard in block/property key names (at the root level only), and is used as a method for defining base information that can easily be overridden. Most notably, this is seen with the root `Lego* {}` block name. If you look at the bottom of Lego.cfg, you'll see the `LegoRR {}` block "; Settings for the final version", Rock Raiders uses the executable name (LegoRR.exe) -> `LegoRR::Block::Path::To::PropertyKey` when looking up configuration values. If you want to change the executable name, make sure the name starts with Lego, and then you can define custom config settings depending in the full name of the exe when launched. Which brings us to the next character(s):

"::" - You'll see this syntax with Levels::LevelName, and Textures::Rock|Ice|Lava,. Internally, Lego Rock Raiders uses this to lookup any property or block key. It uses the same syntax as C++'s namespaces. I'm not quite sure how the usage of "::" is done with property keys, as is seen with `Stream_Objective_Levels::Level01  @Sounds\Streamed\Objectiv\MisObj01` and such, it's possible the handling for this is hardcoded.

 

You cannot use double-quotes to include spaces in key names or values. Spaces can only be used for certain non-file-path text by inserting '_' underscore characters.

 

 

Common Lego.cfg Syntaxes:

 

Common Keywords and Literals

 

Keywords:  TRUEFALSEYESNOONOFFNULL

Integer:  25-100+32
Float:  0.1-2.5+1.060.0f

RGB:  255:0:13

(the `#.#f` float syntax is only seen with the 2 `PolyRange` properties. This is, again, a C/C++ syntax)

 

Common Value Delimiters

 

Very often, a single property key will require multiple points of information to fill in its value. There are 3 common characters used to separate this information, however these are only used for certain properties. Many will use multiple delimiters to signal different things. And many blocks will have comments stating how to format said property value.

 

Delimiters:  ':'  ','  '|'

 

Commen Key/Value Prefixes

 

Three special prefix characters are seen on many property key and value names. These are as-always, used on a case-by-case basis:

 

'@' (values) - Presumably specifies to stream a sound property value (rather than loading it in memory all at once). This prefix is also utilized in the ToolTips section to denote an image name instead of text (however this is never actually used in Lego.cfg, only commented on).

'*' (values) - Seen on a handlful of Sounds\ path property values. Purpose is unknown.

'!' (keys) - Seen on a handfull of SFX/SND/etc. property keys. Purpose isn't fully understood, but it seems this is used to denote that a property is an "expensive" resource, that can be ignored/excluded when using an appropriate `-reduceX` command line argument.

 

Format Characters (sprintf)

 

A few property values have dynamic information that needs to be replaced at runtime. These property values expect special "%_" format character patterns. More than anything else, messing up these property values can and will crash the game (immediately upon usage). This is because a program will pass in N values to replace N format characters, any difference in number will imbalance the program's execution stack, and immediately start causing all kinds of unexpected behavior until a crash is inevitable.

 

See the printf reference for all existing format characters, however "%d" and "%%" are only ever seen in Lego.cfg. You can change up and replace "%d" with a different format specifier in-some-cases, but beware that it's a bit more complicated than just keeping the same number of format specifiers.

 

"%%" - An escape to insert in a real '%' percent sign. This does not consume one of those N values passed by the program, it is simply the only way to specify a percent sign without causing formatting to occur.

"%d" - Insert a signed integer (whole number that can be negative or positive).

 

All Format Usages:

 

Spoiler

Lego* { Menu {

 Overwrite {

  ; i.e. "You are about to overwrite Saved Game 2. All the old Saved Game information will be lost. Continue?"

  Text    You_are_about_to_overwrite_Saved_Game_%d._All_the_old_Saved_Game_information_will_be_lost._Continue?

 }

 SaveText {

  Slot     %d%%_of_game_completed.            ; i.e. "95% of game completed."

  SaveSel  Save_Game_%d_selected._Saving...   ; i.e. "Save Game 2 selected. Saving..."

  LoadSel  Saved_Game_%d_selected._Loading... ; i.e. "Saved Game 2 selected. Loading..."

 }

 

 

Making Use of this Information:

 

Syntax Highlighting for VSCode (WIP)

 

This syntax highlighting is based on information that was only known before going into the assembly, a lot of the edge cases listed above aren't added in yet, but your average cfg file should be as readable as ever.

 

The extension development is available on GitHub, (along with the rest of this research). It's not in any finished state, or on the VSCode Marketplace, but it can be installed by dropping the containing folder, vscode-lrr, into `C:\Users\<YOU>\.vscode\extensions\`.

 

Spoiler

 

397OLs6.png

 

 

 

Fixing the ROCK FOG Comments

 

Comparison of changing `HighFogColourRGB` to `255:0:0` without removing the `// ROCK FOG` comments, and with removing them:

 

Spoiler

Bon6UA8.png 

 

Spoiler

bw7MpXE.png

 

Changing the "LegoRR" Root Block Name

 

As explained in the section describing '*' block name wildcard characters, the root namespace at the bottom of Lego.cfg is looked up based on the name of the game executable. When put into practice, here's an example of 3 game variations added to a single Lego.cfg file (ignore the fact that I created duplicate WAD files to match the exe names). The following executable-name-specific properties are defined:

LegoRR::Main::PowerCrystalRGB  255:000:000 ; RED  Energy Crystals for "LegoRR.exe"

LegoSL::Main::PowerCrystalRGB  000:255:255 ; CYAN Energy Crystals for "LegoSL.exe"

LegoSS::Main::PowerCrystalRGB  000:000:255 ; BLUE Energy Crystals for "LegoSS.exe"

 

Spoiler

0EfX3JR.png

 

Spoiler

SOTB2gG.png

 

Spoiler

JBT1sgj.png

 

Edited by trigger_segfault
Move ROCK FOG images into spoilers (table removed with help of mobile). Add explanation of how the game exe name determines the full "LegoRR" namespace at the bottom of Lego.cfg, and add section at the bottom putting this info into practice.
Link to comment
Share on other sites

aidenpons

Impressively found! I had no idea that // didn't work as a comment and instead caused the next line to break :D

In particular, I've always been curious about what the ! , @ , and * operators all do. You see the former two in sounds occasionally and the * once as a weird case. I've also always been marginally curious why indents were always used in the .cfg yet seemed to never be necessary.

 

I wonder if the // comment style is a leftover from the mission scripting with .nrn and .nrm files compiling into .npl , where // is used as a comment.

 

 

As a side note, one of the original devs said a long time ago (source somewhere buried in here) that their original .cfg editor was Notepad - which explains why you can open and edit it in Notepad so easily. :P

 

  • Thanks 1
Link to comment
Share on other sites

trigger_segfault
Quote

I've always been curious about what the ! , @ , and * operators all do.

 

These all seem to be on a case-by-case basis, though what is used is all related to sound/media properties. '@' prefixes on property values are only seen for Sounds under the Sounds\Streamed\ path, so I think it specifies to stream the sound rather than loading it in memory all at once. However, '@' is also mentioned in the comment for `ToolTips`:

; Use underscores as spaces an \n as a return character...

; Use @ to denote an image...

Which is also interestingly the only time I've seen backslash character escapes actually being mentioned.

 

As for the other two, I don't really have a solid idea. Theories with no basis behind them include: "interruptible" sounds or sound priority. Like Rock Raiders screaming and running from monsters will stop screaming the moment they're safe, and then immediately restart the sound a split-second later because "safe" is just a few pixels difference. :P

 

Quote

I wonder if the // comment style is a leftover from the mission scripting with .nrn and .nrm files compiling into .npl , where // is used as a comment.

 

This is pretty likely, but I also think someone might have thought it was supported without checking. "//" comments are also a C/C++ language syntax like "::", so it's not surprising to mix them up.

As for the indentation, it's not enforced, but it certainly makes the monolithic Lego.cfg file bearable to read. No syntax highlighting, and a much smaller monitor resolution 20 years ago must have been hell for dealing with this file. xD  The indentation and spacing help make things easier to skim and read at a glance.

 

Quote

(source somewhere buried in here)

 

Oh wow! Seeing dev commentary is something I'd only dreamed of! Definitely gonna enjoy reading through all that!

Link to comment
Share on other sites

  • Community Moderators
lol username
8 hours ago, trigger_segfault said:

Double-slash `//` comments are NOT supported... yet they still appear in Lego.cfg if you search for `// ROCK FOG`. And look! This creates 3 tokens which would throw off the file... except it doesn't, because they appear in pairs every time when defining `FogColourRGB` and `HighFogColourRGB`. This essentially removes the `HighFogColourRGB` property, because the parser ends up treating it as a property value and not a key like so:
{ FogColourRGB = 110:110:155, // = ROCK, FOG = HighFogColourRGB, 155:155:110 = //, ROCKFOG }

Hah! Great find.

 

Cool stuff all around.

Link to comment
Share on other sites

  • lol username featured this topic
trigger_segfault

Finally found the usages for the key and value prefixes (at least for sounds)

 

Key Prefixes:

 

Reduce Prefix: '!'

 

This tells the game that the sound (and presumably any other resource) will be removed with `-reduce____` command-line arguments. This has been tested on sound effects using `-reducesamples`. This will eliminate a large number of tooltip and interface sounds we all known and love. :(

        !SurfaceSFX_Medium          Sounds\Voices\Surfaces\looserock

        !SurfaceSFX_Rubble          Sounds\Voices\Surfaces\rubble

Other spotted uses of this prefix:

Probably for `-reduceflics`:

        Flics {

            !Score              Avi\Capt.flh|121|153|234|327

        }

Probably for `-reduceanimation`:

        !MenuWipe                   Interface\FrontEnd\Rock_Wipe\RockWipe

 

 

Value Prefixes:

I've only researched usages of this for sound at the moment. Though again, the config file states other areas where this should be supported.

 

Multiple Instance Prefix: '*'

 

States a sound can have multiple instances playing at once. If this prefix is not present, and a new sound needs to play, it will cut off any currently playing instance of the sound and start a new one.

        SFX_Drill                   *Sounds\drtdrillc

 

Streamed Prefix: '@'

 

Sound is streamed (and it will sound a bit louder). Cannot be used with '*' to play multiple instances at once.

When this prefix is not specified. The sound file will be fully loaded in memory, supposedly on boot.

        Stream_Objective_Levels::Level01            @Sounds\Streamed\Objectiv\MisObj01

 

BUG: When defining a Sound Group (with ','), the Streamed flag will be applied to all future sounds in a group after the first-listed sound with '@'. (Note: It hasn't exactly been tested if Streaming functionality is even supported with Sound Groups)

 

Reduce Volume Prefix: '#'

 

Reduces the volume of a sound. This one follows a format: #number#

Where number should be a whole number between -10000 and 0 (anything else is set to 0). The lowest number will make it silent, while the highest (0), will keep it at the original volume. (this prefix is never used)

        !SurfaceSFX_Immovable       #-100#Sounds\Voices\Surfaces\solidrock

 

Combinations with '#'

This prefix can be combined with '*' or '@', however note that the order is important:

The Multiple Instance prefix must come before the Reduced Volume prefix:

        !SurfaceSFX_Immovable       *#-100#Sounds\Voices\Surfaces\solidrock

Meanwhile the Steamed prefix must come *after* the Reduced Volume prefix:

        !SurfaceSFX_Immovable       #-100#@Sounds\Voices\Surfaces\solidrock

 

POSSIBLE BUG (technical): When parsing the this prefix, it seems the program doesn't properly terminate the number string, which can potentially allow for garbage data to be included in the number. Worst-case-scenario is the volume would be quieter, but it entirely depends on what data was previously in this location.

 

 

Miscellaneous:

 

Sound Group: ','

 

This is used a lot in the samples Block to define multiple sound effects that can randomly be selected from a single type of sound:

        !SFX_Drip                   Sounds\drip1,Sounds\drip2,Sounds\drip3,Sounds\dripsA,Sounds\dripsB,Sounds\dripsC

 

Different Value Prefixes listed above should supposedly work with each individual sound (the prefixes are parsed individually for each sound in a group).

 

LIMITATION: The game engine only has room for 200 total extra sounds defined in groups. (1 slot is used for each sound defined after the first comma ',').

 

BUG:

When defining a second sound (turning it into a group), the first sound's information is overwritten with that of the second sound (which also makes the second sound twice-as-likely to play when more than 2 sounds are listed. You may notice a lot of first-listed sounds that you've never heard before in-game. Listen to groan1.wav or slip1.wav for good examples:

        !SND_Slipup                 Sounds\Minifigure\slip1,Sounds\Minifigure\slip2

        SND_Groan                   Sounds\Minifigure\groan1,Sounds\Minifigure\groan2

 

Link to comment
Share on other sites

  • Administrators
Cirevam

All of this is extremely useful. Thank you for digging into the game and sharing all of this. The last bit about sound groups confirms something I suspected for a long time but never figured out on my own. It seems easily remedied, but that 200 grouped sound limit makes it harder. A sound group like "slip1, slip2, slip1, slip1" would play slip1 and slip2 in equal amounts, but uses twice as many grouped sound slots.

  • Like 1
Link to comment
Share on other sites

trigger_segfault
Posted (edited)
On 6/25/2021 at 1:40 PM, Cirevam said:

All of this is extremely useful. Thank you for digging into the game and sharing all of this. The last bit about sound groups confirms something I suspected for a long time but never figured out on my own. It seems easily remedied, but that 200 grouped sound limit makes it harder. A sound group like "slip1, slip2, slip1, slip1" would play slip1 and slip2 in equal amounts, but uses twice as many grouped sound slots.

 

No problem, honestly this has been a blast. :D

 

And yup. The only non-patch solution to keep balanced randomness would be to list every sound twice in a row. Though better readability would be listing them in order "slip1, slip1, slip2, slip2, ..."
Thankfully the number of used slots in a virgin Lego.cfg file is only 34/200 (no sound groups are commented out either).

 

However, the bigger limitation seems to be the total number of definable Samples (number of properties in Samples block).

The game engine has a (now-confirmed maximum) of 495 Samples properties.

The number of properties used in a virgin Lego.cfg file is 446/495, (461/495 if you include commented-out properties, all of which are unique names). Which leaves very little room for custom sounds in mods. :(

Edited by trigger_segfault
Found confirmation for the 495-upper limit
Link to comment
Share on other sites

aidenpons

Wow, amazing finds once again!

 

On 6/25/2021 at 12:46 PM, trigger_segfault said:

BUG:

When defining a second sound (turning it into a group), the first sound's information is overwritten with that of the second sound (which also makes the second sound twice-as-likely to play when more than 2 sounds are listed. You may notice a lot of first-listed sounds that you've never heard before in-game. Listen to groan1.wav or slip1.wav for good examples:

        !SND_Slipup                 Sounds\Minifigure\slip1,Sounds\Minifigure\slip2

        SND_Groan                   Sounds\Minifigure\groan1,Sounds\Minifigure\groan2

 

 

Huh. Not only is this amusingly jank, I'm also surprised at how it means some sounds that were intended to play just never do - particularly those slipup and groan sounds (which relate to small spiders): what was supposed to be a choice of two sounds becomes only a choice of one. The scared scream is a choice of three not four. Rock monster steps, electric fence zaps, raider hup's & haa's, and some other sounds are also affected by this, but they're much less noticeable.

 

I kinda want to see if I can restore them in Improvements Pack...

 

On 6/25/2021 at 12:46 PM, trigger_segfault said:

Other spotted uses of this prefix:

Probably for `-reduceflics`:

        Flics {

            !Score              Avi\Capt.flh|121|153|234|327

        }

Probably for `-reduceanimation`:

        !MenuWipe                   Interface\FrontEnd\Rock_Wipe\RockWipe

 

This would make a lot of sense - using those various command line parameters you can stop those animations from playing (I particularly remember toying around with the rockfall due to it behaving weirdly on one of my PCs), and it makes sense that the sounds wouldn't play as well.

 

On 6/25/2021 at 12:46 PM, trigger_segfault said:

The sound file will be fully loaded in memory, supposedly on boot.

 

On boot makes a lot of sense with some of the modding crashes I've encountered. Whilst I've never poked sound modding, I noticed that some invalid model etc files were causing crashes on model load, as opposed to eg any time you actually teleport the model in. Using Community Edition you can enable the -logging parameter which tells you what the game is loading when. I can see that LRR is loading a whole bunch of sound files, like the Catamaran's splash (Rapid Rider), but not the mission briefings - which would make sense with what you've specified and how the Streamed section works.

 

 

Fascinating discoveries!

 

 

  • Like 1
Link to comment
Share on other sites

trigger_segfault
23 hours ago, aidenpons said:

I kinda want to see if I can restore them in Improvements Pack...

 

Sounds good! I've actually already gotten an assembly patch working to fix this, though I've only done tests with the Masterpiece executable, (and very few tests at that)... and it's in Python (which I need to port to some more-accessible language).

As long as the Improvement Pack's order is "sound1,sound1,sound2,sound2,...", then it shouldn't cause problems whether the user has the patch applied or not.

 

And on an unrelated note...

 


Dependencies

 

So all dependencies have an "AlwaysCheck:" prefix in Lego.cfg, it turns out there actually is another prefix that can be used:

 

"HitOnceStay:" - This is the only prefix the game actually checks for in the Dependencies block. This states that once the requirements are met. The player never needs to meet the requirements again. The only exception is for objects that need a place to teleport in. (i.e. Vehicles cannot be teleported if there's no power/or Power Station)

 

Dependencies {

 

    HitOnceStay:Hoverboard    Pilot:0,ToolStation:1,TeleportPad:0,PowerStation:0

    AlwaysCheck:SmallDigger   Pilot:0,ToolStation:1,TeleportPad:0,PowerStation:0

    ; any prefix besides "HitOnceStay" can be used in-place of "AlwaysCheck"

    ; just make sure to include the ':'

    FooBarBaz12:SmallTruck    Pilot:0,ToolStation:1,TeleportPad:0,PowerStation:0

}

 

Example

 

Using the example for HitOnceStay above, this shows that removing the LV1 ToolStation will still allow the Hover Scout to teleport in, while the Small Digger cannot.

 

Spoiler

WFfkbFd.png

 

Spoiler

LQlz1uj.png

 

 

Link to comment
Share on other sites

trigger_segfault
Posted (edited)

Progress Bar Direction:

 

The prefix seen in front of the ProgressWindow property near the top of Lego.cfg states the direction the loading bar expands in. Valid values are:

  • U p
  • R ight
  • D own
  • L eft

 

    ProgressWindow   L:142,450,353,9  ; loading to the left for a fresh new look

 

You can also leave out the prefix (and the engine checks for that), but it'll end up placing the loading text in the top left corner.

 

Spoiler

TePy8zD.png

 

 

Map File Modifiers:

 

This is the most bizarre find so far, and I can't think of any conceivable use for this, considering how strict many of the MAP file formats are.

 

A handful of Map file properties defined in individual level blocks allow a numeric modifier postfix: "Map\file\path.map:2"  (valid range is -128 to 127)

When reading the respective map file, all block values (as can be checked in the RRU knowledge base), will have this modifier number subtracted from them. Positive numbers (which are subtracted) seem to be less useful, considering many maps will always contain block values with 0, forcing the value into an invalid range.

 

MAP types with modifiers:

  • PredugMap
  • TerrainMap
  • CryoreMap
  • PathMap
  • BlockPointersMap

 

In practice (many crashes were had in finding good fits):

 

    PathMap  Levels\GameLevels\Level23\Path_23.map:1   ; change the starting power paths into Rubble

 

Spoiler

PathMap -1 (Rouble Trouble)
RRfDgo0.png

Undiscovered Caverns cannot take advantage of the PathMap (as already noted in the RRU Knowledge Base)
zVHz7eW.png

 

    TerrainMap   Levels\GameLevels\Level23\Surf_23.map:-1   ; Reduce the hardness level of all walls? (and other effects)

 

Spoiler

TerrainMap +1 (Driller Night!)
5O0RNLf.png

TerrainMap -1 (Rouble Trouble)

What have I done!???

C3AUvY6.png

 

    CryoreMap   Levels\GameLevels\Level23\Cror_23.map:-1  ; every block without spawns now produces 1 energy crystal (also ore and energy crystal spawns are swapped)

 

Spoiler

CryoreMap -9 (Driller Night!)

xJfdLJH.png

 

    PredugMap    Levels\GameLevels\Level23\Dugg_23.map:-2  ; change undiscovered caverns into undiscovered Slimy Slug holes (this could break everything because I recall seeing a very low hard limit on number of holes)
 

Spoiler

PredugMap -2 (Driller Night!)

BpFRDys.png


Well... this is awkward...

EQavyab.png

.

Edited by trigger_segfault
Testing is finished, example images of MAP file modifiers and ProgressWindow added.
Link to comment
Share on other sites

aidenpons
Posted (edited)
On 7/4/2021 at 8:59 PM, trigger_segfault said:

A handful of Map file properties defined in individual level blocks allow a numeric modifier postfix: "Map\file\path.map:2"  (valid range is -128 to 127)

When reading the respective map file, all block values (as can be checked in the RRU knowledge base), will have this modifier number subtracted from them.

 

what

 

why is this a thing

 

... and what can I do with it???

 

I love the pictures, they really illustrate the point. It's bizarre that, of all the maps I'd expect to be in there, High.map (also called the Surface map, confusingly) is not there - modifying the heightmap up and down by a bit would make perfect sense (if being mostly useless). Instead... it's BlockPointersMap, which is solely used for combining with the NERPs scripting to eg say "check for Small Digger running over this tile and make monsters emerge at that tile). I can only guess that something involving CryoreMap was possibly a cheatcode?

 

Usability-wise, there's functionally nothing you get out of this that you wouldn't by just, well, putting it in the map in the first place xD want five crystals everywhere? Just put those in the map to begin with! So the question of why it exists is even more bizarre. Maybe it has something to do with the unused .blx files hanging around in the Levels directory?

 

 

Also, of all the things to be hardcoded, from some menu positions to the pathfinding to things like Barriers, I was not expecting the direction the loading bar loads to be customizable. What the...

 

 

 

Fascinating finds, I'm still shocked.

Edited by aidenpons
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.