Skip to content

Latest commit

 

History

History
178 lines (149 loc) · 8.82 KB

FEATURES.md

File metadata and controls

178 lines (149 loc) · 8.82 KB

TextFilter feature inventory

This is the observable behavior contract for the current Minecraft 26.2 mod version. It retains the behavior of the latest pre-port implementation (0193d71, Minecraft 1.21.10) except where the sections below document the current port explicitly.

Lifecycle and packaging

  • TextFilter is a server-only Fabric mod (environment: "server") intended for dedicated servers. It is not loaded in a client environment.
  • Initialization loads or creates the config, compiles the configured patterns and replacements, extends the in-memory whitelist with replacement output symbols, then registers the /textfilter command.
  • The packaged mod includes its permissions API compatibility library and RE2/J. Fabric API is an external runtime dependency.
  • Required mixins cover exactly three game classes. A missing injection is a startup error rather than a silently disabled filter.

Configuration

  • The config path is <fabric-config-dir>/textfilter.jsonc.

  • Gson reads JSONC with line and block comments. Generated files include field documentation.

  • The fields shared by every filtering surface are:

    • whitelist: a regex matched against each UTF-16 character.
    • blacklist: a regex that vetoes otherwise-whitelisted characters.
    • replacements: case-sensitive RE2/J regex keys with literal replacement values.
  • Defaults are:

    • Whitelist: [\\u0020-\\u007e \\n § а-я іїєґ \\u00C0-\\u024F \\u2010-\\u2027 ≈≠∞±×÷€₴]
    • Blacklist: [ыъэ]
  • Replacement: case-insensitive standalone xd, xD, XD, xdd, XDDD, and similar variants to 😂, using (?i)\\bxd+\\b.

  • Whitelist and blacklist patterns use case-insensitive Java Unicode matching. Replacement patterns use RE2/J and remain case-sensitive unless they contain an inline flag such as (?i).

  • Replacement patterns are compiled once at initialization or reload. RE2/J guarantees linear-time matching and does not support backreferences, lookaround, or every Java regex construct. Its \b boundary uses ASCII word characters.

  • Every UTF-16 unit present in a non-null replacement value is escaped as a literal alternative and added to the effective in-memory whitelist. This does not mutate the configured whitelist or rewrite the config file.

  • Blacklist matching still runs after the effective whitelist and can veto an automatically allowed replacement symbol.

  • Config format version 2 is stored in configVersion. If JSONC is absent, an old textfilter.json is imported once; its literal replacement keys are escaped into equivalent RE2/J patterns. The old JSON remains untouched and is ignored afterward.

  • A missing file is created with defaults through a temporary file. The store uses an atomic move when supported and falls back to a replacing move otherwise. Empty or malformed JSONC, read errors, invalid patterns, and unsupported versions use in-memory defaults during startup without overwriting the broken file. Unknown fields are ignored.

  • Omitted fields in a version 2 file retain their Java defaults without rewriting the file. A legacy file without replacements migrates the old literal xd default.

  • replacements: null disables replacements. A null whitelist/blacklist or an invalid whitelist, blacklist, or replacement regex fails pattern reload.

  • Config values are cached. Disk edits take effect on restart or successful /textfilter reload.

Filtering pipeline

Every sign line, book string, anvil name, and player chat message uses the same pipeline:

  1. Null and empty strings are returned unchanged.
  2. Each UTF-16 char must fully match the configured whitelist or a symbol from a replacement value.
  3. A whitelisted character is removed if it fully matches the blacklist.
  4. RE2/J replacement patterns are applied globally and sequentially in map iteration order, with each configured value inserted literally.

