Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 12 additions & 303 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,16 @@
> Current version: **v6.0.1**
---

> [!WARNING]
> **This datapack is considered safe to use, but it is still actively receiving security improvements, bug fixes, and new features. Please keep it up to date.**
>
> **Do not copy `datalib:input` or `datalib:engine` into your own datapack.** It is an internal implementation detail and may change without notice between releases.

---

> [!NOTE]
> /reload is no longer required. dataLib initializes automatically, and player-targeted commands (such as tellraw @s) are executed when the first player joins the world.
---
> ⚠️ A `/reload` is still required after installation or after adding the load hook below. Automatic first-join initialization is not implemented in this version — do not rely on this until verified in-game with repeated reloads.

> [!NOTE]
> **v6.0.1:** the stage-0 load entry point was renamed `dl_load:_` → `dl_load:main`, the marker-entity + `say` broadcast pattern was replaced everywhere with `tellraw` (using the current `click_event`/`command` button format — the old `clickEvent`/`value` field names were renamed by Mojang as of 1.21.5), and the load pipeline was split into `dl_load:resolve/*` (version + dependency resolution) and `dl_load:loader/*` (scoreboard/storage initialization).
---

> 🛡️ **This is a Minecraft Datapack — it contains no executables or scripts outside of `.mcfunction` files.**
> Some antivirus software may flag `.mcfunction` files as suspicious due to macro-like syntax. This is a **false positive**. The pack has been scanned on [VirusTotal](https://www.virustotal.com) and returned clean.
> **Only download from this official repository.** Do not trust redistributed or repackaged versions from third-party sources.
Expand All @@ -31,9 +27,9 @@

## 📦 Installation

1. Place dataLib-full.zip into <world>/datapacks/
1. Place [dataLib.zip](https://cdn.modrinth.com/data/jYMa7nhW/versions/EwBBaMuX/dataLib.zip?mr_download_reason=standalone) into `<world>/datapacks/`

2. Add the following logic to your datapack's initialization files:
2. Add the following logic to your datapack's `load` tag. Replace `<namespace>` with your own datapack's namespace (e.g. `mypack`) — this applies only to the function names, never to `datalib:engine`, which is dataLib's own fixed storage and must not be changed:

```mcfunction
#> <namespace>:load
Expand All @@ -43,14 +39,17 @@ execute unless data storage datalib:engine {global:{loaded:1b}} run function <na
```mcfunction
#> <namespace>:load_datalib

execute if data storage <namespace>:engine {loaded_datalib:1b} run return 0
# Guard checks the SAME storage/path the trigger condition checks (datalib:engine global.loaded).
execute if data storage datalib:engine {global:{loaded:1b}} run return 0

function dl_load:load/yes
function dl_load:load/fork_no

data modify storage <namespace>:engine loaded_datalib set value 1b
data modify storage datalib:engine global.loaded set value 1b
```

**Fixed bug:** an earlier version checked `datalib:engine {global:{loaded:1b}}` in the trigger but set `<namespace>:engine loaded_datalib` in the guard — two different storages, two different paths. The set never satisfied the trigger's condition, so `load_datalib` re-ran every time the load tag fired. This doesn't crash anything, but it silently re-triggers `dl_load:load/yes` and `fork_no` on every reload, which can accumulate side effects depending on what those functions do. Both the check and the set must target `datalib:engine global.loaded`. If you're updating from an older copy of this README, verify by reloading multiple times and confirming `load_datalib` does NOT re-run after the first load.

---

## 🏗️ Storage Architecture
Expand All @@ -59,6 +58,7 @@ data modify storage <namespace>:engine loaded_datalib set value 1b
datalib:engine (persistent data)
├── global
│ ├── version: "v6.0.1"
│ ├── loaded: 1b
│ └── tick: <int>
├── players
│ └── Steve { coins:150, level:5, xp:2300, online:1b, ... }
Expand All @@ -73,36 +73,7 @@ datalib:input (sending data to a function)
datalib:output (receiving results from a function)
```

---

## 🔍 Predicate Reference

Used with `execute if predicate <id>`.

| Predicate | Description |
|---|---|
| `datalib:is_survival` | Player is in survival mode |
| `datalib:is_creative` | Player is in creative mode |
| `datalib:has_empty_mainhand` | Main hand is empty |
| `datalib:is_full_health` | Player is at full health (20 HP) |
| `datalib:is_sneaking` | Player is sneaking |
| `datalib:is_sprinting` | Player is sprinting |
| `datalib:is_burning` | Player is on fire |
| `datalib:is_on_ground` | Player is on the ground |
| `datalib:is_daytime` | Daytime (0–12000 ticks) |
| `datalib:is_raining` | It is raining |
| `datalib:is_thundering` | There is a thunderstorm |
| `datalib:in_overworld` | Player is in the Overworld |
| `datalib:in_nether` | Player is in the Nether |
| `datalib:in_end` | Player is in the End |
| `datalib:is_hungry` | Food level is below 20 (`datalib.food` score) |
| `datalib:health_below_half` | Health is at or below 10 HP (`datalib.health` score) |
| `datalib:is_holding_sword` | Main hand holds any item in `#minecraft:swords` |
| `datalib:is_flying` | Player is flying (creative/spectator flight or elytra without gliding) |
| `datalib:is_in_water` | Player's location fluid tag is `#minecraft:water` |
| `datalib:weather_clear` | Not raining and not thundering |

Full reference: [Predicate Reference](https://github.com/runtoolkit/dataLib-dp/wiki)
**Note:** All dataLib-owned state lives under the `datalib:` namespace only. Never mix a consuming pack's own namespace into dataLib's load-flag logic — that mismatch was the source of the bug above.

---

Expand Down Expand Up @@ -150,273 +121,11 @@ function datalib:core/lib/string/replace
# datalib:output string.result → "Hello Everyone"
```

---

## 🔑 Permission System

Per-player permission tags, gated behind admin status. Admins (`datalib.admin` tag) can grant/revoke arbitrary permission strings; a denied check plays a sound and shows the player a "no permission" message.

```mcfunction
$data modify storage datalib:input perm set value {player:"Steve",perm:"build"}
function datalib:api/perm/grant with storage datalib:input perm
# grants tag perm.build to Steve, records advancement datalib:api/perm/build

$data modify storage datalib:input perm set value {player:"Steve",perm:"build"}
function datalib:api/perm/check with storage datalib:input perm
# returns 1 if Steve is admin OR holds tag perm.build, else 0 (and notifies the player)
```

Related: `perm/revoke`, `perm/has`, `perm/list`, `perm/clear`, plus `perm/trigger/*` for binding permissions to `/trigger` commands.

---

## ⚠️ `test_block` Log Spam Risk

Not a vulnerability in dataLib or in Minecraft/Log4j — this is a config pitfall to avoid if any part of your setup uses `minecraft:test_block`.

`minecraft:test_block[mode=log]` is designed to write its `message` field to the log on every redstone trigger. If a `test_block` in `mode=log` is wired to a constant power source (a `redstone_block`, or a repeating trigger inside a `minecraft:tick` function), it will write that line to the log continuously — every tick if triggered from `tick`. This is expected behavior of the block, not a bug, but repeated/automated triggering can grow log files quickly and add disk/IO load on a server.

This has no relation to Log4j or any CVE — it's a plain log call (`LOGGER.info(message)`), not a lookup/JNDI-style resolution path.

**If you use `test_block` anywhere in a datapack:**
- Don't wire `mode=log` test_blocks to `minecraft:tick` or any always-on power source.
- Prefer `mode=accept`/`mode=fail` for actual gametest logic, and reserve `mode=log` for one-off manual debugging.
- If you inherited a world with one already spamming, break the redstone connection or `/setblock` over it to remove it.

---

## 🛡️ Security Levels

A separate, numeric permission tier (`dl.perm_level` scoreboard, 0–4) that gates access to command-execution features such as `cb/run` — independent from the tag-based Permission System above.

| Level | Meaning |
|---|---|
| 0 | No access (default for new players) |
| 1 | Basic — `cmd/` functions requiring `admin_min_level=1` |
| 2 | Standard — full `cmd/` access (default) |
| 3 | Elevated — may trigger `$$(cmd)` execution |
| 4 | Super — may trigger `$$(cmd)` even in sandbox mode |

```mcfunction
$function datalib:api/security/set_level {player:"Steve",level:3}
$function datalib:api/security/get_level {player:"Steve"}
```

> **Note:** if `admin_can_override` is `0b`, even level-4 players are still capped by the `security.sandbox_cmd_min_level` config floor.

---

## ⏱️ Command Block Callbacks (`cb/`)

Runs an arbitrary command string through a temporary command block instead of `execute run`, with optional scheduling. Gated by the Security Levels system above (`security.cmd_min_level`).

```mcfunction
# Immediate — fires next tick, cleaned up 2 ticks later
data modify storage datalib:input cb set value {cmd:"say hello"}
function datalib:api/cb/run

# Delayed — pushes onto an internal delay queue
data modify storage datalib:input cb set value {cmd:"say delayed!",delay:40}
function datalib:api/cb/run_delayed
```

`x`/`y`/`z` in the input default to `0 -64 0`. Related: `cb/run_sequence`, `cb/cancel`, `cb/list`, `cb/queue_size`.

---

## 💉 Injecting into Another Datapack

These methods **do not merge dataLib into your source datapack.** Your original datapack folder is left untouched. Each method reads a target datapack path, copies `datalib` in alongside it, patches a generated hook into `<namespace>:load`, and writes the result as a **new, separate zip** next to your original — producing two outputs:

- `dataLib-full.zip` — dataLib alone, unchanged (from `./gradlew zipFull`)
- `<target>-injected.zip` — your datapack + dataLib + the load hook, packaged together

None of the four methods below write to your source files. They only read from `datapacks/dataLib` (or `dataLib-full.zip`) and the target path you give them, and write to an output directory.

---

### Method 1 — GitHub Actions

Add this as a **reusable workflow** (`.github/workflows/inject.yml`) in your *own* datapack repo, or call it via `workflow_call` from `runtoolkit/dataLib-dp`. It checks out both repos, copies `datalib` into a scratch copy of your pack, patches the hook, and uploads the result as a build artifact — your repo's tracked files are never modified or committed back.

```yaml
name: Inject dataLib

on:
workflow_dispatch:
inputs:
target_namespace:
description: "Your datapack's namespace (e.g. mypack)"
required: true
datalib_ref:
description: "dataLib-dp ref/tag to inject"
default: "main"

jobs:
inject:
runs-on: ubuntu-latest
steps:
- name: Checkout target datapack
uses: actions/checkout@v6
with:
path: target

- name: Checkout dataLib-dp
uses: actions/checkout@v6
with:
repository: runtoolkit/dataLib-dp
ref: ${{ inputs.datalib_ref }}
path: datalib-src

- name: Build dataLib-full.zip
working-directory: datalib-src
run: |
chmod +x gradlew
./gradlew zipFull --no-daemon

- name: Assemble injected copy (scratch dir, source untouched)
env:
NS: ${{ inputs.target_namespace }}
run: |
set -euo pipefail
mkdir -p out/injected
cp -r target/. out/injected/
mkdir -p tmp_datalib
unzip -q datalib-src/build/dist/dataLib-full.zip -d tmp_datalib
cp -r tmp_datalib/data/datalib out/injected/data/
cp -r tmp_datalib/data/dl_load out/injected/data/
cp -r tmp_datalib/data/stringlib out/injected/data/
cp -r tmp_datalib/data/datalib.main out/injected/data/
cp -r tmp_datalib/data/stringlib out/injected/data/

LOAD_FILE="out/injected/data/${NS}/function/load.mcfunction"
mkdir -p "$(dirname "$LOAD_FILE")"
if ! grep -q "loaded_datalib" "$LOAD_FILE" 2>/dev/null; then
cat <<EOF >> "$LOAD_FILE"

execute unless data storage datalib:engine {global:{loaded:1b}} run function ${NS}:load_datalib
EOF
fi

cat <<EOF > "out/injected/data/${NS}/function/load_datalib.mcfunction"
execute if data storage ${NS}:engine {loaded_datalib:1b} run return 0

function dl_load:load/yes
function dl_load:load/fork_no

data modify storage ${NS}:engine loaded_datalib set value 1b
EOF

- name: Zip injected pack
run: cd out/injected && zip -qr ../../${{ inputs.target_namespace }}-injected.zip .

- uses: actions/upload-artifact@v7
with:
name: dataLib-full
path: datalib-src/build/dist/dataLib-full.zip
if-no-files-found: error

- uses: actions/upload-artifact@v7
with:
name: ${{ inputs.target_namespace }}-injected
path: ${{ inputs.target_namespace }}-injected.zip
if-no-files-found: error
```

Two artifacts are produced per run: the plain `dataLib-full.zip` and `<namespace>-injected.zip`. Neither is committed to any repo.

---

### Method 2 — Bash

```bash
#!/usr/bin/env bash
# inject-datalib.sh
# Usage: ./inject-datalib.sh <path-to-target-datapack> <target-namespace> [dataLib-full.zip]
set -euo pipefail

TARGET_DIR="${1:?target datapack path required}"
NAMESPACE="${2:?target namespace required}"
DATALIB_ZIP="${3:-dataLib-full.zip}"

if [ ! -d "$TARGET_DIR" ]; then
echo "error: target datapack not found: $TARGET_DIR" >&2
exit 1
fi

# If dataLib-full.zip is missing, clone dataLib-dp and build it.
if [ ! -f "$DATALIB_ZIP" ]; then
echo "warning: $DATALIB_ZIP not found. Cloning and building dataLib-dp..." >&2
BUILD_DIR="$(mktemp -d)"
git clone https://github.com/runtoolkit/dataLib-dp.git "$BUILD_DIR/dataLib-dp"
(
cd "$BUILD_DIR/dataLib-dp"
chmod +x ./gradlew
./gradlew zipFull
)
# Locate the produced zip (adjust glob if the actual output path differs).
BUILT_ZIP="$(find "$BUILD_DIR/dataLib-dp" -maxdepth 6 -iname 'dataLib-full*.zip' -print -quit)"
if [ -z "$BUILT_ZIP" ]; then
echo "error: zipFull ran but no dataLib-full*.zip was found under build output." >&2
rm -rf "$BUILD_DIR"
exit 1
fi
cp "$BUILT_ZIP" "$DATALIB_ZIP"
rm -rf "$BUILD_DIR"
fi

OUT_DIR="$(mktemp -d)"
trap 'rm -rf "$OUT_DIR"' EXIT

# Copy target into scratch — source datapack is never touched.
cp -r "$TARGET_DIR/." "$OUT_DIR/"

# Unpack dataLib and copy only its known data folders in.
UNZIP_DIR="$(mktemp -d)"
unzip -q "$DATALIB_ZIP" -d "$UNZIP_DIR"
mkdir -p "$OUT_DIR/data"

for module in datalib datalib.main dl_load player_action stringlib; do
SRC="$UNZIP_DIR/data/$module"
if [ ! -d "$SRC" ]; then
echo "error: expected module missing from zip: data/$module" >&2
rm -rf "$UNZIP_DIR"
exit 1
fi
cp -r "$SRC" "$OUT_DIR/data/"
done
rm -rf "$UNZIP_DIR"

# Patch the load hook.
LOAD_FILE="$OUT_DIR/data/$NAMESPACE/function/load.mcfunction"
mkdir -p "$(dirname "$LOAD_FILE")"
touch "$LOAD_FILE"
if ! grep -q "loaded_datalib" "$LOAD_FILE"; then
{
echo ""
echo "execute unless data storage datalib:engine {global:{loaded:1b}} run function ${NAMESPACE}:load_datalib"
} >> "$LOAD_FILE"
fi

cat > "$OUT_DIR/data/$NAMESPACE/function/load_datalib.mcfunction" <<EOF
execute if data storage ${NAMESPACE}:engine {loaded_datalib:1b} run return 0
function dl_load:load/yes
function dl_load:load/fork_no
data modify storage ${NAMESPACE}:engine loaded_datalib set value 1b
EOF

OUT_ZIP="$(basename "$TARGET_DIR")-injected.zip"
( cd "$OUT_DIR" && zip -qr "$OLDPWD/$OUT_ZIP" . )
echo "Wrote $OUT_ZIP ($TARGET_DIR was not modified)"
```

---

## 💬 Support

[![Issues](https://img.shields.io/github/issues/runtoolkit/dataLib-dp?style=for-the-badge)](https://github.com/runtoolkit/dataLib-dp/issues)
[![Discussions](https://img.shields.io/github/discussions/runtoolkit/dataLib-dp?style=for-the-badge&logo=github&color=blue)](https://github.com/runtoolkit/dataLib-dp/discussions)

---

*dataLib v6.0.1 | MC Java 26.3-snapshot-5 | Pure Datapack*
*dataLib v6.0.1 | MC Java 26.2 | Pure Datapack*