Skip to content

Another preprocessor, this time for an overengineered version of INI format: includes, variables, templates, expressions and so on

License

Notifications You must be signed in to change notification settings

ac-custom-shaders-patch/inipp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

INIpp

License Release

INIpp is an extension for the good old INI format, designed specially for Custom Shaders Patch for Assetto Corsa, in an attempt to make preparing configs easier. A change there, a tweak here, and it became incompatible with regular parsers, and then things only got worse, to a point where my initial idea of making specialized parsers for Python and JavaScript became a bit unrealistic. Maybe later I’ll get back to it, once format is established, but for now, here is some sort of preprocessor, which is basically just a piece of code from CSP parsing those configs.

In its main mode, INIpp preprocessor prints data in JSON format, since not all data could be represented with INI (look at “Quotes for specific values” section). There is a flag to change it behavior.

Important note

Please note, if you’re making configs for CSP, you don’t need to read any of this, especially everything towards the end — those features are mainly for standard includable files, designed to make creating actual configs easier. For example, this thing is made possible with INIpp, and you can use it without overthinking anything (only unusual thing here is that the same section name is used twice, but it’ll unwrap to unique names):

[INCLUDE: common/materials_carpaint.ini]
CarPaintMaterial = Carpaint

[Material_CarPaint]
Skins = ?
ClearCoatThickness = 0.1

[Material_CarPaint]
Skins = thick
ClearCoatThickness = 0.5

[Material_CarPaint_Gold]
Skins = yellow

Features

Auto-indexing sections and values

Instead of writing [SECTION_0], [SECTION_1], [SECTION_2], … and painfully keeping track of used indices to avoid conflicts, just use “...” (or “…”). Parser will automatically find next free index for each section, keeping them in order. You can combine both styles too, to force specific sections to have specific indices if required. No more conflics. Similar thing will work with values as well.

Example

Before

[SECTION_...]
PROP_... = value 1
PROP_... = value 1

[SECTION_...]
PROP_... = value 1

After

[SECTION_0]
PROP_0 = value 1
PROP_1 = value 1

[SECTION_1]
PROP_0 = value 1

Setting values to several sections at once

Quite often, different sections need to contain the same values. With INIpp, to save time and make things easier, you can move such values in a common section: list names of sections in square brackets separated by comma, and all values will be assigned to all the sections in the list.

Example

Before

[SECTION_0]
KEY = 0

[SECTION_1]
KEY = 1

[SECTION_0, SECTION_1]
KEY_SHARED = VALUE

After

[SECTION_0]
KEY = 0
KEY_SHARED = VALUE

[SECTION_1]
KEY = 1
KEY_SHARED = VALUE

Quotes for specific values

With regular INI-files (at least the way they work in Assetto Corsa), it’s impossible to have a multi-line value, or a value with symbols like “[”, “]”, “;”, “,” or “//”. With INIpp, you can wrap values in quotes, either “"” or “'”. Character escaping also works.

Example
[SECTION]
KEY_0 = "value, with; \"all\" [sorts] of=//symbols"
KEY_1 = \"here, quotes do nothing"
KEY_2 = as well as "here"
KEY_3 = and\, this\, is\, a\, single\, value
KEY_4 = "easy to create
multiline strings too"

Multi-line lists

Some of those filters for CSP are long, very long. Quite difficult to work with. So now you can use “\” at the end of the line and parser will treat values as if it’s a singe line. But, of course, if there are quotes around it, it’s not going to work.

Example
[GRASS_FX]
GRASS_MATERIALS = grass, grass_ext, sbancamento, grass_ext_flat, \
  gras_brd_ext, grs-brd

Including

One INI-file might reference other files with [INCLUDE] sections. Included file will be loaded in place of original [INCLUDE] section. Also, that section might contain parameters that will be passed to included file (see “Variables”).

Each file can be included only once, secondary inclusions will be ignored. However, if variables within [INCLUDE] section are different, the same file might be included more than once.

Another important note: if included file defines a new properly [SECTION] KEY = VALUE, it can be overwritten in original file after [INCLUDE] section, thus allowing to alter pretty much anything (apart from case with auto-indexing sections and keys).