Additional observable rules:

  • An empty whitelist or exact [] allows only symbols found in replacement values; without replacements it removes all characters from nonempty input.
  • An empty blacklist or exact [] vetoes nothing.
  • Multi-character regex matches are not performed across input characters.
  • Supplementary Unicode characters are evaluated as two surrogate units.
  • Replacement output is not filtered during the pass that creates it. Its symbols are included in the runtime whitelist so a later pass retains them, unless the blacklist explicitly vetoes them.
  • Replacement patterns are global, case-sensitive by default, and may cascade into later rules. Overlapping rules therefore depend on map iteration order.
  • Replacement values are literal. $1, $, and backslashes do not expand capture groups.
  • A rule that has no match returns its input without copying it. Matching rules allocate a new intermediate string. Processing cost still grows with the number of rules and the length of each intermediate value.
  • . follows Java regex semantics per UTF-16 unit; it does not match every line terminator.

Filtered surfaces

Signs

  • A sign edit filters all four lines on the edited face.
  • Global or sign bypass preserves the vanilla result unchanged.
  • Without bypass, each line is flattened to plain text, filtered, and rebuilt as a literal component. Both raw and vanilla-filtered line arrays receive that result.
  • Dye color and glowing state survive. Per-line style, translation, click, and hover structure do not.
  • A non-ServerPlayer still takes the filtering path without a permission call.
  • Existing signs are not retroactively filtered merely by loading or rendering.

Books

  • Book edits and signing filter the optional title and every page before Mojang's own text filter runs.
  • Global or book bypass skips only TextFilter; vanilla filtering still runs.
  • Stored books and books created through commands/data are not retroactively filtered.

Anvils

  • The pending item name is filtered before vanilla blank-name, equality, result, and rename-cost logic completes.
  • Global or anvil bypass leaves the original pending name intact.
  • A non-ServerPlayer path still filters without a permission call.
  • Vanilla validates the submitted input first; replacement output may introduce characters that validation would reject or expand beyond the input length.
  • The mixin writes the filtered value back to the pending name. A later result recalculation can consequently filter that prior result again.

Player chat

  • Vanilla decodes and verifies signed player chat before TextFilter changes it.
  • TextFilter filters the canonical message body before vanilla text filtering and the first chat decorator run.
  • Global or chat bypass returns the original chat message object and retains its vanilla signed-message path.
  • If canonical text is unchanged, the original message object, body, signature, and link are retained.
  • If changed, a new unsigned player message preserves the sender UUID and uses the filtered text as its canonical body. Signature, session/link state, timestamp, salt, last-seen messages, prior unsigned content, and filter mask are reset using vanilla unsigned-message semantics.
  • Vanilla text filtering and decorators subsequently operate on the filtered canonical body. This prevents formatters such as Styled Chat 2.13.0+26.2 from restoring the original text by rereading the signed body.
  • Changed chat is not cryptographically signed by the player and may be marked or hidden by client secure-chat settings. The original input was verified only when vanilla supplied a signed session.
  • Commands, system messages, command output, death messages, and advancement messages are outside the filter.

Permissions and command

  • Permission nodes are:

    • textfilter.bypass
    • textfilter.signBypass
    • textfilter.bookBypass
    • textfilter.anvilBypass
    • textfilter.chatBypass
    • textfilter.reload
  • The global bypass OR the matching category bypass skips TextFilter. An explicit denial of one does not override a grant of the other.

  • Permission fallback is false for filtering. Operator status alone does not bypass signs, books, anvils, or chat.

  • /textfilter reload is available to vanilla permission level 4 OR holders of textfilter.reload. Because this is an explicit OR, a permission-provider denial does not override level 4.

  • The root /textfilter node has no action by itself.

  • A successful reload returns command result 1 and sends green feedback with exact text TextFilter successfully reloaded, broadcast to operators.

  • If parsing or pattern compilation fails, success feedback is not sent and the previously active configuration remains installed.

Deliberate exclusions

TextFilter has no generic hook for entity names, name tags, scoreboard text, command arguments/output, existing stored books, existing signs, or arbitrary item names. The four surfaces above are the complete feature set.