2.0.5 update introduced binary format change. Attached the new one
QuestPrototypes.cfg.bin.zip
binary dump for your consideration (the asset is 334 bytes, Stalker2/Content/GameLite/DLCGameData/Ultimate/QuestPrototypes.cfg.bin):
0000000000: 02 00 00 00 08 00 00 00 │ 00 00 00 00 0F 00 00 00 ☻ ◘ ☼
0000000010: 44 4C 43 30 30 5F 55 6C │ 74 69 6D 61 74 65 00 38 DLC00_Ultimate 8
0000000020: 00 00 00 44 4C 43 47 61 │ 6D 65 44 61 74 61 2F 55 DLCGameData/U
0000000030: 6C 74 69 6D 61 74 65 2F │ 51 75 65 73 74 50 72 6F ltimate/QuestPro
0000000040: 74 6F 74 79 70 65 73 2F │ 44 4C 43 30 30 5F 55 6C totypes/DLC00_Ul
0000000050: 74 69 6D 61 74 65 2E 63 │ 66 67 00 04 00 00 00 53 timate.cfg ♦ S
0000000060: 49 44 00 04 00 00 00 44 │ 4C 43 00 09 00 00 00 55 ID ♦ DLC ○ U
0000000070: 6C 74 69 6D 61 74 65 00 │ 0B 00 00 00 53 51 31 33 ltimate ♂ SQ13
0000000080: 5F 41 75 64 69 6F 00 34 │ 00 00 00 44 4C 43 47 61 _Audio 4 DLCGa
0000000090: 6D 65 44 61 74 61 2F 55 │ 6C 74 69 6D 61 74 65 2F meData/Ultimate/
00000000A0: 51 75 65 73 74 50 72 6F │ 74 6F 74 79 70 65 73 2F QuestPrototypes/
00000000B0: 53 51 31 33 5F 41 75 64 │ 69 6F 2E 63 66 67 00 00 SQ13_Audio.cfg
00000000C0: 00 00 00 00 00 00 00 00 │ 00 00 00 00 02 00 00 00 ☻
00000000D0: 01 00 00 00 01 00 00 00 │ 00 00 00 00 02 00 00 00 ☺ ☺ ☻
00000000E0: 00 02 00 00 00 03 00 00 │ 00 03 00 00 00 01 00 00 ☻ ♥ ♥ ☺
00000000F0: 00 00 00 00 00 00 00 00 │ 00 00 04 00 00 00 04 00 ♦ ♦
0000000100: 00 00 05 00 00 00 00 00 │ 00 00 00 00 00 00 00 06 ♣ ♠
0000000110: 00 00 00 06 00 00 00 00 │ 00 00 00 07 00 00 00 00 ♠ •
0000000120: 02 00 00 00 03 00 00 00 │ 03 00 00 00 06 00 00 00 ☻ ♥ ♥ ♠
0000000130: 00 00 00 00 00 00 00 00 │ 00 04 00 00 00 04 00 00 ♦ ♦
0000000140: 00 05 00 00 00 00 00 00 │ 00 00 00 00 00 00 ♣
last update (2.0.4) it was parsed into something like:
DLC00_Ultimate : struct.begin
SID = DLC00_Ultimate
DLC = Ultimate
struct.end
SQ13_Audio : struct.begin
SID = SQ13_Audio
DLC = Ultimate
struct.end
It's currently not parsing: Error: Unexpected end of binary cfg data at BinaryCursor.ensureAvailable
I don't have the old bin cfg version unfortunately, that would need redownloading 160 gb.
I tried claude free it says this, but I am totally out of tokens to update the code:
In update 2.0.5, the .bin configuration format was streamlined by removing the intermediate type/flag index from key-value field entries.
Previously, key-value pairs stored 3 or 4 string pool indices ([key_idx, type_idx, value_idx]) and nested struct headers stored 2 indices.
In version 2.0.5, key-value fields store 2 indices ([key_idx, value_idx]) while nested struct headers store 1 index ([struct_name_idx]).
Test code to reproduce:
import { Struct } from 's2cfgtojson';
import * as fs from 'fs';
const config = new Struct();
const name = String.raw`C:\Temp\Exports\Stalker2\Content\GameLite\DLCGameData\Ultimate\QuestPrototypes.cfg.bin`
const parsed = Struct.fromBinary(fs.readFileSync(name));
fs.writeFileSync('out.json', JSON.stringify(parsed, null, 2)); // to JSON
fs.writeFileSync('out.cfg', parsed.toString(parsed)); // to CFG
Upd. I have a working solution, but it's for bin2cfg.py.
click to expand
--- bin2cfg.py Fri Sep 11 14:34:16 2026
+++ bin2cfg2.py Fri Sep 11 16:32:22 2026
@@ -6,6 +6,7 @@
* Based on JSON Converter by sdwvit: https://github.com/sdwvit/S2CfgToJSON
* Binary reader PR by thexii: https://github.com/sdwvit/S2CfgToJSON/pull/1
+* Updated for Version 2.0.5 binary format changes
Usage:
python3 bin2cfg.py input.cfg.bin [output.cfg]
@@ -19,11 +20,6 @@
(recursively) is converted. By default each converted ".cfg" file is
written right next to its source ".cfg.bin" file. Pass -o/--output-dir
to instead mirror the directory tree under a separate output root.
-
-Updates:
-
-* 2026-08-26: Preserved numeric literals, fixed float infinity, added progress
-
"""
from __future__ import annotations
@@ -340,8 +336,8 @@
return ""
-def read_binary_header(reader: BinaryCursor) -> List[str]:
- reader.read_uint32() # version
+def read_binary_header(reader: BinaryCursor) -> Tuple[int, List[str]]:
+ version = reader.read_uint32()
string_count = reader.read_int32()
string_pool: List[str] = []
reader.read_uint32() # reserved
@@ -364,14 +360,21 @@
else:
string_pool.append("")
- return string_pool
+ return version, string_pool
-def skip_post_pool_padding(reader: BinaryCursor) -> None:
- if reader.position + 9 <= reader.length:
- reader.read_uint32()
- reader.read_uint32()
- reader.read_byte()
+def skip_post_pool_padding(reader: BinaryCursor, version: int) -> None:
+ if version >= 2:
+ if reader.position + 13 <= reader.length:
+ reader.read_uint32()
+ reader.read_uint32()
+ reader.read_uint32()
+ reader.read_byte()
+ else:
+ if reader.position + 9 <= reader.length:
+ reader.read_uint32()
+ reader.read_uint32()
+ reader.read_byte()
class BinaryBlock:
@@ -383,18 +386,33 @@
self.position = position
-def read_binary_cfg_config(reader: BinaryCursor, pool_size: int) -> BinaryBlock:
+def read_binary_cfg_config(reader: BinaryCursor, pool_size: int, version: int) -> BinaryBlock:
position = reader.position
values: List[int] = []
- while reader.position + 4 <= reader.length:
- value = reader.read_int32()
- if value <= 0:
- break
- if value > pool_size:
- reader.position -= 4
- break
- values.append(value)
+ if version >= 2:
+ # 2.0.5 config block format
+ name_idx = reader.read_int32()
+ type_idx = reader.read_int32()
+ val_idx = reader.read_int32()
+
+ values = [name_idx, type_idx, val_idx]
+
+ if val_idx == 0:
+ values.append(reader.read_int32())
+ else:
+ values.append(reader.read_int32())
+ values.append(reader.read_int32())
+ else:
+ # Pre-2.0.5 config block format
+ while reader.position + 4 <= reader.length:
+ value = reader.read_int32()
+ if value <= 0:
+ break
+ if value > pool_size:
+ reader.position -= 4
+ break
+ values.append(value)
last_byte = reader.read_byte()
return BinaryBlock(values, last_byte, position)
@@ -414,9 +432,15 @@
return parent_name, ref_path
-def read_binary_struct(reader: BinaryCursor, string_pool: List[str]) -> Node:
- block = read_binary_cfg_config(reader, len(string_pool))
- name = get_binary_string(block.values[1], string_pool) if len(block.values) > 1 else ""
+def read_binary_struct(reader: BinaryCursor, string_pool: List[str], version: int) -> Node:
+ block = read_binary_cfg_config(reader, len(string_pool), version)
+
+ if version >= 2:
+ name_idx = block.values[0] if len(block.values) > 0 else 0
+ else:
+ name_idx = block.values[1] if len(block.values) > 1 else 0
+
+ name = get_binary_string(name_idx, string_pool) if name_idx else ""
node = Node(name)
if block.last_byte > 0 and block.last_byte in (1, 5, 7):
@@ -428,22 +452,33 @@
fields_count = reader.read_int32()
for current_field in range(fields_count):
- field_block = read_binary_cfg_config(reader, len(string_pool))
- if len(field_block.values) <= 1:
- continue
+ field_block = read_binary_cfg_config(reader, len(string_pool), version)
+
+ if version >= 2:
+ if len(field_block.values) < 3:
+ continue
+ field_name = get_binary_string(field_block.values[0], string_pool)
+ n_values = len(field_block.values)
+ is_struct = (n_values == 4)
+ has_value = (n_values == 5)
+ else:
+ if len(field_block.values) <= 1:
+ continue
+ field_name = get_binary_string(field_block.values[0], string_pool)
+ n_values = len(field_block.values)
+ is_struct = (n_values == 2)
+ has_value = (n_values in (3, 4))
- field_name = get_binary_string(field_block.values[0], string_pool)
- n_values = len(field_block.values)
- if n_values == 2:
+ if is_struct:
reader.position = field_block.position
- nested = read_binary_struct(reader, string_pool)
+ nested = read_binary_struct(reader, string_pool, version)
assign_field(
node,
field_name,
"" if is_empty_nested_struct(nested) else nested,
current_field,
)
- elif n_values in (3, 4):
+ elif has_value:
raw_value = get_binary_string(field_block.values[2], string_pool).strip()
assign_field(node, field_name, parse_value(raw_value), current_field, raw=raw_value)
@@ -455,8 +490,8 @@
if reader.length < 12:
return []
- string_pool = read_binary_header(reader)
- skip_post_pool_padding(reader)
+ version, string_pool = read_binary_header(reader)
+ skip_post_pool_padding(reader, version)
roots: List[Node] = []
while reader.position < reader.length:
@@ -464,7 +499,7 @@
break
childs_count = reader.read_int32()
for _ in range(childs_count):
- root = read_binary_struct(reader, string_pool)
+ root = read_binary_struct(reader, string_pool, version)
root.__internal__.isRoot = True
roots.append(root)
Already updated https://joric.github.io/stalker with it. Haven't tested it for too long. Seems to replace [*] with numbers and repeat inherited properties, e.g. in BarbedWirePrototypes.cfg , bit it's fine for me. I use it for DLC data which is not in the SDK.
2.0.5 update introduced binary format change. Attached the new one
QuestPrototypes.cfg.bin.zip
binary dump for your consideration (the asset is 334 bytes,
Stalker2/Content/GameLite/DLCGameData/Ultimate/QuestPrototypes.cfg.bin):last update (2.0.4) it was parsed into something like:
It's currently not parsing: Error: Unexpected end of binary cfg data at BinaryCursor.ensureAvailable
I don't have the old bin cfg version unfortunately, that would need redownloading 160 gb.
I tried claude free it says this, but I am totally out of tokens to update the code:
In update 2.0.5, the .bin configuration format was streamlined by removing the intermediate type/flag index from key-value field entries.
Previously, key-value pairs stored 3 or 4 string pool indices ([key_idx, type_idx, value_idx]) and nested struct headers stored 2 indices.
In version 2.0.5, key-value fields store 2 indices ([key_idx, value_idx]) while nested struct headers store 1 index ([struct_name_idx]).
Test code to reproduce:
Upd. I have a working solution, but it's for bin2cfg.py.
click to expand
Already updated https://joric.github.io/stalker with it. Haven't tested it for too long. Seems to replace [*] with numbers and repeat inherited properties, e.g. in
BarbedWirePrototypes.cfg, bit it's fine for me. I use it for DLC data which is not in the SDK.