TSLPatcher 2DAList Syntax - NickHugi/PyKotor GitHub Wiki
TSLPatcher 2DAList Syntax - Complete Guide
Overview
The [2DAList] section in TSLPatcher's changes.ini enables you to modify 2DA (Two-Dimensional Array) files used throughout KotOR and TSL. 2DA files are tabular data structures that store game information such as appearances, classes, feats, items, spells, and more. You can change existing rows, add new rows, copy rows, and add columns using various targeting methods and value types.
The [2DAList] section is processed after [TLKList] but before [GFFList] in HoloPatcher, meaning you can use StrRef# tokens from TLKList, and any 2DAMEMORY# tokens you create will be available to GFFList and other sections.
Table of Contents
- Quick Start
- Cheatsheet
- Basic Structure
- Processing Order
- File-Level Configuration
- Modification Types
- Target Types
- Cell Values and RowValue Types
- Memory Token System
- Special Functions
- Examples
- Common Pitfalls and Troubleshooting
- Integration with Other Sections
Quick Start
- Add your 2DA file under
[2DAList]:
[2DAList]
Table0=appearance.2da
- Create a section named exactly like that file:
[appearance.2da]
ChangeRow0=modify_appearance
- Create the modification section to change a row:
[modify_appearance]
RowIndex=5
label=CUSTOM_APPEARANCE
modeltype=1
- Store values for later use with
2DAMEMORY#tokens:
[modify_appearance]
RowIndex=5
label=CUSTOM_APPEARANCE
2DAMEMORY10=RowIndex
2DAMEMORY11=label
- Use tokens from TLKList or other 2DA modifications:
[add_new_appearance]
label=MY_NEW_APPEARANCE
name=StrRef50
appearance=2DAMEMORY10
Cheatsheet
- Modification types:
ChangeRow#,AddRow#,CopyRow#,AddColumn# - Row targeting:
RowIndex=#→ Target by numeric row index (0-based)RowLabel=label→ Target by row label (first column value)LabelIndex=value→ Find row where "label" column equals value
- Cell values:
ColumnName=value→ Set cell to constant stringColumnName=StrRef#→ Use TLK stringref tokenColumnName=2DAMEMORY#→ Use 2DA memory tokenColumnName=high()→ Maximum value in that columnColumnName=RowIndex→ Current row's indexColumnName=RowLabel→ Current row's labelColumnName=RowCell('column')→ Value from another cellColumnName=****→ Empty string
- Memory storage:
2DAMEMORY#=RowIndex→ Store row index2DAMEMORY#=RowLabel→ Store row label2DAMEMORY#=ColumnName→ Store cell value from that columnStrRef#=value→ Store stringref for later use
- Special row properties:
RowLabel=value→ Set row label (for AddRow/CopyRow)NewRowLabel=value→ Alternative name for RowLabelExclusiveColumn=name→ Check for existing row by column value (AddRow/CopyRow)
Basic Structure
[2DAList]
!DefaultDestination=override
!DefaultSourceFolder=.
Table0=appearance.2da
Replace0=classes.2da
[appearance.2da]
!Destination=override
!SourceFolder=.
!SourceFile=custom_appearance.2da
!ReplaceFile=0
!SaveAs=appearance.2da
ChangeRow0=modify_row_1
AddRow0=add_new_row
CopyRow0=copy_existing_row
AddColumn0=add_new_column
[modify_row_1]
RowIndex=5
label=CUSTOM_APPEARANCE
modeltype=1
[add_new_row]
ExclusiveColumn=label
RowLabel=10
label=NEW_APPEARANCE
name=StrRef100
[copy_existing_row]
RowLabel=1
ExclusiveColumn=label
NewRowLabel=9
label=COPIED_APPEARANCE
[add_new_column]
ColumnLabel=NewColumn
DefaultValue=0
I5=CustomValue
L1=AnotherValue
The [2DAList] section declares which 2DA files you want to modify. Each entry (like Table0, Table1, etc., or Replace0, Replace1, etc.) references another section with the same name as the filename.
Syntax Notes:
- Use
Table#to add a new 2DA file modification (non-replacing) - Use
Replace#to replace an existing 2DA file before applying modifications - The
#is a sequential number starting from 0 (Table0, Table1, Table2, etc.) - Numbers can be sequential, but gaps are allowed (Table0, Table2, Table5 is valid)
- Each file section contains modification entries (
ChangeRow#,AddRow#,CopyRow#,AddColumn#)
Processing Order
In HoloPatcher, the 2DAList runs in the following execution order:
- [InstallList] - Files are installed first
- [TLKList] - TLK modifications (creates
StrRef#tokens) - [2DAList] ← You are here - 2DA file modifications (creates
2DAMEMORY#tokens) - [GFFList] - GFF file modifications (can use
StrRef#and2DAMEMORY#tokens) - [CompileList] - Script compilation (can use
StrRef#and2DAMEMORY#tokens) - [HACKList] - Binary hacking (can use
StrRef#and2DAMEMORY#tokens) - [SSFList] - Sound set modifications (can use
StrRef#and2DAMEMORY#tokens)
Important: Since 2DAList runs after TLKList, you can use StrRef# tokens in your 2DA modifications. Any 2DAMEMORY# tokens you create will be available to all subsequent sections (GFFList, CompileList, HACKList, SSFList).
File-Level Configuration
Top-Level Keys in [2DAList]
| Key | Type | Default | Description |
|---|---|---|---|
!DefaultDestination |
string | override |
Default destination for all 2DA files in this section |
!DefaultSourceFolder |
string | . |
Default source folder for 2DA files. Relative path from mod_path (typically the tslpatchdata folder, which is the parent directory of changes.ini and namespaces.ini). When ., refers to the tslpatchdata folder itself. Path resolution: mod_path / !DefaultSourceFolder / filename |
File Section Configuration
Each 2DA file requires its own section (e.g., [appearance.2da]).
| Key | Type | Default | Description |
|---|---|---|---|
!Destination |
string | Inherited from !DefaultDestination |
Where to save the modified file (override or path\to\file.mod) |
!SourceFolder |
string | Inherited from !DefaultSourceFolder |
Source folder for the 2DA file. Relative path from mod_path (typically the tslpatchdata folder). When ., refers to the tslpatchdata folder itself. |
!SourceFile |
string | Same as section name | Alternative source filename (useful for multiple setup options using different source files) |
!ReplaceFile |
0/1 | 0 | If 1, overwrite existing file before applying modifications. If 0 (default), modify the existing file in place. |
!SaveAs |
string | Same as section name | Alternative filename to save as (useful for renaming files during installation) |
!OverrideType |
string | warn (HoloPatcher) / ignore (TSLPatcher) |
How to handle existing files in Override when destination is an ERF/RIM archive. Valid values: ignore, warn, rename |
Destination Values:
overrideor empty: Save to the Override folderModules\module.mod: Insert into an ERF/MOD/RIM archive (use backslashes for path separators)- Archive paths must be relative to the game folder root
Syntax Notes:
!DefaultSourceFolderand!SourceFolderdefault to.which refers to thetslpatchdatafolder itself- When specifying paths, use backslashes (
\) as path separators (TSLPatcher style), though forward slashes (/) are also accepted and normalized - Path resolution:
mod_path / !SourceFolder / !SourceFile(or section name if!SourceFileis not set)
Modification Types
ChangeRow - Modify Existing Row
Syntax: ChangeRow#=section_name
Changes an existing row in the 2DA file. You must specify which row to modify using one of the target types (see Target Types).
Required Keys:
- One of:
RowIndex,RowLabel, orLabelIndex(to identify which row to modify)
Optional Keys:
- Any column name (to modify cell values)
2DAMEMORY#=value(to store values in memory)StrRef#=value(to store stringref values in memory)
Example:
[2DAList]
Table0=appearance.2da
[appearance.2da]
ChangeRow0=modify_appearance_5
[modify_appearance_5]
RowIndex=5
label=CUSTOM_APPEARANCE
modeltype=1
Example with Memory Storage:
[modify_appearance_5]
RowIndex=5
label=CUSTOM_APPEARANCE
modeltype=1
2DAMEMORY10=RowIndex ; Store row index (5) in memory token 10
2DAMEMORY11=RowLabel ; Store row label in memory token 11
2DAMEMORY12=modeltype ; Store modeltype value in memory token 12
StrRef20=name ; Store name stringref in TLK memory token 20
Behavior:
- If the target row is not found, a warning is logged and the modification is skipped
- Existing cell values are overwritten with new values
- Columns that are not specified remain unchanged
- Memory tokens are evaluated and stored after cell modifications are applied
AddRow - Add New Row
Syntax: AddRow#=section_name
Adds a new row to the 2DA file. If ExclusiveColumn is specified and a row with that value already exists, the existing row is modified instead of adding a new one.
Required Keys:
- None (empty section creates a row with default/empty values)
Optional Keys:
ExclusiveColumn=column_name→ Check if a row with the same value in this column already exists; if so, modify it instead of addingRowLabel=valueorNewRowLabel=value→ Set the row label (defaults to current row count if not specified)- Any column name (to set cell values)
2DAMEMORY#=value(to store values in memory)StrRef#=value(to store stringref values in memory)
Example:
[appearance.2da]
AddRow0=add_new_appearance
[add_new_appearance]
ExclusiveColumn=label
RowLabel=100
label=MY_NEW_APPEARANCE
name=StrRef200
modeltype=2
Example with Exclusive Column (Prevent Duplicates):
[add_new_appearance]
ExclusiveColumn=label
label=MY_NEW_APPEARANCE
name=StrRef200
If a row with label=MY_NEW_APPEARANCE already exists, it will be modified. Otherwise, a new row will be added.
Behavior:
- New row is added with the specified cell values
- If
ExclusiveColumnis specified and a matching row exists, that row is updated instead - Row label defaults to the current row count (as a string) if
RowLabel/NewRowLabelis not specified - Memory tokens are evaluated and stored after the row is added/modified
CopyRow - Copy and Conditionally Add Row
Syntax: CopyRow#=section_name
Copies an existing row (identified by a target) and optionally adds it as a new row or modifies an existing one if ExclusiveColumn matches.
Required Keys:
- One of:
RowIndex,RowLabel, orLabelIndex(to identify the source row to copy)
Optional Keys:
ExclusiveColumn=column_name→ If a row with the same value in this column already exists, modify that row instead of adding a new oneRowLabel=valueorNewRowLabel=value→ Set the new row's label (defaults to current row count if not specified)- Any column name (to override cell values from the copied row)
2DAMEMORY#=value(to store values in memory)StrRef#=value(to store stringref values in memory)
Example:
[appearance.2da]
CopyRow0=copy_appearance_1
[copy_appearance_1]
RowLabel=1
ExclusiveColumn=label
NewRowLabel=50
label=COPIED_APPEARANCE
name=StrRef300
Example - Copy and Modify:
[copy_appearance_1]
RowIndex=5
ExclusiveColumn=label
label=MODIFIED_COPY
modeltype=3
Behavior:
- The source row (identified by target) is copied
- All cell values from the source row are preserved unless overridden
- If
ExclusiveColumnis specified and a matching row exists, that existing row is updated - If
ExclusiveColumnis not specified or no match is found, a new row is added - If the source row is not found, an error is raised
- Memory tokens are evaluated and stored after the row is copied/modified
AddColumn - Add New Column
Syntax: AddColumn#=section_name
Adds a new column to the 2DA file with a default value for all rows. Specific rows can be given custom values using index or label-based inserts.
Required Keys:
ColumnLabel=column_name→ The name of the new columnDefaultValue=value→ Default value for all rows (use****for empty string)
Optional Keys:
I#=value→ Set value for row at index#(e.g.,I5=CustomValuesets row index 5)Llabel=value→ Set value for row with labellabel(e.g.,L1=CustomValuesets row with label "1")2DAMEMORY#=I#or2DAMEMORY#=Llabel→ Store the cell value from the new column into memory token#after the column is created- Use
I#format to reference by row index (e.g.,2DAMEMORY10=I5stores the value from row index 5 in the new column) - Use
Llabelformat to reference by row label (e.g.,2DAMEMORY10=L1stores the value from the row with label "1" in the new column) - Memory storage happens after the column is created and all insert values are applied
- Use
Example:
[appearance.2da]
AddColumn0=add_custom_column
[add_custom_column]
ColumnLabel=CustomColumn
DefaultValue=0
I5=SpecialValue
L1=AnotherValue
2DAMEMORY10=I5
Example with Memory Storage:
[add_custom_column]
ColumnLabel=NewProperty
DefaultValue=****
I0=ValueForRow0
I1=ValueForRow1
L5=ValueForLabel5
2DAMEMORY20=I0 ; Store value from row index 0 after column is created
2DAMEMORY21=L5 ; Store value from row label 5 after column is created
Behavior:
- New column is added to all rows
- All rows initially receive the
DefaultValue - Rows specified in
I#orLlabelentries get their custom values - If a row specified in
I#doesn't exist, an error is raised - If a row specified in
Llabeldoesn't exist, an error is raised - Memory Storage: Memory tokens specified with
2DAMEMORY#=I#or2DAMEMORY#=Llabelstore the cell value from the new column after it's created and all insert values are applied2DAMEMORY#=I5retrieves the cell value from row index 5 in the newly created column2DAMEMORY#=L1retrieves the cell value from the row with label "1" in the newly created column- This allows you to capture values from the new column for use in later modifications
Special Value Syntax in AddColumn:
For I# and Llabel values (the right side of the assignment), you can use:
- Constant strings:
I5=CustomValue - Token references:
I5=2DAMEMORY10,I5=StrRef20 - Special functions (
high(),RowIndex,RowLabel) are not supported in AddColumn (unlike ChangeRow/AddRow/CopyRow)
Memory Storage Syntax:
For memory storage (2DAMEMORY#=), you must use:
2DAMEMORY#=I#- Store value from row at index#in the new column2DAMEMORY#=Llabel- Store value from row with labellabelin the new column
The I# and Llabel on the right side of 2DAMEMORY#= refer to which row's value to store from the newly created column, not the value to insert.
Target Types
Target types identify which row to modify in ChangeRow and CopyRow operations. Only one target type can be specified per modification.
RowIndex
Syntax: RowIndex=integer
Targets a row by its numeric index (0-based).
Example:
[modify_row]
RowIndex=5
label=MODIFIED
Behavior:
- Directly accesses the row at the specified index
- If the index is out of bounds, the row is not found and a warning is logged
- The value must be a valid integer
- Dynamic targeting: Can use
2DAMEMORY#tokens for dynamic row selection (e.g.,RowIndex=2DAMEMORY10will use the value stored in token 10)
RowLabel
Syntax: RowLabel=label_string
Targets a row by its label (the value in the first column, typically named "label").
Example:
[modify_row]
RowLabel=1
label=MODIFIED
Behavior:
- Searches for a row where the row label matches the specified value
- Uses string comparison (case-sensitive)
- If no matching row is found, a warning is logged
- Dynamic targeting: RowLabel can accept token references for dynamic row selection:
RowLabel=2DAMEMORY10- Uses the value stored in 2DA memory token 10RowLabel=StrRef20- Uses the stringref value from TLK memory token 20 (converted to string)
- This allows you to dynamically determine which row to modify based on previously stored values
LabelIndex
Syntax: LabelIndex=value
Targets a row by searching the "label" column for a matching value. This is different from RowLabel because it searches within a specific column named "label" rather than using the row's label value.
Example:
[modify_row]
LabelIndex=MY_APPEARANCE
label=MODIFIED
Behavior:
- Requires the 2DA to have a column named "label"
- Searches all rows for a cell in the "label" column that matches the specified value
- If the "label" column doesn't exist, an error is raised
- If no matching row is found, a warning is logged
- Dynamic targeting: LabelIndex can accept token references for dynamic row selection:
LabelIndex=2DAMEMORY10- Uses the value stored in 2DA memory token 10LabelIndex=StrRef20- Uses the stringref value from TLK memory token 20 (converted to string)
- This allows you to dynamically search for rows based on previously stored values
Note: RowLabel and LabelIndex may seem similar, but they operate differently:
RowLabeluses the row's label value (first column's value)LabelIndexsearches within a column named "label" for a matching value
Cell Values and RowValue Types
When setting cell values in ChangeRow, AddRow, and CopyRow, you can use various value types. Each cell value is parsed as a RowValue type based on the syntax.
Constant String Values
Syntax: ColumnName=any_string
The simplest value type - a literal string that will be placed in the cell.
Example:
label=CUSTOM_APPEARANCE
modeltype=1
description=This is a custom appearance
Empty String
Syntax: ColumnName=****
Use **** to set a cell to an empty string.
Example:
comment=****
notes=****
Token References
StrRef Tokens
Syntax: ColumnName=StrRef#
References a stringref token created in the [TLKList] section. The token number is extracted and the value is looked up from TLK memory.
Example:
name=StrRef50
description=StrRef100
Behavior:
- Token must be defined in
[TLKList]before use - Value stored in the token is used as the cell value
- If token is not found, an error is raised
2DAMEMORY Token References(#2damemory-token-references)
Syntax: ColumnName=2DAMEMORY#
References a 2DA memory token created in a previous 2DAList modification. The token number is extracted and the value is looked up from 2DA memory.
Example:
appearance=2DAMEMORY10
model=2DAMEMORY5
Behavior:
- Token must be defined earlier in the same or a previous 2DA file modification
- Value stored in the token (as a string) is used as the cell value
- If token is not found, an error is raised
- Important:
!FieldPathtokens (used in GFFList) cannot be used here - only string values are supported - The token value is looked up from
memory.memory_2da[token_id]at runtime - If the token contains a
PureWindowsPath(from GFFList!FieldPath), aTypeErrorwill be raised
Special Functions
high() - Maximum Value
Syntax: ColumnName=high() or ColumnName=high(column_name)
Returns the maximum value from a column or the maximum row label.
Without Column Name:
RowLabel=high() ; Maximum row label (used when setting row label)
forcehostile=high() ; Maximum value in the "forcehostile" column
With Column Name:
forcehostile=high(modeltype) ; Maximum value from "modeltype" column
Behavior:
high()without column name inRowLabelcontext returns the maximum row labelhigh()without column name in a cell context returns the maximum value from that cell's columnhigh(column)returns the maximum value from the specified column- Values are compared as integers if possible, otherwise as strings
- Only works in ChangeRow, AddRow, and CopyRow (not in AddColumn)
RowIndex - Current Row Index
Syntax: ColumnName=RowIndex
Returns the numeric index of the current row as a string.
Example:
index_value=RowIndex
Behavior:
- Returns the row index (0-based) as a string
- Only works in ChangeRow, AddRow, and CopyRow cell values
- Cannot be used as a target (use
RowIndex=#for targeting)
RowLabel - Current Row Label
Syntax: ColumnName=RowLabel
Returns the label of the current row (value in the first column).
Example:
label_copy=RowLabel
Behavior:
- Returns the row's label value as a string
- Only works in ChangeRow, AddRow, and CopyRow cell values
- Cannot be used as a target (use
RowLabel=valuefor targeting)
RowCell - Value from Another Cell
Syntax: ColumnName=RowCell('column_name')
Note: This syntax is not directly supported in the INI format. In practice, you would reference a column name directly to get its value, or use 2DAMEMORY# tokens. The RowCell type exists internally but is primarily used for memory storage operations.
For Memory Storage:
2DAMEMORY10=modeltype ; Stores value from "modeltype" column (this internally uses RowCell)
Memory Token System
The memory token system allows you to store values from 2DA modifications for use in other sections or later modifications.
2DAMEMORY Tokens
Syntax: 2DAMEMORY#=value_source
Stores a value in 2DA memory at token #. The token number can be any non-negative integer (typically starting from 0 or 1).
Available Value Sources:
| Value Source | Description | Example | Internal Type |
|---|---|---|---|
RowIndex |
Store the row's numeric index (0-based) as string | 2DAMEMORY10=RowIndex |
RowValueRowIndex() |
RowLabel |
Store the row's label value (first column) | 2DAMEMORY11=RowLabel |
RowValueRowLabel() |
ColumnName |
Store the value from a specific column in the current row | 2DAMEMORY12=modeltype |
RowValueRowCell(column) |
StrRef# |
Store the stringref value from a TLK token (converted to string) | 2DAMEMORY13=StrRef50 |
RowValueTLKMemory(token_id) |
2DAMEMORY# |
Copy value from another 2DA token | 2DAMEMORY14=2DAMEMORY10 |
References existing token |
Note: The internal types listed above are runtime evaluation objects that compute values when the modification is applied. They are only used for storage operations (left side of 2DAMEMORY#=), not for cell value assignments (right side of ColumnName=).
Example - Storing Multiple Values:
[modify_appearance]
RowIndex=5
label=CUSTOM_APPEARANCE
modeltype=1
2DAMEMORY10=RowIndex ; Stores "5"
2DAMEMORY11=RowLabel ; Stores "CUSTOM_APPEARANCE" or row label value
2DAMEMORY12=modeltype ; Stores "1"
Example - Using Stored Values:
[add_new_appearance]
label=ANOTHER_APPEARANCE
modeltype=2DAMEMORY12 ; Use stored modeltype value
appearance=2DAMEMORY10 ; Use stored row index
Behavior:
- Tokens are stored as strings in 2DA memory (
memory.memory_2da[token_id]) - Evaluation Order: Memory storage operations (
2DAMEMORY#=...) are evaluated after all cell modifications are applied within the same modification section - This means you cannot use a token in a cell value (
ColumnName=2DAMEMORY#) and create it (2DAMEMORY#=...) in the same section - create tokens in earlier modifications - Tokens are available to all subsequent sections (GFFList, CompileList, HACKList, SSFList)
- Tokens persist across multiple 2DA file modifications within the same
[2DAList]section - If a token is referenced before being set, a
KeyErroris raised:"2DAMEMORY{id} was not defined before use" - Storage Type: Values are stored as
strtype (orPureWindowsPathfor GFFList!FieldPath, but those cannot be used in 2DAList)
StrRef Tokens (TLK Memory)
Syntax: StrRef#=value_source
Stores a stringref value in TLK memory at token #. These tokens are primarily created in [TLKList], but can also be set here.
Available Value Sources:
| Value Source | Description | Example |
|---|---|---|
ColumnName |
Store the stringref from a specific column (value must be convertible to integer) | StrRef20=name |
StrRef# |
Copy stringref value from another TLK token | StrRef21=StrRef20 |
2DAMEMORY# |
Store stringref from a 2DA token (value must be convertible to integer) | StrRef22=2DAMEMORY10 |
Example:
[modify_appearance]
RowIndex=5
name=12345
StrRef30=name ; Store stringref 12345 in token 30
StrRef31=StrRef30 ; Copy token 30 to token 31
Behavior:
- Values are stored as integers in TLK memory
- The source value must be convertible to an integer (stringrefs are integers)
- Tokens are available to all subsequent sections
- StrRef tokens are primarily used in GFFList for localized string fields
Token Usage in Other Sections
Once created, 2DAMEMORY# and StrRef# tokens can be used in:
- Later 2DA modifications - Use in ChangeRow, AddRow, CopyRow, or AddColumn
- GFFList - Use in field values, field paths, or TypeId fields
- CompileList - Use as preprocessor tokens in NSS scripts (
#2DAMEMORY#and#StrRef#) - HACKList - Use in binary patch values
- SSFList - Use for sound stringref assignments
Example Cross-Section Usage:
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddRow0=new_appearance
[new_appearance]
RowLabel=100
label=MY_APPEARANCE
2DAMEMORY10=RowIndex
[GFFList]
File0=item.uti
[item.uti]
ModelVariation=2DAMEMORY10 ; Use the stored row index
Examples
Example 1: Modify Existing Appearance
[2DAList]
Table0=appearance.2da
[appearance.2da]
ChangeRow0=modify_human_male
[modify_human_male]
RowLabel=1
label=HUMAN_MALE
modeltype=1
2DAMEMORY10=RowIndex
Example 1a: Dynamic Row Targeting with Tokens
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddRow0=store_row_index
[store_row_index]
RowLabel=100
label=MY_APPEARANCE
2DAMEMORY10=RowIndex ; Store the new row's index
[appearance.2da]
ChangeRow0=modify_using_token
[modify_using_token]
RowIndex=2DAMEMORY10 ; Dynamically target the row we just created
label=MODIFIED_APPEARANCE
modeltype=2
This demonstrates how you can store a row index in one modification and use it to target that row in a later modification.
Example 2: Add New Appearance with Exclusive Check
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddRow0=add_custom_appearance
[add_custom_appearance]
ExclusiveColumn=label
RowLabel=100
label=CUSTOM_APPEARANCE
name=StrRef500
modeltype=2
Example 3: Copy Row and Modify
[2DAList]
Table0=appearance.2da
[appearance.2da]
CopyRow0=copy_and_modify
[copy_and_modify]
RowIndex=5
ExclusiveColumn=label
label=MODIFIED_COPY
modeltype=3
2DAMEMORY20=RowIndex
Example 4: Add Column with Custom Values
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddColumn0=add_custom_property
[add_custom_property]
ColumnLabel=CustomProperty
DefaultValue=0
I5=100
L1=200
2DAMEMORY30=I5
Example 5: Complex Multi-Row Modification
[2DAList]
Table0=appearance.2da
[appearance.2da]
ChangeRow0=modify_row_1
ChangeRow1=modify_row_2
AddRow0=add_new_row
CopyRow0=copy_row
AddColumn0=add_column
[modify_row_1]
RowIndex=1
label=MODIFIED_1
2DAMEMORY10=RowIndex
[modify_row_2]
RowLabel=2
label=MODIFIED_2
appearance=2DAMEMORY10
[add_new_row]
ExclusiveColumn=label
RowLabel=50
label=NEW_APPEARANCE
name=StrRef1000
[copy_row]
RowIndex=1
ExclusiveColumn=label
label=COPIED_APPEARANCE
modeltype=2DAMEMORY10
[add_column]
ColumnLabel=NewColumn
DefaultValue=****
I1=Value1
I2=Value2
L50=Value3
2DAMEMORY40=I1
Example 6: Using Tokens from TLKList
[TLKList]
StrRef1000=Hello World
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddRow0=new_appearance
[new_appearance]
RowLabel=100
label=MY_APPEARANCE
name=StrRef1000 ; Use token from TLKList
Example 7: Cross-File Token Usage
[2DAList]
Table0=appearance.2da
Table1=classes.2da
[appearance.2da]
AddRow0=store_appearance
[store_appearance]
RowLabel=100
label=MY_APPEARANCE
2DAMEMORY10=RowIndex
[classes.2da]
ChangeRow0=use_appearance
[use_appearance]
RowIndex=5
appearance=2DAMEMORY10 ; Use token from previous file modification
Common Pitfalls and Troubleshooting
Row Not Found Errors
Problem: "The source row was not found during the search"
Solutions:
- Verify the target row exists (check RowIndex is within bounds, RowLabel matches exactly, or LabelIndex value exists in "label" column)
- Ensure case-sensitivity:
RowLabel=MyLabelwill not matchmylabel - Check that the 2DA file has been loaded correctly
- Verify
!SourceFileis correct if using a custom source file
Example Fix:
; Before (may fail if row doesn't exist)
[modify_row]
RowIndex=999
label=MODIFIED
; After (check row exists first, or use safer targeting)
[modify_row]
RowLabel=1
label=MODIFIED
Token Not Defined Errors
Problem: "2DAMEMORY# was not defined before use" or "StrRef# was not defined before use"
Solutions:
- Ensure the token is created before it's used
- For 2DAMEMORY tokens: Create in an earlier modification in the same file or previous file
- For StrRef tokens: Ensure they're created in
[TLKList]or earlier in[2DAList] - Check token numbers match exactly (no typos)
Example Fix:
; Wrong - token used before creation
[add_row]
label=NEW
appearance=2DAMEMORY10
2DAMEMORY10=RowIndex ; Too late!
; Correct - token created first
[add_row]
label=NEW
2DAMEMORY10=RowIndex ; Create first
appearance=2DAMEMORY10 ; Use after creation
Note: Within the same modification section, memory storage operations (2DAMEMORY#=...) are evaluated after cell modifications (ColumnName=...), so you cannot use a token in a cell value and create it in the same section.
Example of the problem:
; This will FAIL - token used before it's created
[add_row]
label=NEW
appearance=2DAMEMORY10 ; Tries to use token 10 (not yet created)
2DAMEMORY10=RowIndex ; Creates token 10 (too late!)
Solution: Create tokens in earlier modifications, then use them in later ones.
Invalid Column Names
Problem: Column doesn't exist in the 2DA file
Solutions:
- Verify column names match exactly (case-sensitive)
- Use AddColumn to add the column first if it doesn't exist
- Check the source 2DA file to see available columns
ExclusiveColumn Behavior
Problem: Unexpected row modification instead of adding new row (or vice versa)
Solutions:
- Understand that
ExclusiveColumnchecks if a row with the same value exists - If match found: existing row is modified
- If no match: new row is added
- Ensure the column specified in
ExclusiveColumnis included in the cell modifications
Example:
; This will modify existing row if label="MY_APPEARANCE" exists
[add_row]
ExclusiveColumn=label
label=MY_APPEARANCE
name=StrRef100
AddColumn Memory Storage Syntax
Problem: Incorrect syntax for storing values from new column
Solutions:
- Use
I#format for row index:2DAMEMORY#=I5(stores value from row index 5 in the new column) - Use
Llabelformat for row label:2DAMEMORY#=L1(stores value from row with label "1" in the new column) - Memory is stored after the column is created and all insert values (
I#=andLlabel=) are applied - Cannot use other RowValue types (like
RowIndex,RowLabel,ColumnName, etc.) directly in AddColumn memory storage - onlyI#andLlabelformats are supported - The
I#orLlabelsyntax tells the patcher to retrieve the cell value from that specific row in the newly created column
Example:
[add_column]
ColumnLabel=NewColumn
DefaultValue=0
I5=Value
2DAMEMORY10=I5 ; Correct - stores value from row index 5
2DAMEMORY11=RowIndex ; Wrong - RowIndex not supported in AddColumn memory storage
Target Type Confusion
Problem: Confusion between RowLabel and LabelIndex
Solutions:
RowLabel=value→ Uses the row's label (first column value) to find the rowLabelIndex=value→ Searches the "label" column for a matching value- Use
RowLabelwhen you know the row label value - Use
LabelIndexwhen you need to search within a column named "label"
Empty String vs Missing Values
Problem: Confusion about **** vs omitted keys
Solutions:
****explicitly sets a cell to an empty string- Omitting a column key leaves the cell unchanged (in ChangeRow/CopyRow) or empty (in AddRow)
- Use
****when you want to explicitly clear a value
Special Functions Not Working
Problem: high(), RowIndex, RowLabel not working as expected
Solutions:
- Special functions only work in ChangeRow, AddRow, and CopyRow cell values
- They do not work in AddColumn inserts or default values
high()without column name uses the current column context- Check syntax:
high()nothigh,RowIndexnotRowindex
Integration with Other Sections
Using StrRef Tokens from TLKList
Since [2DAList] runs after [TLKList], you can use StrRef# tokens in your 2DA modifications:
[TLKList]
StrRef500=Custom Appearance Name
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddRow0=new_appearance
[new_appearance]
RowLabel=100
label=CUSTOM_APPEARANCE
name=StrRef500
Creating 2DAMEMORY Tokens for GFFList
Any 2DAMEMORY# tokens you create in [2DAList] are available in [GFFList]:
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddRow0=store_appearance_id
[store_appearance_id]
RowLabel=100
label=MY_APPEARANCE
2DAMEMORY10=RowIndex
[GFFList]
File0=item.uti
[item.uti]
ModelVariation=2DAMEMORY10 ; Use stored row index
Using 2DAMEMORY Tokens in CompileList
In [CompileList], you can use 2DAMEMORY# tokens as preprocessor tokens in NSS scripts:
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddRow0=store_id
[store_id]
RowLabel=100
2DAMEMORY10=RowIndex
[CompileList]
File0=script.nss
[script.nss]
; In script.nss:
; ChangeObjectAppearance(OBJECT_SELF, #2DAMEMORY10#);
Using 2DAMEMORY Tokens in HACKList
In [HACKList], you can use 2DAMEMORY# tokens for binary patch values:
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddRow0=store_id
[store_id]
RowLabel=100
2DAMEMORY10=RowIndex
[HACKList]
File0=script.ncs
[script.ncs]
40=2DAMEMORY10 ; Modify offset 40 (hex 0x28) with stored value
Using 2DAMEMORY Tokens in SSFList
In [SSFList], you can use 2DAMEMORY# tokens for sound stringref assignments:
[2DAList]
Table0=appearance.2da
[appearance.2da]
AddRow0=store_id
[store_id]
RowLabel=100
2DAMEMORY10=RowIndex
[SSFList]
File0=soundset.ssf
[soundset.ssf]
Battlecry 1=2DAMEMORY10 ; Use stored value as stringref
Processing Order and Execution
Modification Order Within a File
Modifications within a single 2DA file are processed in the order they appear in the file section:
[appearance.2da]
ChangeRow0=modify_first
ChangeRow1=modify_second
AddRow0=add_new
CopyRow0=copy_existing
AddColumn0=add_column
Processing order:
- All
ChangeRow#modifications (in order) - All
AddRow#modifications (in order) - All
CopyRow#modifications (in order) - All
AddColumn#modifications (in order)
Important: Since AddColumn runs last, columns added by AddColumn cannot be used in earlier ChangeRow/AddRow/CopyRow modifications within the same file. However, tokens created in earlier modifications are available for AddColumn.
Cross-File Token Availability
Tokens created in earlier files are available to later files:
[2DAList]
Table0=appearance.2da
Table1=classes.2da
[appearance.2da]
AddRow0=create_token
[create_token]
RowLabel=100
2DAMEMORY10=RowIndex
[classes.2da]
ChangeRow0=use_token
[use_token]
RowIndex=5
appearance=2DAMEMORY10 ; Token from previous file is available
Advanced Patterns
Pattern 1: Conditional Row Creation
Use ExclusiveColumn to prevent duplicate rows:
[add_appearance]
ExclusiveColumn=label
label=MY_APPEARANCE
name=StrRef100
This will modify existing row if label=MY_APPEARANCE exists, otherwise add a new row.
Pattern 2: Storing Multiple Values from One Row
[modify_appearance]
RowIndex=5
2DAMEMORY10=RowIndex
2DAMEMORY11=RowLabel
2DAMEMORY12=modeltype
2DAMEMORY13=name
Store multiple values for use in other sections.
Pattern 3: Incremental Row Labels
Use high() to automatically assign the next available row label:
[add_appearance]
RowLabel=high()
label=NEW_APPEARANCE
Pattern 4: Copy and Modify Pattern
Copy an existing row, modify some values, and store the new row index:
[copy_modify]
RowIndex=1
ExclusiveColumn=label
label=MODIFIED_COPY
modeltype=3
2DAMEMORY10=RowIndex
Summary
The [2DAList] section provides powerful tools for modifying 2DA files:
- ChangeRow: Modify existing rows by index, label, or label column
- AddRow: Add new rows with optional duplicate checking
- CopyRow: Copy existing rows with modifications
- AddColumn: Add new columns with default and custom values
- Memory Tokens: Store values for cross-file and cross-section use
- Token Integration: Use StrRef tokens from TLKList and provide 2DAMEMORY tokens to other sections
Key points to remember:
- Tokens are evaluated after cell modifications within the same section
- Tokens persist across multiple files in the same
[2DAList]section ExclusiveColumnprovides smart duplicate prevention- Special functions (
high(),RowIndex,RowLabel) only work in ChangeRow/AddRow/CopyRow - AddColumn runs last within a file, so new columns can't be used in earlier modifications
- All memory tokens are available to subsequent sections (GFFList, CompileList, HACKList, SSFList)
For more information on related sections, see:
- TSLPatcher TLKList Syntax - Creating StrRef tokens
- TSLPatcher GFFList Syntax - Using 2DAMEMORY tokens in GFF files
- TSLPatcher CompileList Syntax - Using tokens in script compilation
- TSLPatcher HACKList Syntax - Using tokens in binary patches
- TSLPatcher SSFList Syntax - Using tokens in sound sets