Example
[INCLUDE]
INCLUDE = common/shared_file.ini

[INCLUDE]
; as a special section, you can define more than one of those, both files
; will be included
INCLUDE = common/another_shared_file.ini

; shorter syntax
[INCLUDE: common/shared_file.ini]

Variables

Variables are available in order to reduce copy-paste. They can be defined in a space of a single INI-file as defaults (all included files will inherit them), or they can be defined within a section. Or, [INCLUDE] sections might set variables for included files, which won’t affect main file.

Basic example

main.ini

[INCLUDE: extra.ini]
SomeVariable = 10

; value of “SomeVariable” won’t be available to “another_extra.ini”
[INCLUDE: another_extra.ini]

[SECTION_3]
LocalVariable = 1
KEY = $LocalVariable

extra.ini

[SECTION_1]
KEY = $SomeVariable

As you can see from this example, variables are defined just as any other values. Or, in other words, any values could be referenced as variables, as long as they were defined before usage. By default INIpp removes referenced values to keep output tidy, you can change that behavior with [@INIPP] @ERASE_REFERENCED = 0.

To use a variable, refer to its name with $ sign in front, like so: $Variable. You can also use curly brackets to make it more clear that this is a variable and nothing else: ${Variable}. If variable is missing, its mention with curly brackets will turn into nothingness, while simpler way will remain unchanged (maybe it’s not a variable at all, but instead, a value which is a bit more weird than usual).

Example with missing variables
[SECTION_1]
VALUE_0 = $MissingValue    ; will resolve into “$MissingValue”
VALUE_1 = ${MissingValue}  ; will resolve into empty value

Variables can also be lists and what not, and their mentions will be expanded into lists as well. With curly brackets, you can also use a specific item of a list, or subset of it, or, for example, get amount of items in the list (good way to see if variable was set at all). Indexing starts with 1. Subsets can be defined as either beginning and length (with single “:”), or beginning and excluding end (with “::”), allowing to get all items exluding last, for example.

Possible modes defined in curly brackets (all modes can work with subset if needed):

  • :count — returns amount of values;
  • :length — returns string length of value (or several);
  • :exists — turns to 1 if value(s) exists, or in 0 otherwise;
  • :vec2, :vec3, :vec4 — turn value into a vector: forces certain length, turns everything which isn’t a number to 0, good for sanitizing inputs (see “Expressions”);
  • :required or :? — drop the whole value if variable (or its subset) is missing.
Subsets example

Before

[DEFAULTS]
PointInSpace = 12.3, 14.6, -25.2

[SECTION_0]
POINT = $PointInSpace

; Notice how indices are starting with 1. Older versions were using 0,
; but now we got Lua onboard and it uses 1. Also, it’s just nicer this way.
; Usually I wouldn’t do something so breaking, but it seems like there is not
; a lot of INIpp stuff yet and this is really annoying to have that difference.
COORD_X = ${PointInSpace:1}
COORD_Y = ${PointInSpace:2}
COORD_Z = ${PointInSpace:3}

[SECTION_1]
NUMBER_OF_DIMENSIONS = ${PointInSpace:count}

HAS_SECOND_DIMENSION = ${PointInSpace:2:exists}
HAS_THIRD_DIMENSION = ${PointInSpace:3:exists}
HAS_FOURTH_DIMENSION = ${PointInSpace: 4: exists} ; feel free to use as many spaces as you like
COORD_LAST = ${PointInSpace:-1}

; Four different ways of taking X and Y values (for three-dimensional value):
COORDS_XY_0 = ${PointInSpace::2}     ; just take first two elements
COORDS_XY_1 = ${PointInSpace:1:2}    ; taking two elements from 1th forward
COORDS_XY_2 = ${PointInSpace: 1 :: 3}   ; taking from 1th to 3th element excluding 3th
COORDS_XY_3 = ${PointInSpace: 1 :: -1}  ; taking from 0th to 1th element from the end excluding it

COORDS_XY_LENGTH = ${PointInSpace:1:2:length} ; total length of a strings of first two values
COORDS_XZ = ${PointInSpace:1}, ${PointInSpace:3}

After

