Decoding STEP 7 Project File Format: S7-300/400 Block Internals

David Krause14 min read
S7-300SiemensTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Overview

Siemens STEP 7 v5.x stores S7-300 and S7-400 PLC projects as a directory tree of binary files inside an .s7p archive (or as an unzipped folder when "Save As → Reorganize" has been used). Unlike the XML-based TIA Portal projects, the classic STEP 7 v5 format relies on dBase III/IV tables (.dbf), memo fields (.dbt), and proprietary binary blobs. Engineers who need to automate mass-edits, generate documentation, audit project structure, or migrate between project tools must reverse-engineer this layout because Siemens publishes no formal specification.

This reference documents the practical internals of STEP 7 v5 project files based on field reverse-engineering: the on-disk directory hierarchy, the role of baustein.dbf and subblk.dbf, the MC7 bytecode blob stored in subblk.dbt, the meaning of the MC5CODE, SSBPART, and ADDINFO columns, the YDBs symbol table, and the chain that connects Station → CPU → Blocks. It also covers how AS-OS-Engineering handles cross-project block transfer and how the post-v5.5 block encryption interacts with file-level tooling.

A standalone reference implementation for reading this layout is the open-source enlyze/S7-Project-Explorer on GitHub, which opens an .s7p archive, walks the S7Proj tree, and exports variable lists. Siemens publishes a general STEP 7 v5.5 programming and configuration manual through the SIMATIC support portal, but the on-disk file format is documented only through community reverse-engineering.

Scope. This article covers STEP 7 v5.x project files for S7-300/S7-400 (classic). TIA Portal projects use a completely different XML-based format and are out of scope here.

Project Directory Layout

A typical STEP 7 v5.5 project expanded to disk looks like the tree below. The .s7p archive itself is just a ZIP container; renaming to .zip extracts the inner tree.

MyProject.s7p                       (archive index, optional ZIP)
MyProject/
┌─ s7proj.ndx                       (project index)
│─ Global/
│  │─ LanguageDB/
│  │─ SymbolTable/
│  │─ YDBs/                         (cross-project symbol database)
│─ Station1/
│  │─ s7hstatx.ndx                  (station index)
│  │─ IM151/                        (rack / IM folder)
│  │─ CPU315/
│  │  │─ s7hcpu.ndx                 (CPU index)
│  │  │─ S7Program/
│  │  │  │─ Blocks/                 ← blocks live here
│  │  │  │  │─ baustein.dbf
│  │  │  │  │─ baustein.dbt
│  │  │  │  │─ subblk.dbf
│  │  │  │  │─ subblk.dbt
│  │  │  │─ Sources/
│  │  │  │─ IM/

The block container is the Blocks folder inside S7Program. Every S7-300/400 CPU in the station has its own S7Program/Blocks tree. The hierarchy engineers most often need to walk is:

Station → Rack (IM/Slot folder) → CPU folder → S7Program → Blocks

Tools that need to enumerate all blocks must iterate stations, then racks, then CPUs, then enter S7Program/Blocks. The station index (s7hstatx.ndx) and the CPU index (s7hcpu.ndx) are themselves small B*tree-like indices whose records point at the matching folder names.

The dBase Layer

baustein.dbf

baustein.dbf is the block header table. One row per block in the CPU's program. The columns below are field-derived from reverse-engineering; Siemens does not publish the schema.

Column Type Meaning
BLKNAME C8 Block name, e.g. "OB1", "FB100", "DB200", "UDT5", "VAT1"
BLKTYPE C1 Block type code: 'O' OB, 'F' FB, 'C' FC, 'D' DB, 'T' UDT, 'V' VAT, 'I' SFB, 'J' SFC, 'X' SDB
BLKNUMBER N5 Block number
AUTHOR C8 Author string (Properties → Created By)
FAMILY C8 Family string (Properties → Family)
HEADER C8 Title (Properties → Header)
REVIS N2 Revision counter; increments on every save
CHG_DATE D8 Last change date (YYYYMMDD)
CHG_TIME C8 Last change time (HH:MM:SS)
SSBPART C1 Know-how-protection / encryption marker
ADDINFO C8 Packed additional info: encryption flag, active/passive, language
LENDBT N6 Length of memo payload in subblk.dbt
MEMOFREE N6 Free bytes in memo
MEMOPACK N6 Memo block packing factor (each block = 512 × MEMOPACK bytes)
CREATOR C8 Creator string

The dBase III file header lives at offset 0 (32 bytes), followed by 32-byte field descriptors, then an 0x0D terminator. Tools that write baustein.dbf files must preserve the field count in the header bytes 0x04–0x07 and the record count in 0x04–0x07 of the header; otherwise STEP 7 refuses to open the project.

subblk.dbf

