Skip to content

ClientDB (DBC & DB2)

The client-side databases — every file under DBFilesClient/: maps, spells, items, creature display info, taxi nodes… wowlib.db reads and writes all of them through one runtime-schema table: the schema for any table of any supported client era comes from the community WoWDBDefs definitions baked into the library as data, so nothing per-table is generated or compiled — and every era of every table is available from the same class.

wowlib speaks every on-disk database format a last-minor client ships:

Clients .dbc .db2
Vanilla / TBC / WotLK WDBC —
Cata / MoP / WoD WDBC WDB2
Legion WDBC leftovers WDC1
BfA / Shadowlands — WDC3
Dragonflight — WDC4 / WDC5
The War Within — WDC5

Reads sniff the file magic, so you never name the format; a loaded table re-emits the format it was read from, and a fresh table picks the era's native one.

The table surface

Everything lives on the generic table:

  • Table.open(name, version) resolves the era's schema by name (wowlib.db.table_names() lists what exists) and returns an empty table ready to read any file of that table.
  • table[i] is a live Record view — columns read and write as attributes (row.map_name), shaped by the column.
  • table.column(name) hands out a whole column: a zero-copy numpy view for numeric columns, lists for strings.
  • Cell-level access (get_int/set_string/…) addresses (row, column, element) directly — the shape renderers and bulk editors want.

Columns keep their WoWDBDefs names, converted to snake_case. Pre-Cataclysm localized-string columns expose one slot per client language (the Record attribute is the list[str] of slots; locstring_flags rides alongside); Cataclysm onwards they are plain strings.

from wowlib import FileKey, versions
from wowlib.fs import FileSystem, FileSystemSettings
from wowlib.db import EncryptedPolicy, Table

settings = FileSystemSettings("/Games/WoW 3.3.5a", versions.wotlk)
with FileSystem.open(settings) as fs:
    table = Table.open("Map", versions.wotlk)
    table.read(fs, FileKey("DBFilesClient/Map.dbc"))

    for row in table:
        print(row.id, row.directory, row.map_name[0])   # slot 0 = enUS

    names = table[0].map_name
    names[0] = "Azeroth Reforged"
    table[0].map_name = names
    ids = table.column("id")                            # zero-copy numpy
    data = table.write(EncryptedPolicy.Preserve)        # bytes, or write(fs, key, …)

Round-trip guarantees

  • WDBC / WDB2 (pre-Legion): byte-perfect — an unmodified table writes back identical bytes, string-block quirks included.
  • WDC1/3/4/5: canonical re-encode — the write is a fresh, tightly packed encoding (bit widths, pallet/common compression and copy tables re-derived) that re-reads to equal values. Multi-section tables may reorder rows (records are id-keyed); single-section tables keep order.

Encrypted sections

Modern .db2 files can contain TACT-encrypted sections. Rows whose keys the storage holds decode normally; rows behind missing keys are absent from records and reported via encrypted_sections / fully_decoded. Register keys with FileSystem.import_keys to decode them. On write, EncryptedPolicy picks between re-emitting a keyless file verbatim (Preserve, the default choice — edits to decoded rows are not applied then) and writing a plain table of just the decoded rows (Drop).

Shared value types

The table classes themselves live on the generic table; this renders only what they share.

Client-side database files (DBFilesClient): the runtime-schema Table and the value types it shares.

TableBase

TableBase()

The common surface of every client-database table: decode (read), encode (write), validation, and the preserved decode state. Concrete tables add their typed records.

strings property

strings: StringBlock

The preserved string block the record string fields were decoded from; offsets never move, write() appends new strings past its end.

encrypted_sections property

encrypted_sections: list[EncryptedSection]

The encrypted sections skipped on read: their records are not in records, but the file re-writes them verbatim.

fully_decoded property

fully_decoded: bool

Whether the whole table decoded — false when encrypted sections were skipped.

read

read(data: bytes) -> None
read(fs: FileSystem, key: FileKey) -> None

Decode a table file image.

Parameters:

Name Type Description Default
data bytes

the whole file content

required

Returns:

Type Description
None

nothing; raises on malformed input or a schema mismatch

Load the table from a client filesystem.

Parameters:

Name Description Default
fs

the filesystem gateway

required
key

the file to read

required

Returns:

Type Description
None

nothing; raises when loading fails

write

