-
Notifications
You must be signed in to change notification settings - Fork 4
Mod Creation Best Practices
KotOR modding has historically been fragmented: different tools, version quirks, and a lot of critical knowledge trapped in forum memory. The practical parts deserve to be durable. Use whatever tools help you ship, but prefer workflows that are reproducible, merge-aware, and explainable to the next person who has to maintain the mod.
The sections below cover where files should go, when patchers are required, how to avoid common compatibility mistakes, and what to test before release. For tool syntax and installation, see:
The practices below follow both the current toolchain and long-running community workflows. Stoffe's original TSLPatcher release thread explains the historical reasons for merge-aware installs 1, while later community tooling discussions and release pages show how those expectations carried forward into HoloPatcher-era mod packaging 2 3. For older standalone GFF-editing culture, see the K-GFF thread 4.
- Decide whether each file is global (
override/) or module-scoped (modules/*.mod). - Use patcher-based merges for shared 2DA, TLK, and GFF content instead of shipping blind overwrites.
- Install into a clean test setup at least once from scratch.
- Test reinstall, uninstall/restore, and any mutually exclusive installer options.
- Document load order or incompatibilities when they still matter after patching.
- Check mobile/iOS casing if you intend to support it.
The game resolves resources in a fixed order. Understanding this order is essential for knowing where to place your mod's files and how conflicts arise.
In summary, the engine checks (1) the override folder (override/), (2) currently loaded MOD/ERF files such as modules in the Modules folder, (3) the current save game when in game, and then (4) KEY/BIF vanilla game data. Override and MOD content therefore take precedence over vanilla BIFs. See Concepts for the full sequence and how the resource manager satisfies requests; Container-Formats#key documents the KEY binary layout.
Use these placement rules:
- Use the override folder for most standalone mod content: textures, models, scripts, 2DA edits, dialog TLK changes, and GFF-based resources such as creatures, items, and placeables that are meant to replace or add to the base game globally. Files in
override/are loaded for every module and save. If your mod only adds or replaces a few files and does not need an installer, placing files directly inoverride/is standard. - Use MOD files (ERF) for module-specific content: area GFFs (ARE, GIT, and similar files), module-specific 2DAs or scripts, and anything that should load only when that module is played. TSLPatcher and HoloPatcher can write into both override and MOD. When building a MOD, your installer typically packs resources into a
.modfile and optionally copies shared files such as global 2DAs to override. Putting everything in override can work, but it increases the chance of overwriting or being overwritten by other mods; merging 2DAs via TSLPatcher reduces conflicts. - Follow the long-standing community convention of using
overridefor global replacements and module containers for module-specific content, with patchers aimed at the game root directory rather thanoverrideby itself so they can resolve all affected paths correctly 1 2.
Some data, such as certain creature or trigger state, is stored in save games. Changing those after a save is loaded may require a new playthrough or a script-based workaround. See "Removing GFF structs" below for script-based removal of instances when patchers cannot delete structs.
Point TSLPatcher at your main game installation directory instead of the override folder itself. That means the folder containing swkotor.exe or swkotor2.exe and the override folder. If you use Steam Workshop content such as TSLRCM, point the patcher at that Workshop folder when installing mods that need to merge with Workshop content. This ensures the patcher finds the correct 2DA, TLK, and GFF files and can merge rather than overwrite when configured to do so.
Real-world install guides and release pages consistently surface the same failure modes. Deadly Stream's K1CP release notes warn users to extract archives before running the installer and to avoid protected install locations when possible 3, and the r/kotor install guides warn that users with multiple live installs can confuse legacy auto-detect patchers 5. If your mod still ships classic TSLPatcher, document the intended target path explicitly and assume users may have Steam, GOG, Workshop, or backup copies side by side.
When multiple mods change the same 2DA, such as spells.2da or appearance.2da, raw overwrites would make only one mod's changes take effect. TSLPatcher can merge 2DA changes by adding or updating rows based on your 2DAList instructions so that several mods' additions coexist. Configure your mod's 2DAList, and optionally TLKList for string references, so that your rows are appended or matched by key column; the patcher then merges instead of replacing the whole file. See TSLPatcher 2DAList Syntax for exact syntax.
Similarly, TLKList allows adding or changing string entries in dialog.tlk without wiping the rest of the file. Use TLKList so that your mod's new StrRefs are appended and existing entries are updated only where intended. See TSLPatcher TLKList Syntax.
TSLPatcher GFFList can add or edit structs and fields in GFF files such as creature templates, module instances, inventories, and dialog data. It cannot remove structs; for that, use a script-based workaround described below or a workflow that deliberately rebuilds the target data.
Many modders use GUI tools to edit GFF and 2DA files when building mods manually or to inspect game data. Community knowledge (including LucasForums archives) frequently references:
- KotOR Tool provides a graphical interface for editing GFF and 2DA files, reducing the need to hand-edit binary formats. It can open BIF/ERF and edit creatures, items, 2DAs, and other resources.
- K-GFF is a standalone GFF editor with search, useful when you need field-level control while inspecting or editing GFF structures.
- GFF2XML / XML2GFF by tk102 are command-line utilities that convert GFF to XML and back, enabling diff-friendly and scriptable edits.
- For 2DA editing, Werf's editor was historically used to convert between compiled (v2.b) and text (v2.0) 2DA formats. Modern tools such as KotOR Tool and Holocron Toolset often handle 2DA internally. When merging 2DAs by hand, copy unique rows from one file into the other and update any references in your mod's GFF or scripts that point to row indices.
When combining multiple mods that touch the same 2DA, manual merging involves copying unique lines from one 2DA into the other and updating references in affected files; using TSLPatcher 2DAList is preferred when possible for repeatable installs.
One recurring limitation is that patchers can add or edit GFF content but cannot surgically delete a struct from an existing file. If you need to remove a GIT instance or suppress stock content for compatibility, you have to solve it another way.
Removing GFF structs when patchers cannot
Neither HoloPatcher nor TSLPatcher supports removing a GFF struct, such as deleting a GIT instance from an ARE or other GFF. If you need to remove placeables, creatures, or other instances for compatibility or design reasons, you must do it at runtime with a script: locate the objects and destroy them, and run that script once, for example gated by a local variable, so it does not run every frame or conflict with other mods.
Example scenario: remove more than 20 box placeables, remove 2 of 4 droids, and move the remaining 2 droids to new positions. Several possible solutions were tested, including GetFirstObjectInShape / GetNextObjectInShape and GetNearestObjectToLocation. Those approaches worked well with a small number of objects but sometimes left a few of the box placeables on the level, so the final workaround used GetObjectByTag / DestroyObject.
The practical workflow is:
-
Write a script that finds and removes the relevant objects at runtime.
-
Gate that script with a local variable so it only runs once.
-
Attach it at a point that fits the module flow, while minimizing compatibility damage.
In a small encounter it may be simpler to remove all four droids and create two new ones in the desired positions. If you instead need to remove only some instances from a much larger population, such as 5 out of 30 copies of an object, then GetFirstObjectInShape/GetNextObjectInShape may be the more practical approach.
Example script:
void main(){
if(GetLocalNumber( OBJECT_SELF, 32 ) != 150){
DestroyObjectsByTag("objectTag");
//DestroyPlaceablesAndCreaturesInArea(oLoc1, SHAPE_CUBE, 36.0f);
SetLocalNumber(OBJECT_SELF, 32, 150);
}
}
void DestroyObjectsByTag(string tag){
int i = 0;
object oPlc = GetObjectByTag(tag);
while(GetIsObjectValid(oPlc)){
DestroyObject(oPlc);
i++;
oPlc = GetObjectByTag(tag, i);
}
}
void DestroyPlaceablesAndCreaturesInArea(location oLoc1, int nShape, float areaSize){
object oPlc = GetFirstObjectInShape(nShape, areaSize, oLoc1, 0, OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE);
while (GetIsObjectValid(oPlc))
{
DestroyObject(oPlc);
oPlc = GetNextObjectInShape(nShape, areaSize, oLoc1, 0, OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE);
}
}-
For textures, especially TGA versus TPC install order, players often debate override precedence. For workflow context, see Deadly Stream — mod installation order and TGA vs TPC. Format SSOT remains:
-
Document install order when it still matters, because many mods depend on override and MOD load order. When using TSLPatcher or HoloPatcher, merging 2DA and TLK reduces order sensitivity for those files.
-
Test on a clean game install or a known-good backup so conflicts are attributable to your mod or to a specific combination.
-
Use the patcher's backup and restore flow, such as HoloPatcher's "Uninstall Mod/Restore Backup", before reinstalling or switching options. Installing twice without reverting can duplicate 2DA rows or TLK entries and cause crashes. See Installing Mods with HoloPatcher.
-
State clearly in your readme that users must extract the release archive before running any installer. This warning appears often because people still launch patchers from inside archive viewers and then report broken installs.
-
If your audience includes Windows users, mention that
Program Filesand similarly protected locations can block writes or confuse less technical users. Community support threads repeatedly show that a direct, explicit destination path in the instructions prevents a large share of install failures. -
For mobile targets such as iOS, file names must be lowercase. Use HoloPatcher's "Fix iOS Case Sensitivity" when targeting mobile. See Installing Mods with HoloPatcher.
The ExclusiveColumn field is perfect for this situation. Here's an example where we know the 'label' and we want to simply store the RowIndex.
[genericdoors.2da]
CopyRow0=copy_row_0
CopyRow1=copy_row_1
CopyRow2=copy_row_2
CopyRow3=copy_row_3
CopyRow4=copy_row_4
[copy_row_0]
ExclusiveColumn=label
LabelIndex=Hammerhead2
label=Hammerhead2
2DAMEMORY114=RowIndex
[copy_row_1]
ExclusiveColumn=label
LabelIndex=Hammerhead3
label=Hammerhead3
2DAMEMORY115=RowIndex
[copy_row_2]
ExclusiveColumn=label
LabelIndex=SleheyronDoor1
label=SleheyronDoor1
2DAMEMORY116=RowIndex
[copy_row_3]
ExclusiveColumn=label
LabelIndex=SleheyronDoor2
label=SleheyronDoor2
2DAMEMORY117=RowIndex
[copy_row_4]
ExclusiveColumn=label
LabelIndex=YavinHgrDoor1
label=YavinHgrDoor1
2DAMEMORY118=RowIndex- HoloPatcher README for Mod Developers -- Mod packaging and testing workflow
- TSLPatcher's Official Readme -- Preserved legacy installer reference
- Concepts -- Resource resolution order and override precedence
- GFF File Format -- Binary GFF container and typed resource overview
- TSLPatcher 2DAList Syntax -- 2DA merge syntax
- TSLPatcher GFFList Syntax -- GFF field patching syntax
- Installing Mods with HoloPatcher -- End-user installation guide
- NSS File Format -- Scripting reference