[SECTION_0]
COORD_X = 12.3
COORD_Y = 14.6
COORD_Z = -25.2
POINT = 12.3,14.6,-25.2

[SECTION_1]
COORDS_XY_0 = 12.3,14.6
COORDS_XY_1 = 12.3,14.6
COORDS_XY_2 = 12.3,14.6
COORDS_XY_3 = 12.3,14.6
COORDS_XY_LENGTH = 8
COORDS_XZ = 12.3,-25.2
COORD_LAST = -25.2
HAS_FOURTH_DIMENSION = 0
HAS_SECOND_DIMENSION = 1
HAS_THIRD_DIMENSION = 1
NUMBER_OF_DIMENSIONS = 3

Substitute of variables within more complex values would also work as expected, same for strings with double quotes. Strings with single quotes don’t get variables though, similar to the way PHP works.

More complex example
[DEFAULTS]
Prefix = ello

[SECTION_1]
GREETING_0 = H${Prefix} World   ; works either with curly braces
GREETING_1 = "H$Prefix World"   ; or with double quotes
GREETING_2 = H$Prefix World     ; or even without quotes
GREETING_FAILED_1 = 'H${Prefix} World'  ; but not with single quotes
[DEFAULTS]
SomeVariable = A, B
OtherVariable = $SomeVariable, "[$SomeVariable]"

[SECTION_1]
LETTERS_WITH_ZEROS = ${SomeVariable}0 ; A0, B0
LETTERS_AND_LETTERS_IN_BRACKETS = prefix $OtherVariable ; prefix A, prefix B, prefix [A], prefix [B]

Important: I suggest using CamelCase for variables (and, shown later in docs, templates). CSP, as most other apps, expects values in upper case, so there’ll be much less chance of confict.

There is a scope mechanism for finding a variable, with inheritance and all that, which should work reasonable in most cases. However, I’d still recommend to prefer original names for parameters, as it might not always behave that well trying to figure out which value is to use. As a rule of thumb, parameters from [DEFAULTS] have lowest priority, parameters set explicitly for mixins and templates have highest priority, values from target section are in the middle.

Skipping

If needed, you can force INIpp to remove content of entire sections with ACTIVE = 0. If such value has been set, parser will erase any other values from the section at the final stage. Convinient for making some things optional.

Example
[SHADER_REPLACEMENT_...]
ACTIVE = $UseSpecificReplacement  ; skip section if variable wasn’t set to 1
MATERIALS = $SomeMaterialsToTweak
SHADER = ksPerPixel

[SHADER_REPLACEMENT_...]
ACTIVE = ${OtherMaterialsToTweak:count}  ; if materials are not set, whole section will be skipped
MATERIALS = $OtherMaterialsToTweak
SHADER = ksPerPixelNM

Expressions

This one is simple. If you add “$” in front of a string, its value will be calculated with Lua interpreter. On its own, it’s not very helpful, but could be convenient once variables get involved.

Important: variables substitution works differently for expressions. Strings get wrapped in quotes, missing values turn to nil, and if variable is a list, it’ll be passed as a table. Also, of course, if expression returns table, it’ll turn into a list. If variable has the length of 2, 3 or 4 and consists of nothing but valid numbers, it’ll be passed as a vector (the same table, but with working operators). Also, you can create new vectors in Lua using vec2(x, y), vec3(x, y, z) and vec4(x, y, z, w). Vectors also have methods vec2(x, y):length(), vec2(x, y):normalize() and dot(vec2(x, y), vec2(z, w)).

Expressions can drop the entire value by using discard() function. Also, there is a def(X, Y) function, returning Y if X is nil, great for defining default values.

Example

√2

[TEST]
SQUARE_ROOT_OF_TWO = $" sqrt(2) " ; `math.` functions copied to global scope

More useful example

[DEFAULTS]
LightsIntensity = 2
LightsDirection = 0.5, 0.5, 1

; Basic maths and flags
[LIGHT_...]
INTENSITY = $" 2 * $LightsIntensity "
ACTIVE = $" $LightsIntensity > 1 " ; boolean values will produce either 1 or 0