subblk.dbf is the block body table. One row per section (also called sub-block). For most compiled blocks there is exactly one section. The body of the section is stored as a memo in subblk.dbt.

Column Type Meaning
BLKNAME C8 Block name (matches baustein)
BLKNUMBER N5 Block number
SUBBLKTYPE C1 Section type: 'C' code (MC7), 'I' interface, 'N' network comment, 'T' temp, 'D' DB body, 'S' static, 'B' instance DB
SUBBLKNUMBER N2 Section index (0001, 0002, …)
AUTHOR C8 Author
LANG C2 Language indicator: 1 STL/AWL, 2 LAD, 3 FBD
MC5CODE N6 Byte offset into subblk.dbt where the section body begins
SSBPART C1 Sub-section partition / protection flag
ADDINFO C8 Sub-section flags: encrypted, compressed, structure / instance variant, network offsets
OFFSET N6 Byte offset for body start
LENDBT N6 Section body length in bytes
MEMOFREE N6 Free memo bytes
MEMOPACK N6 Memo packing factor

The MC5CODE column is the pointer engineers most often need: it gives the byte offset inside subblk.dbt where the section body begins. Siemens labels the bytecode "MC5" or "MC7" interchangeably in different documents; the bytecode format itself is MC7.

MC7 Bytecode in subblk.dbt

subblk.dbt is a dBase III memo file. It is organized as blocks whose size equals MEMOPACK × 512 bytes (default 512). The MC7 bytecode for a single compiled STL block begins at the offset stored in MC5CODE and runs for the number of bytes given by LENDBT.

MC7 is a stack machine. Every instruction is one or two bytes. The first byte is the opcode; some opcodes consume a second byte that encodes operand size (bit/byte/word/dword) and addressing mode. The table below is the commonly observed opcode map. It is reverse-engineered, not published by Siemens, so treat it as field-derived.

Hex Mnemonic Description
0x00 NOP No operation
0x10 U AND
0x11 UN AND-NOT
0x20 O OR
0x30 X XOR
0x40 = Assign
0x50 L Load
0x51 T Transfer
0x70 CALL Block call (followed by FB/FC number)
0x71 CC Conditional call
0x80 JU Jump unconditional
0x81 JC Jump if RLO=1
0x90 S Set
0x91 R Reset
0xA0 NOT Negate RLO
0xB0 BE Block end
0xB1 BEB Block end conditional

The complete opcode set exceeds 200 entries. For most automation tasks (symbol export, block inventory, cross-reference generation) reading MC7 is unnecessary — the STL source in the Sources folder or the interface/parameter table can be parsed instead. The MC7 path matters only when generating executable code or building a disassembler.

Network Division Inside MC7

A single MC7 blob contains all networks concatenated; there is no per-network boundary in the bytecode. STEP 7 stores the network separator information outside the MC7 stream:

  • subblk.dbf rows of SUBBLKTYPE='N' carry the network title and comment text.
  • The ADDINFO column of the SUBBLKTYPE='C' (code) row contains the byte offsets of each network header inside the MC7 stream, packed as a sequence of little-endian 16-bit values.
  • The SSBPART flag bit 0x01 marks whether the next byte after the network header is the start of a new network.

When STEP 7 rebuilds the STL view from MC7, it reads these offsets, slices the bytecode at the network boundaries, formats each slice back into STL using the opcode table, and overlays the network comments from the 'N' rows.

Practical implication for tooling: to enumerate networks you must read all SUBBLKTYPE='N' rows for a given block and concatenate their ADDINFO payloads in SUBBLKNUMBER order. The MC7 itself does not embed the network separator. Skipping this step is the single most common bug in early ST-to-MC7 converters.

SSBPART and ADDINFO Decoding

These two columns hold the protection and structural state of a block.

SSBPART values observed in field projects:

Value Meaning
' ' (blank) Unprotected
'K' Know-how protected (legacy, reversible by editing)
'B' Strong block encryption (v5.5 SP2+)
'P' Password-locked but not encrypted

ADDINFO is a packed 8-byte field. Its layout for compiled blocks (SUBBLKTYPE='C') is approximately:

Byte 0  : Encryption flag (0x00 none, 0x10 KHP, 0x20 strong)
Byte 1  : Compile state   (0x00 source, 0x01 compiled, 0x02 inconsistent)
Byte 2  : Instance mode   (single / multi / reference)
Byte 3  : Reserved
Byte 4-7: Cross-compile ID / revision tag

The strong block encryption (post-v5.5 SP2) cannot be reversed from the file alone; the decryption key is derived from the CPU serial and the project password. Tools that strip know-how protection by rewriting SSBPART=' ' only work for the legacy KHP; modern block encryption must be removed from inside STEP 7 with the original password.

