S.T.A.L.K.E.R. 2 Zona Configurator is a tool to apply JSON based mods to S.T.A.L.K.E.R. 2: Heart of Chernobyl game.
The application's primary goal is to simplify the process of applying mods to the game. The main disadvantages of the current modding system are:
- One mod can overwrite the changes of another mod.
- After every game update, mod makers have to update their mods to work with the new version of the game.
This tool solves these problems by extracting fresh config files from the game and packing all the mods into a single PAK file.
- Support mods in JSON format
- Automatically unpack necessary config files from the game
- Eliminate the mods conflicts by packing them into a single PAK file
- You will be warned if JSON mods have conflicts with each other
- Generate a single PAK file with all the mods
- Copy additional files like assets to the ~mods folder
- Generate a changelog file
- Generate a diff file with the changes made by the JSON mods in a format of your choice
- Generate a diif file with the changes in pak mods
- Download the latest release from the Releases page.
- Extract the archive to a folder of your choice.
- Create the file
appsettings.Mine.json(if it's not created) in the root folder of the application with the following content:
{
"AppConfig": {
"Game": {
"GamePath": "D:\\Steam\\steamapps\\common\\S.T.A.L.K.E.R. 2 Heart of Chornobyl"
}
}
} GamePath - path to the game folder. All the backslashes should be escaped with another backslash. So you should write \\ instead of \.
Notes:
- You can use the
appsettings.jsonfile as a template and update any value you want inappsettings.Mine.json. The application will use the values fromappsettings.Mine.jsonif they are present.
- Place your mods in the
modsfolder. It should be in JSON format, not PAK files! Some mods can be found here. - Run the application by double-clicking on the
S2ZonaConfigurator.exefile.
You will see the output in the console. If everything is successful, the output should be something like this:
╔═════════════════════════════════════════════════════════════════════════════════════════════════════════
║ Processing Summary
╠═════════════════════════════════════════════════════════════════════════════════════════════════════════
║ Total Mods Processed: 10
║ Successful: 10
║ Failed: 0
╚═════════════════════════════════════════════════════════════════════════════════════════════════════════
╔═════════════════════════════════════════════════════════════════════════════════════════════════════════
║ Pak Creation Status
╠═════════════════════════════════════════════════════════════════════════════════════════════════════════
║ Pak Path: D:\Games\Stalker2Dir\Stalker2\Content\Paks\~mods\ZonaBundle.pak
║ Status: Successfully created
╚═════════════════════════════════════════════════════════════════════════════════════════════════════════
That's it! You can now run the game with the mods applied.
Just some more notes on the usage:
- See
appsettings.jsonfor more configuration options. It can be overridden byappsettings.Mine.json. - You can turn off a mod by adding
$at the beginning of the mod file name. For example,$super_mod.jsonwill be ignored - Mods can be placed in subfolders of the
modsfolder. The application will process them all - The application produces a changelog file in the
~modsfolder. You can turn it off by settingOutputChangelogFiletofalsein theappsettings.Mine.jsonfile. - The application has a feature to detect mods conflicts and it's turned on by default. You can turn it off by setting
DetectModConflictstofalsein theappsettings.Mine.jsonfile. - The application can delete old mods before generating new ones. You can turn it on by setting
DeleteOldModstotruein theappsettings.Mine.jsonfile. It will delete all the files in the~modsfolder starting with value specified inOutputPakName. The default value isZonaBundle. It can be useful if you use mods with additional files like assets.
The application can generate a diff file with the changes made by the mods. You can turn it on by setting GenerateDiffReport to true in the appsettings.Mine.json file.
Additionally, you can change:
- The diff output format in
DiffConfig->DiffFormatin theappsettings.Mine.jsonfile. Possible values areGitHubMarkdown,SideBySideMarkdown,Unified,HTML. - The content size of the
difffile inDiffConfig->ContextLinesin theappsettings.Mine.jsonfile. The default value is3. This will keep thedifffile small and informative. But you can set it to-1to include the whole file content.
The application currently supports two modes:
Main. This is the default mode. It will process all the mods in themodsfolder and create a PAK file.PakModsDiff. This mode is used to compare the mods in thepak__modsfolder with the game files. It will generate adifffile with the changes made by the mods. It can be useful to see the changes made by the pak mods. If there is a new file in the pak mod (like some asset), the app will try to search through all the files in the game's Paks folder, so it will take some time.
You can change the mode in the appsettings.Mine.json file AppConfig->Options->AppMode property. pak__mods directory can be changed in the AppConfig->Paths->PakModsDirectory property.
If anyone wants to create a mod supported by this application, please follow the instructions below.
Always verify that the changes made by the application are correct. Use any diff tool or the built-in diff feature to compare the original and modified files.
I recommend turning off CleanWorkDirectory in the appsettings.Mine.json file to keep the modified files for further analysis and turning on GenerateDiffReport to generate a diff file.`
An example of a simple mod can be found in the mods/$Example.json. Aloso check the mods in the S2ZonaMods repository.
The mod should be in JSON format. Here is an example of a simple mod:
{
"version": "1.0",
"description": "The day is 2 hours of real time",
"author": "JakeKingsly",
"actions": [
{
"type": "Modify",
"file": "Stalker2/Content/GameLite/GameData/CoreVariables.cfg",
"path": "DefaultConfig::RealToGameTimeCoef",
"value": 12,
"defaultValue": 24
}
]
}This mod will change the RealToGameTimeCoef value in the CoreVariables.cfg file to 12.
The schema of the mod file lokks like that:
version- version of the mod (required)description- description of the mod (required)author- author of the mod (optional)actions- list of actions to apply (required)type- type of the action (required)file- path to the file to modify (required). It can be specified only in the first action of the mod. If the file is used in multiple actions, then thefileproperty can be omitted in the next actions. The application will use the file path from the first action. It can be specified again if the file path should be changed aftern n actions.path- path to the value/structure to modify (required), but optional for theReplaceactiondefaultValue- default value (optional)comment- comment for the action (optional)
Here is an example of re-using the file property in multiple actions and resetting it to Stalker2/Content/GameLite/GameData/EffectPrototypes.cfg:
{
"version": "1.0",
"description": "Test file path",
"author": "",
"actions": [
{
"type": "Modify",
"file": "Stalker2/Content/GameLite/GameData/CoreVariables.cfg",
"path": "DefaultConfig::RealToGameTimeCoef",
"value": 12,
"defaultValue": 24
},
{
"type": "Modify",
"path": "DefaultConfig::StartYear",
"value": 2024
},
{
"type": "RemoveStruct",
"file": "Stalker2/Content/GameLite/GameData/EffectPrototypes.cfg",
"path": "RadiationMechanics::MechanicsEffect::ConditionEffects::LightRadiation::ApplicableEffects"
}
]
}There is a special case for path property. In the config files, structures like [*] can be presented in the same parent structure multiple times. For instance:
GeneralNPC_Neutral_Stormtrooper_ItemGenerator : struct.begin {refurl=../ItemGeneratorPrototypes.cfg;refkey=[0]}
SID = GeneralNPC_Neutral_Stormtrooper_ItemGenerator
RefreshTime = 1d
ItemGenerator : struct.begin
[*] : struct.begin
Category = EItemGenerationCategory::SubItemGenerator
PossibleItems : struct.begin
[0] : struct.begin
ItemGeneratorPrototypeSID = GeneralNPC_Neutral_WeaponPistol
Chance = 0.4
struct.end
[1] : struct.begin
ItemGeneratorPrototypeSID = GeneralNPC_Consumables_Stormtrooper
Chance = 1
struct.end
struct.end
struct.end
[*] : struct.begin
...
struct.end
[*] : struct.begin
...
struct.end
struct.end
struct.end
In this case, the path property should be specified like this:
{
"type": "Modify",
"file": "Stalker2/Content/GameLite/GameData/ItemGeneratorPrototypes/NPC_ItemGenerators.cfg",
"path": "GeneralNPC_Neutral_Stormtrooper_ItemGenerator::ItemGenerator::[*]:0::PossibleItems::[1]::Chance",
"value": "3"
}So because we can't distinguish structures [*] by name, we should add the index of the structure in the path. Indexes are zero-based.
Depending on the type of the action, the mod can have different properties. Here are the possible types of actions:
- Modify
- Add
- RemoveLine
- RemoveStruct
- AddStruct
- Replace
Changes the value of the property
value- new value of the property (optional ifvaluesis specified). Example: "value": 12values- one can specify apathto the sturcture and then specify the values to change in that structure, so that wil simplify the mod file. Example:
{
"type": "Modify",
"file": "Stalker2/Content/GameLite/GameData/CoreVariables.cfg",
"path": "DefaultConfig",
"values": {
"ALifeGridUpdateDelay": 6.0,
"StartYear": 2024
}
}Adds a new property to the structure
value- value of the new property (required). Thepathshould point to the parent structure where the new property should be added and end with the property name. Example:
{
"type": "Add",
"file": "Stalker2/Content/GameLite/GameData/CoreVariables.cfg",
"path": "DefaultConfig::NewStuff",
"value": 1.6
}This will add a new property NewStuff with the value 1.6 to the DefaultConfig structure.
Removes a line from the file
path- one needs to specify the path to the line to remove. Example:
{
"type": "RemoveLine",
"file": "Stalker2/Content/GameLite/GameData/CoreVariables.cfg",
"path": "DefaultConfig::StartMinute"
}This will remove the StartMinute property from the DefaultConfig structure.
Removes a structure from the file
path- one needs to specify the path to the structure to remove. Example:
{
"type": "RemoveStruct",
"file": "Stalker2/Content/GameLite/GameData/EffectPrototypes.cfg",
"path": "RadiationMechanics::MechanicsEffect::ConditionEffects::LightRadiation::ApplicableEffects"
}This will remove the ApplicableEffects structure from the LightRadiation structure.
Adds a new structure to the file. There are different ways to add a new structure:
- Add one structure at a time:
value- value of the new structure (required). Thepathshould point to the parent structure where the new structure should be added and end with the new structure name.valueshould be a JSON object. Example:This will add a new array structure element to the{ "type": "AddStruct", "file": "Stalker2/Content/GameLite/GameData/ItemGeneratorPrototypes/Gamepass_ItemGenerators.cfg", "path": "GamePass_Stash_ItemGenerator_Cheap::ItemGenerator::[0]::PossibleItems::[5]", "value": { "ItemPrototypeSID": "EArtifactFlash", "Chance": 0.0018, "MinCount": 1, "MaxCount": 1 } }PossibleItemswith index 5.
- Add multiple structures at once with the same parent. Optimized for arrays:
-
structures- list of structures to add (required). Thepathshould point to the parent structure where the new structures should be added. All the structures in the list will receive automatically generated indexes based on the last index in the parent structure. Example:{ "type": "AddStruct", "file": "Stalker2/Content/GameLite/GameData/ItemGeneratorPrototypes/Gamepass_ItemGenerators.cfg", "path": "GamePass_Stash_ItemGenerator_Cheap::ItemGenerator::[1]::PossibleItems", "structures": [ { "ItemPrototypeSID": "EArtifactFlash", "Chance": 0.0018, "MinCount": 1, "MaxCount": 1 }, { "ItemPrototypeSID": "EArtifactSnowflake", "Chance": 0.0018, "MinCount": 1, "MaxCount": 1 }, ] }This will add two new array structure elements to the
PossibleItemswith automatically generated indexes. The result will be something like this:[1] : struct.begin Category = EItemGenerationCategory::Consumable bAllowSameCategoryGeneration = true PossibleItems : struct.begin ..... // Last element in the array [4] : struct.begin ItemPrototypeSID = Bandage Weight = 3 MinCount = 1 MaxCount = 1 struct.end // New elements [5] : struct.begin ItemPrototypeSID = EArtifactFlash Chance = 0.0018 MinCount = 1 MaxCount = 1 struct.end [6] : struct.begin ItemPrototypeSID = EArtifactSnowflake Chance = 0.0018 MinCount = 1 MaxCount = 1 struct.end
- Add multiple structures at once with the same parent. Named structures:
This is similar to the previous one, but the structures will have names instead of indexes.
structures- list of structures to add (required). Thepathshould point to the parent structure where the new structures should be added. The names of the structures are specified in the JSON object. Example:This will add new structures with names{ "type": "AddStruct", "file": "Stalker2/Content/GameLite/GameData/ItemGeneratorPrototypes/Gamepass_ItemGenerators.cfg", "path": "GamePass_Stash_ItemGenerator_Cheap::ItemGenerator::[1]", "structures": [ { "ItemPrototypeFactor": { "ItemTag": "EItemTag::Helmet", "Factor": 1.5 } }, { "ItemPrototypeFilter": { "ItemTag": "EItemTag::Weapon", "Filter": true } } ] }ItemPrototypeFactorandItemPrototypeFilterto theItemGeneratorwith index 1.
Can be used to replace a certain substring in the file. This method should be used with caution. Choose the values to replace wisely, so it wont replace something that should not be replaced. There are two ways to use it:
- Replace by a single value:
value- is a JSON object with two properties:oldandnew.oldis the value to find and replace andnewis the value to replace with. Example:{ "type": "Replace", "file": "Stalker2/Content/GameLite/GameData/ItemPrototypes/ArtifactPrototypes.cfg", "value": { "old": "EEffectDisplayType::EffectLevel", "new": "EEffectDisplayType::Value" } "comment": "Replace all EEffectDisplayType::EffectLevel with EEffectDisplayType::Value" }
- Replace using regex:
value- is a JSON object with two properties:oldandnew.oldis the regex pattern to find and replace andnewis the value to replace with.isRegex- should be set totrueto use regex. Example:This will use a regex pattern to find all the{ "type": "Replace", "file": "Stalker2/Content/GameLite/GameData/ItemPrototypes/QuestItemPrototypes.cfg", "path": "", "value": { "old": "Weight\\s*=\\s*[0-9]*\\.?[0-9]+", "new": "Weight = 0" }, "isRegex": true, "comment": "Replace all Weight values with 0" }Weightvalues and replace them with0.
If your mod requires additional things like textures, sounds, etc., packed in .ucas, and .utoc files, you have two options to include them in the mod:
- Create a folder with the same name as the mod file and place all the necessary files there. The application will copy all the files from the folder to the
~modsfolder. - Create a zip archive with the same name as the mod file and place all the necessary files there. The application will extract all the files from the archive to the
~modsfolder.
The important thing is that a JSON mod file is necessary even if it doesn't change any config files. Just create an JSON file like this:
{
"version": "1.0",
"description": "This mod only copies assets",
"author": "Me",
"actions": []
}Keep in mind, that it won't pack the files into the .pak, .ucas, .utoc files. It will just copy them to the ~mods folder. But that's fine because we can't merge the assets files.