; Strings, tables and vectors
[LIGHT_...]
DESCRIPTION = $" 'Light with the intensity: ' .. (2 * $LightsIntensity) "
COLOR = $" { $LightsIntensity * 0.2, $LightsIntensity * 0.3, $LightsIntensity * 0.4 } " ; tables will turn into lists
DIRECTION = $" ${LightsDirection:vec3}:normalize() " ; “:vec3” is not required, but it’ll make sure expression will work even if LightsDirection doesn’t have any numbers in it

; Example of default values
[EXAMPLE]
KEY = $" def($Variable, 10) " ; if $Variable is not set, uses 10
KEY_2 = $" def2($Variable, 10, 15) " ; if $Variable is not set, uses (10, 15)

Functions

See more

To simplify writing expressions, it’s possible to define functions. Here is an example:

; this function turns color from HEX or 0–255 format into regular normalized numbers
[FUNCTION: ParseColor]
ARGUMENTS = v  ; input arguments to be used in code below
PRIVATE = 0    ; if set to 1, Lua state will be reset before loading next file
CODE = '
  if type(v) == "string" and v:sub(1, 1) == "#" then 
    if #v == 7 then return { tonumber(v:sub(2, 3), 16) / 255, tonumber(v:sub(4, 5), 16) / 255, tonumber(v:sub(6, 7), 16) / 255 } end
    if #v == 4 then return { tonumber(v:sub(2, 2), 16) / 15, tonumber(v:sub(3, 3), 16) / 15, tonumber(v:sub(4, 4), 16) / 15 } end
    error("Invalid color: "..v)
  end
  if type(v) == "table" and #v == 3 and ( v[1] > 1 or v[2] > 1 or v[3] > 1 ) then return { v[1] / 255, v[2] / 255, v[3] / 255 } end
  return v'
  
[DEFAULTS]
ColorHex = #abcdef
ColorHexShort = #f80
ColorRgb = 255, 127, 0
ColorRel = 1, 0.5, 0

[TEST]
VALUE1 = $" ParseColor( $ColorHex ) "       ; 0.67, 0.804, 0.937
VALUE2 = $" ParseColor( $ColorHexShort ) "  ; 1.0, 0.53, 0.0
VALUE3 = $" ParseColor( $ColorRgb ) "       ; 1.0, 0.5, 0.0
VALUE4 = $" ParseColor( $ColorRel ) "       ; 1.0, 0.5, 0.0

Some common functions could be moved into an external Lua-file, which can be included like this:

[USE: common/functions_base.lua]  ; search is done the same way as for included INI-files
PRIVATE = 0                       ; again, if set to 1, Lua state will be reset before loading next file

Templates

See more

With templates, you can create your own type of section which will unwrap into something different. Like so:

[TEMPLATE: Material_CarPaint]   ; prefix “TEMPLATE:” specifies it’s a template
SHADER = smCarPaint             ; this property will be copied to any section based on this template
MATERIALS = $CarPaintMaterial   ; templates can have their own variables

[SHADER_REPLACEMENT_... : Material_CarPaint]  ; with “:”, it marks section as based on a template
CarPaintMaterial = CarPaint_EXT               ; this is how section can specify template properties
SKINS = new_skin                              ; plus, it can define its own properties

This will be read into this:

[SHADER_REPLACEMENT_0]
MATERIALS = CarPaint_EXT
SHADER = smCarPaint
SKINS = new_skin

Important: please prefer using CamelCase for templates and variables. CSP, as most other apps, expects values in upper case, so there’ll be less chance of confict.

Templates, of course, can also specify default section name, which will be used if no specific is set, with @OUTPUT property:

[TEMPLATE: Material_CarPaint]
CarPaintShader = smCarPaint                 ; this is how pattern can define value by default
@OUTPUT = SHADER_REPLACEMENT_0CARPAINT_...  ; I highly recommend to use “...” in there: the whole idea 
                                            ; of templates was to be able to use them more than once
SHADER = $CarPaintShader
MATERIALS = $CarPaintMaterial

[Material_CarPaint]
CarPaintMaterial = CarPaint_EXT

Which produces:

[SHADER_REPLACEMENT_0CARPAINT_0]
MATERIALS = CarPaint_EXT
SHADER = smCarPaint