write(policy: EncryptedPolicy) -> bytes
write() -> bytes
write(fs: FileSystem, key: FileKey, policy: EncryptedPolicy) -> None
write(fs: FileSystem, key: FileKey) -> None

Serialize the table to a file image; a loaded table re-emits the format it was read from. policy decides how keyless encrypted sections are handled.

Parameters:

Name Type Description Default
policy EncryptedPolicy

keyless-section handling (WDC only)

required

Returns:

Type Description
bytes

the file bytes

Serialize the table into a client filesystem (project overlay); the target path's extension picks .dbc/.db2 in the mixed eras.

Parameters:

Name Type Description Default
fs

the filesystem gateway

required
key

the file to write

required
policy EncryptedPolicy

keyless-section handling (WDC only)

required

Returns:

Type Description
bytes

nothing; raises when saving fails

validate

validate() -> ValidationReport

Check the logical integrity contracts the records must satisfy to survive a write and load in the client: the primary key stays unique, and no string holds an embedded NUL the string block would truncate. write() never runs this.

Returns:

Type Description
ValidationReport

every violated contract, in record order

ensure_valid

ensure_valid() -> None

Validate and raise on the first error instead of returning a report — the assert-style face of validate().

Returns:

Type Description
None

nothing; raises when validate() finds any error

LocString8

LocString8()
LocString8(values: Sequence[str] = ['', '', '', '', '', '', '', ''], flags: int = 0)

A localized string column of a pre-Cataclysm client database: one string per language slot in the client's fixed column order, plus the locale flags field. Use at()/set() to address slots by Locale.

values property

values: list[str]

The language slot values in client column order (0 enUS, 1 koKR, 2 frFR, 3 deDE, 4 zhCN, 5 zhTW, 6 esES, 7 esMX, then ruRU/ptBR/itIT for 16-slot eras); empty for languages the file does not carry.

flags property writable

flags: int

The trailing locale flags field; a bitmask the client never reads, preserved verbatim.

at

at(locale: Locale) -> str

The string stored for a locale; empty when the locale has no slot in this client era.

Parameters:

Name Type Description Default
locale Locale

the locale to look up

required

Returns:

Type Description
str

the slot value, empty when absent

set

set(locale: Locale, value: str) -> None

Store a string in a locale's slot.

Parameters:

Name Type Description Default
locale Locale

the locale to write

required
value str

the string to store

required

Returns:

Type Description
None

nothing; raises when the locale has no slot in this client era

LocString16

LocString16()
LocString16(values: Sequence[str] = ..., flags: int = 0)

A localized string column of a pre-Cataclysm client database: one string per language slot in the client's fixed column order, plus the locale flags field. Use at()/set() to address slots by Locale.

values property

values: list[str]

The language slot values in client column order (0 enUS, 1 koKR, 2 frFR, 3 deDE, 4 zhCN, 5 zhTW, 6 esES, 7 esMX, then ruRU/ptBR/itIT for 16-slot eras); empty for languages the file does not carry.

flags property writable

flags: int

The trailing locale flags field; a bitmask the client never reads, preserved verbatim.

at

at(locale: Locale) -> str

The string stored for a locale; empty when the locale has no slot in this client era.

Parameters:

Name Type Description Default
locale Locale

the locale to look up

required

Returns:

Type Description
str

the slot value, empty when absent

set

set(locale: Locale, value: str) -> None

Store a string in a locale's slot.

Parameters:

Name Type Description Default
locale Locale

the locale to write

required
value str

the string to store

required

Returns:

Type Description
None

nothing; raises when the locale has no slot in this client era

EncryptedPolicy

Bases: IntEnum

How a client-database write handles keyless encrypted sections: Preserve re-emits the file's original bytes verbatim (encrypted content intact, edits to decoded rows NOT applied); Drop writes only the decoded rows as a plain unencrypted table, discarding the rows behind missing keys.

Preserve class-attribute instance-attribute

Preserve = 0

Drop class-attribute instance-attribute

Drop = 1

EncryptedSection

EncryptedSection()
EncryptedSection(key_hash: int, record_count: int, ids: list[int])

An encrypted WDC section: its records are behind a TACT key wowlib does not hold, so they are absent from records.

key_hash property writable

key_hash: int

The section's TACT key lookup hash (names the missing key).

record_count property writable

record_count: int

How many records the encrypted section holds.

ids property writable

ids: list[int]

The ids of the encrypted records, when the file lists them.