Field note. When you write baustein.dbf, never downgrade SSBPART='B' to blank — STEP 7 will mark every block as "inconsistent" and refuse to compile.

Symbol Storage (YDBs)

Symbols are not stored inside the block folder. They live in the global symbol database under the project's YDBs folder. The YDB format is a sequence of binary records keyed by symbol name and scope. Each record carries:

  • Symbol name (ASCII, null-terminated, max 24 chars)
  • Operand identifier (I, Q, M, DB, PI, PQ, C, T, …)
  • Byte address and bit address
  • Data type code (BOOL, INT, WORD, REAL, BYTE, …)
  • Scope flag (global / local / DB-bound)
  • Comment string
  • Timestamp and revision

Local symbols declared inside an FB/FC interface are stored with the block in subblk.dbf rows of SUBBLKTYPE='I' (interface: IN, OUT, IN_OUT, STAT) and SUBBLKTYPE='T' (TEMP). The YDBs folder is the source of truth for the global symbol table and the cross-references used by the editor.

Tools that build cross-reference tables should therefore read:

  1. Global symbols from YDBs/*.ydb
  2. Block-local symbols from subblk.dbf rows of type 'I' and 'T'
  3. Instance DB layout from subblk.dbf rows of type 'B'

Station → CPU → Blocks Chain

When iterating programmatically through a STEP 7 v5 project:

  1. Open the project root and read s7proj.ndx for the list of stations.
  2. For each station, read s7hstatx.ndx to find racks (IM modules).
  3. For each rack entry, descend into the rack folder; locate CPU folders by their module type (CPU 312, CPU 315-2 DP, CPU 414-3, …).
  4. Inside each CPU folder, open S7Program/Blocks and read baustein.dbf and subblk.dbf.
  5. Merge with YDBs to resolve symbol names.

Below is a Python sketch of the walker pattern used in tools like enlyze/S7-Project-Explorer:

import os, struct, dbfread

def walk_project(root):
    s7proj_ndx = os.path.join(root, "s7proj.ndx")
    for station in parse_ndx(s7proj_ndx):
        station_dir = os.path.join(root, station)
        stat_ndx = os.path.join(station_dir, "s7hstatx.ndx")
        for rack in parse_ndx(stat_ndx):
            rack_dir = os.path.join(station_dir, rack)
            for cpu in os.listdir(rack_dir):
                if not cpu.startswith("CPU"):
                    continue
                blocks_dir = os.path.join(rack_dir, cpu, "S7Program", "Blocks")
                if not os.path.isdir(blocks_dir):
                    continue
                table = dbfread.DBF(os.path.join(blocks_dir, "baustein.dbf"))
                for row in table:
                    yield row["BLKNAME"], blocks_dir

The s7hstatx.ndx and s7hcpu.ndx files use a compact B*tree on top of the same dBase structure; their record payload names the matching folder names that you walk to on disk.

AS-OS-Engineering and the Cross-Project Compiler

The AS-OS Compiler (Siemens option package for WinCC) transfers blocks between an AS (Automation Station, i.e. STEP 7) and an OS (Operator Station, i.e. WinCC). It does not reuse the on-disk project files directly; instead it packages blocks into a transfer format that contains:

  • Block interface signature (for change detection)
  • Compiled MC7 payload (referenced through MC5CODE / subblk.dbt offsets)
  • Symbol references resolved against the AS symbol table
  • Version stamps (REVIS field from baustein.dbf)

Implementing an AS-OS-compatible compiler therefore requires a complete reader for the layout above plus a writer that can produce a v5.5-compatible transfer container. Field efforts to reimplement this typically start by reusing the read path of an existing library and adding a writer that emits new baustein.dbf, subblk.dbf, and subblk.dbt files while preserving the dBase header byte layout.

STEP 7 v5.5 Specific Considerations

STEP 7 v5.5 introduced changes that affect file-level tooling:

  • Strong block encryption (SSBPART='B', ADDINFO byte 0 = 0x20). Older tools that only handle 'K' cannot unlock these blocks.
  • Newer UDT and DB layout with versioning fields added to baustein.dbf.
  • 64-bit Windows installer changes that affect ODBC / DAO access to .dbf files used by older third-party tools.
  • Enhanced symbol table features (multi-line comments, attributes) that add fields to the YDB record format.

When targeting v5.5, the most reliable approach is to read the .dbf files through the dBase III file format directly rather than going through ODBC, because Microsoft Access and 64-bit ODBC drivers no longer ship with the legacy 32-bit ISAM engine by default. The reference reader in enlyze/S7-Project-Explorer demonstrates a self-contained parser that avoids ODBC altogether.

Practical Tooling

Layer Library / Format Notes
.dbf read/write dbfpy, xBase4Py, dbfread Pure Python, no ODBC dependency
.dbt memo read Custom on top of dbf reader Memo block size = MEMOPACK × 512
.s7p archive Python zipfile .s7p is a plain ZIP container
STL/AWL text Regex + state machine Easier than parsing MC7
MC7 disassembly Reverse-engineered opcode table Incomplete, no Siemens spec
TIA Portal projects lxml / ElementTree TIA Portal uses XML + .bin blobs, a different format entirely

The most widely referenced reference implementation that puts the read path together end-to-end is enlyze/S7-Project-Explorer. It can open an .s7p archive, walk the Station → CPU → Blocks chain, parse baustein.dbf / subblk.dbf, and export variable lists to CSV — a useful baseline to test your own parser against.

Verification & Pitfalls

When you build a tool that reads or writes STEP 7 v5 project files, verify it against these acceptance criteria:

  1. Round-trip integrity — opening the saved project in STEP 7 v5.5 must report no differences versus the original.
  2. Block count — baustein.dbf row count must match the original.
  3. Block type and number — every row's BLKNAME and BLKNUMBER must be preserved.
  4. Memo size — LENDBT must equal the original byte count for every section.
  5. Compilation — STEP 7 must successfully recompile every block without an "inconsistent" warning.
  6. Symbol resolution — every block-local symbol must still resolve against YDBs.
  7. Encryption state — SSBPART and ADDINFO must match the original. Downgrading 'B' to blank breaks the project.
  8. Revision counters — REVIS will increment automatically inside STEP 7 on next save; do not preserve the old value.

Common pitfalls seen in field implementations:

  • dBase field types — 'M' (memo) pointer fields in baustein.dbf are 6 bytes on disk; preserve the on-disk width even if your in-memory representation is an int.
  • Memo block size — MEMOPACK=2 means 1024-byte memo blocks. Many implementations assume 512 and silently corrupt larger memos.
  • Network separators — they live in ADDINFO of the MC5CODE row, not in the MC7 stream.
  • Block encryption — strip attempts only work for the legacy KHP (SSBPART='K').
  • .s7p archive — it is a ZIP; renaming to .zip and extracting works, but inner filenames can be locale-sensitive German strings unless STEP 7 was installed with English locale.
  • 64-bit Windows — ODBC/DAO access to .dbf is unreliable; use a pure-Python dBase reader instead.
  • v5.5 SP2+ strong encryption — MC7 is encrypted with a key derived from the CPU serial; reading the bytecode requires the project password.
  • Missing 0x0D terminator — if you forget the terminator after the field descriptor block in the dbf header, STEP 7 rejects the file silently.

Where is the MC7 bytecode stored inside a STEP 7 v5 project?

The bytecode lives in subblk.dbt, the memo file paired with subblk.dbf. The starting byte offset for a given block section is stored in the MC5CODE column of the matching subblk.dbf row; the length is in LENDBT. The MC7 stream itself contains only code — no network separators or comments.

How are networks delimited inside the MC7 bytecode?

Networks are not delimited inside the MC7 stream itself. STEP 7 stores the per-network byte offsets in the ADDINFO column of the SUBBLKTYPE='C' row, plus the network title and comment in SUBBLKTYPE='N' rows. To re-derive networks you must combine the two, slicing the bytecode at the offsets and overlaying the titles.

What does the SSBPART column encode?

SSBPART carries the know-how-protection / block-encryption state. Common values are blank (unprotected), K (legacy KHP, reversible), B (strong block encryption added in v5.5 SP2), and P (password-locked). Pair it with ADDINFO byte 0 for the exact encryption mode: 0x00 none, 0x10 KHP, 0x20 strong.

Where are global symbols stored?

In the project's YDBs folder, as a sequence of binary records keyed by symbol name and scope. Block-local symbols live with the block in subblk.dbf rows of SUBBLKTYPE='I' (IN/OUT/IN_OUT/STAT) and 'T' (TEMP). Instance DB layouts live in rows of SUBBLKTYPE='B'.

Can a third-party tool strip know-how protection from a block?

Only the legacy KHP (SSBPART='K') is reversible by rewriting the column to blank and clearing ADDINFO byte 0 to 0x00. The strong block encryption introduced in STEP 7 v5.5 SP2 (SSBPART='B') cannot be removed from the file alone; you need the original project password and STEP 7 to unlock the MC7 payload.

What is the difference between the STEP 7 v5 and TIA Portal project formats?

STEP 7 v5 uses dBase tables (baustein.dbf, subblk.dbf) and binary memos (.dbt) inside an .s7p archive, with MC7 bytecode. TIA Portal projects use an XML manifest at the project root plus .bin blobs inside a folder hierarchy; they have no .dbf files. Tools written for one format do not work on the other.

Back to blog