On top of that, templates can inherit one another:

[TEMPLATE: Material_CarPaint]
@OUTPUT = SHADER_REPLACEMENT_0CARPAINT_...
SHADER = smCarPaint
MATERIALS = $CarPaintMaterial

[TEMPLATE: Material_CarPaint_Old EXTENDS Material_CarPaint]
SHADER = smCarPaint_old

[Material_CarPaint_Old]
CarPaintMaterial = CarPaint_EXT

[Material_CarPaint_Old]  ; notice how we use the same template twice, with different parameters
CarPaintMaterial = CarPaint_EXT2
SKINS = some_skin

Those lines result it:

[SHADER_REPLACEMENT_0CARPAINT_0]
MATERIALS = CarPaint_EXT
SHADER = smCarPaint_old

[SHADER_REPLACEMENT_0CARPAINT_1]
MATERIALS = CarPaint_EXT2
SHADER = smCarPaint_old
SKINS = some_skin

Isn’t that nice? And consider that in everyday usage, templates mess will be hidden in included files.

Mixins

See more

Mixins are similar to templates, but could be deactivated with a condition, making them helpful in some specific cases. For example, imagine a template receiving either list of meshes or materials, and it needs to set MESHES = ${Meshes} or MATERIALS = ${Materials}, but with a condition that empty list shouldn’t be set at all (MESHES = could mean that thing should be applied to none meshes). With mixins, it’s an easy thing to set:

[MIXIN: _MeshesFilter]
@ACTIVE = ${Meshes:count}
MESHES = ${Meshes}

[MIXIN: _MaterialsFilter]
@ACTIVE = ${Materials:count}
MATERIALS = ${Materials}

[TEMPLATE: Material_DigitalScreen]
@OUTPUT = SHADER_REPLACEMENT_0_SCREEN_...
ACTIVE = $" ${Meshes:count} + ${Materials:count} "
@MIXIN = _MeshesFilter
@MIXIN = _MaterialsFilter

Not only templates, but any section can refer to a mixin. Also, mixins can include other mixins as well. Plus, extends keyword allows to build one mixins on top of others similar to templates.

Generators

See more

That’s some really messy stuff, but templates can generate more than a single section. Here is a basic example:

[TEMPLATE: _SimpleGenerator_0]
@OUTPUT = SIMPLE_GENERATOR_0
KEY_0 = $Value

[TEMPLATE: _SimpleGenerator_1]
@OUTPUT = SIMPLE_GENERATOR_1
KEY_1 = $" 2 * $Value "

[TEMPLATE: SimpleGenerator]
Value = 1 ; default value could be set as well
@GENERATOR = _SimpleGenerator_0
@GENERATOR = _SimpleGenerator_1

[SimpleGenerator]
Value = 5

That will generate this:

[SIMPLE_GENERATOR_0]
KEY_0 = 5

[SIMPLE_GENERATOR_1]
KEY_1 = 10

Same sub-template could be used more than once, and main generating template can pass different parameters to sub-templates:

[TEMPLATE: _SimpleGenerator]
@OUTPUT = SIMPLE_GENERATOR_...
KEY_0 = $Value

[TEMPLATE: SimpleGenerator]
@GENERATOR_0 = _SimpleGenerator
@GENERATOR_0:Value = hello
@GENERATOR_1 = _SimpleGenerator
@GENERATOR_1:Value = world

[SimpleGenerator]

Turns to:

[SIMPLE_GENERATOR_0]
KEY_0 = hello

[SIMPLE_GENERATOR_1]
KEY_0 = world

For extra special cases, one generator line can spawn multiple sections:

[TEMPLATE: _NotSoSimpleGenerator]
@OUTPUT = SIMPLE_GENERATOR_...
KEY_0 = $1

[TEMPLATE: NotSoSimpleGenerator]
@GENERATOR = _NotSoSimpleGenerator, 2

[NotSoSimpleGenerator]

Notice how $1 turns to the index:

[SIMPLE_GENERATOR_0]
KEY_0 = 0

[SIMPLE_GENERATOR_1]
KEY_0 = 1

And multidimensional case:

[TEMPLATE: _NotSoSimpleGenerator]
@OUTPUT = $" 'SIMPLE_GENERATOR_' .. $1 .. '_' .. $2 .. '_' .. $3 "
KEY_0 = $1, $2, $3

[TEMPLATE: NotSoSimpleGenerator]
@GENERATOR = _NotSoSimpleGenerator, 2, 2, 2

[NotSoSimpleGenerator]
[SIMPLE_GENERATOR_0_0_0]
KEY_0 = 0,0,0

[SIMPLE_GENERATOR_0_0_1]
KEY_0 = 0,0,1

[SIMPLE_GENERATOR_0_1_0]
KEY_0 = 0,1,0

[SIMPLE_GENERATOR_0_1_1]
KEY_0 = 0,1,1

[SIMPLE_GENERATOR_1_0_0]
KEY_0 = 1,0,0

[SIMPLE_GENERATOR_1_0_1]
KEY_0 = 1,0,1

[SIMPLE_GENERATOR_1_1_0]
KEY_0 = 1,1,0

[SIMPLE_GENERATOR_1_1_1]
KEY_0 = 1,1,1

A few extra tips:

  • You can see more interesting examples in “tests/auto” folder;
  • Single section can implement several templates at once: [Template1, Template2], or: [EXPLICIT_NAME : Template1, Template2];
    • It can also use the same template several times: [EXPLICIT_NAME : Template, Template], might be useful with auto-indexing for keys;
    • Templates can inherit several templates at once: [TEMPLATE: Template1 EXTENDS Template2, Template3];
  • If section is split in several parts, each with its own : Template, template will be processed several times;
  • If ${VariableWithCurlyBrackets} is not defined, it’ll turn into empty space;
  • If $SimpleVariable is missing, it’ll remain as $SimpleVariable;
  • Mixins or just simple sections can use generators as well;
  • Templates can also use @ACTIVE flag, similar to mixins;
  • Mixins are being added right at the moment they’re mentioned: you can call the same mixin twice with different parameters by doubling @MIXIN and redefining parameters before the second call;
  • You can use KEY_…=1 KEY_…=2 KEY_0=3 and it’ll get you all three values (first one will turn into KEY_1 and second to KEY_2 to avoid conflict with KEY_0 defined later);
    • For that to work, values with auto-incrementing keys turn into some mess until the very final stage, so do not reference them in substitutions and expressions, it’s going going to work;
  • Another way to add a parameter to a mixin is to use @MIXIN = MixinName, Key1 = Value1, Key2 = Value2 syntax, although it is limited;
    • It would also work with generators, although, again, limited (lists are not supported);
  • When certain key was already set before, new assignment will redefine it;
    • But, templates and mixins can’t redefine a value set by main section, even though it runs after main section was set (so it could provide template with parameters);
    • However, templates and mixins can redefine parameters which were set by themselves;
    • This allows to create things like counter to see how many times a mixin was called within current section: Count = $" $Count == nil and 1 or $Count + 1 ";
  • If, when template is used, section name is set explicitly, it can be accessed with $TARGET;
  • You can use expressions when referring to sub-template in generator;
  • You can use index variables when setting parameters for sub-templates in generator;
  • There is a crappy thing added in very early stages, before quotes, and kept for compatibility, where you can use Base64 representation of a PNG image without any quotes; please avoid using it if possible, regular quotes should be much better for that;
  • When editing configs for CSP, add [__DEBUG] DUMP_FLATTEN_INI = 1 to see what config has unwrapped to (would be saved nearby; that’s not a feature of this preprocessor).

Plans

  • More tests to cover everything;
  • Something to simplify LUTs creation;
  • Something for expressions to work with colors;
  • Possibly, scripts could have read & write access to whole section.

Credits

  • To working with files and values, some code from Reshade by crosire is used;
  • Alphanumerical sorting thanks to alphanum.hpp by David Koelle;
  • JSON is serialized with library by Niels Lohmann;
  • Debug mode is colorized by rang;
  • Expressions are made possible by Lua.

About

Another preprocessor, this time for an overengineered version of INI format: includes, variables, templates, expressions and so on

Resources

License

Stars

Watchers

Forks

Packages

No packages published